diff --git a/src/Cursor.cpp b/src/Cursor.cpp index f6ca0fcf6e..743be4d4b8 100644 --- a/src/Cursor.cpp +++ b/src/Cursor.cpp @@ -1018,139 +1018,17 @@ bool Cursor::posVisToNewRow(bool movingLeft) void Cursor::posVisToRowExtremity(bool left) { - // prepare bidi tables - Paragraph const & par = paragraph(); - Buffer const & buf = *buffer(); - Row const & row = textRow(); - Bidi bidi; - bidi.computeTables(par, buf, row); - LYXERR(Debug::RTL, "entering extremity: " << pit() << "," << pos() << "," << (boundary() ? 1 : 0)); - if (left) { // move to leftmost position - // if this is an RTL paragraph, and we're at the last row in the - // paragraph, move to lastpos - if (par.isRTL(buf.params()) && row.endpos() == lastpos()) - pos() = lastpos(); - else { - pos() = bidi.vis2log(row.pos()); + TextMetrics const & tm = bv_->textMetrics(text()); + // Looking for extremities is like clicking on the left or the + // right of the row. + int x = tm.origin().x_ + (left ? 0 : textRow().width()); + bool b = false; + pos() = tm.getPosNearX(textRow(), x, b); + boundary(b); - // Moving to the leftmost position in the row, - // the cursor should normally be placed to the - // *left* of the leftmost position. A very - // common exception, though, is if the - // leftmost character also happens to be the - // separator at the (logical) end of the row - // --- in this case, the separator is - // positioned beyond the left margin, and we - // don't want to move the cursor there (moving - // to the left of the separator is equivalent - // to moving to the next line). So, in this - // case we actually want to place the cursor - // to the *right* of the leftmost position - // (the separator). Another exception is if - // we're moving to the logically last position - // in the row, which is *not* a separator: - // this means that the entire row has no - // separators (if there were any, the row - // would have been broken there); and - // therefore in this case we also move to the - // *right* of the last position (this - // indicates to the user that there is no - // space after this position, and is - // consistent with the behavior in the middle - // of a row --- moving right or left moves to - // the next/previous character; if we were to - // move to the *left* of this position, that - // would simulate a separator which is not - // really there!). Finally, there is an - // exception to the previous exception: if - // this non-separator-but-last-position-in-row - // is an inset, then we *do* want to stay to - // the left of it anyway: this is the - // "boundary" which we simulate at insets. - - // Another exception is when row.endpos() is - // 0. - - // do we want to be to the right of pos? - // as explained above, if at last pos in row, stay to the right - bool const right_of_pos = row.endpos() > 0 - && pos() == row.endpos() - 1 && !par.isInset(pos()); - - // Now we know if we want to be to the left or to the right of pos, - // let's make sure we are where we want to be. - bool const new_pos_is_RTL = - par.getFontSettings(buf.params(), pos()).isVisibleRightToLeft(); - - if (new_pos_is_RTL != right_of_pos) { - ++pos(); - boundary(true); - } - } - } else { - // move to rightmost position - // if this is an LTR paragraph, and we're at the last row in the - // paragraph, move to lastpos - if (!par.isRTL(buf.params()) && row.endpos() == lastpos()) - pos() = lastpos(); - else { - pos() = row.endpos() > 0 ? bidi.vis2log(row.endpos() - 1) : 0; - - // Moving to the rightmost position in the - // row, the cursor should normally be placed - // to the *right* of the rightmost position. A - // very common exception, though, is if the - // rightmost character also happens to be the - // separator at the (logical) end of the row - // --- in this case, the separator is - // positioned beyond the right margin, and we - // don't want to move the cursor there (moving - // to the right of the separator is equivalent - // to moving to the next line). So, in this - // case we actually want to place the cursor - // to the *left* of the rightmost position - // (the separator). Another exception is if - // we're moving to the logically last position - // in the row, which is *not* a separator: - // this means that the entire row has no - // separators (if there were any, the row - // would have been broken there); and - // therefore in this case we also move to the - // *left* of the last position (this indicates - // to the user that there is no space after - // this position, and is consistent with the - // behavior in the middle of a row --- moving - // right or left moves to the next/previous - // character; if we were to move to the - // *right* of this position, that would - // simulate a separator which is not really - // there!). Finally, there is an exception to - // the previous exception: if this - // non-separator-but-last-position-in-row is - // an inset, then we *do* want to stay to the - // right of it anyway: this is the "boundary" - // which we simulate at insets. Another - // exception is when row.endpos() is 0. - - // do we want to be to the left of pos? - // as explained above, if at last pos in row, stay to the left, - // unless the last position is the same as the first. - bool const left_of_pos = row.endpos() > 0 - && pos() == row.endpos() - 1 && !par.isInset(pos()); - - // Now we know if we want to be to the left or to the right of pos, - // let's make sure we are where we want to be. - bool const new_pos_is_RTL = - par.getFontSettings(buf.params(), pos()).isVisibleRightToLeft(); - - if (new_pos_is_RTL == left_of_pos) { - ++pos(); - boundary(true); - } - } - } LYXERR(Debug::RTL, "leaving extremity: " << pit() << "," << pos() << "," << (boundary() ? 1 : 0)); } diff --git a/src/Row.cpp b/src/Row.cpp index 57f02e2987..0974265328 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -24,6 +24,7 @@ #include "support/debug.h" #include "support/lassert.h" +#include "support/lstrings.h" #include "support/lyxalgo.h" #include @@ -32,8 +33,18 @@ using namespace std; 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 @@ -52,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; @@ -68,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: @@ -76,7 +87,6 @@ pos_type Row::Element::x2pos(int &x) const i = 0; x = rtl ? int(full_width()) : 0; break; - case SEPARATOR: case INSET: case SPACE: // those elements contain only one position. Round to @@ -96,24 +106,21 @@ pos_type Row::Element::x2pos(int &x) const } -bool Row::Element::breakAt(int w) +bool Row::Element::breakAt(int w, bool force) { if (type != STRING || dim.wid <= w) return false; bool const rtl = font.isVisibleRightToLeft(); - if (rtl) - w = dim.wid - w; - pos_type new_pos = x2pos(w); - if (new_pos == pos) - return false; - str = str.substr(0, new_pos - pos); - if (rtl) - dim.wid -= w; - else - dim.wid = w; - endpos = new_pos; - return true; + FontMetrics const & fm = theFontMetrics(font); + int x = w; + if(fm.breakAt(str, x, rtl, force)) { + dim.wid = x; + endpos = pos + str.length(); + //lyxerr << "breakAt(" << w << ") Row element Broken at " << x << "(w(str)=" << fm.width(str) << "): e=" << *this << endl; + return true; + } + return false; } @@ -225,9 +232,6 @@ ostream & operator<<(ostream & os, Row::Element const & e) case Row::INSET: os << "INSET: " << to_utf8(e.inset->layoutName()) << ", "; break; - case Row::SEPARATOR: - os << "SEPARATOR: extra=" << e.extra << ", "; - break; case Row::SPACE: os << "SPACE: "; break; @@ -258,6 +262,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()) @@ -278,6 +302,7 @@ void Row::finalizeLast() elt.final = true; if (elt.type == STRING) { + dim_.wid -= elt.dim.wid; elt.dim.wid = theFontMetrics(elt.font).width(elt.str); dim_.wid += elt.dim.wid; } @@ -304,8 +329,16 @@ void Row::add(pos_type const pos, char_type const c, Element e(STRING, pos, f, ch); elements_.push_back(e); } - back().str += c; - back().endpos = pos + 1; + if (back().str.length() % 30 == 0) { + dim_.wid -= back().dim.wid; + back().str += c; + back().endpos = pos + 1; + back().dim.wid = theFontMetrics(back().font).width(back().str); + dim_.wid += back().dim.wid; + } else { + back().str += c; + back().endpos = pos + 1; + } } @@ -323,18 +356,6 @@ void Row::addVirtual(pos_type const pos, docstring const & s, } -void Row::addSeparator(pos_type const pos, char_type const c, - Font const & f, Change const & ch) -{ - finalizeLast(); - Element e(SEPARATOR, pos, f, ch); - e.str += c; - e.dim.wid = theFontMetrics(f).width(c); - elements_.push_back(e); - dim_.wid += e.dim.wid; -} - - void Row::addSpace(pos_type const pos, int const width, Font const & f, Change const & ch) { @@ -357,33 +378,17 @@ void Row::shortenIfNeeded(pos_type const keep, int const w) { if (empty() || width() <= w) return; - Elements::iterator const beg = elements_.begin(); Elements::iterator const end = elements_.end(); - Elements::iterator last_sep = elements_.end(); - int last_width = 0; int wid = left_margin; Elements::iterator cit = beg; for ( ; cit != end ; ++cit) { - if (cit->type == SEPARATOR && cit->pos >= keep) { - last_sep = cit; - last_width = wid; - } - if (wid + cit->dim.wid > w) + if (cit->endpos >= keep && wid + cit->dim.wid > w) break; wid += cit->dim.wid; } - if (last_sep != end) { - // We have found a suitable separator. This is the - // common case. - end_ = last_sep->endpos; - dim_.wid = last_width; - elements_.erase(last_sep, end); - return; - } - if (cit == end) { // This should not happen since the row is too long. LYXERR0("Something is wrong cannot shorten row: " << *this); @@ -397,23 +402,41 @@ void Row::shortenIfNeeded(pos_type const keep, int const w) wid -= cit->dim.wid; } + // Try to break this row cleanly (at word boundary) + if (cit->breakAt(w - wid, false)) { + end_ = cit->endpos; + // after breakAt, there may be spaces at the end of the + // string, but they are not counted in the string length + // (qtextlayout feature, actually). We remove them, but do not + // change the endo of the row, since the spaces at row break + // are invisible. + cit->str = rtrim(cit->str); + cit->endpos = cit->pos + cit->str.length(); + dim_.wid = wid + cit->dim.wid; + // If there are other elements, they should be removed. + elements_.erase(next(cit, 1), end); + return; + } + if (cit != beg) { - // There is no separator, but several elements (probably - // insets) have been added. We can cut at this place. + // There is no separator, but several elements have been + // added. We can cut right here. end_ = cit->pos; dim_.wid = wid; elements_.erase(cit, end); return; } - /* 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 some - * other things after it. + /* 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. */ - if (cit->breakAt(w - left_margin)) { + if (cit->breakAt(w - wid, true)) { end_ = cit->endpos; - dim_.wid = left_margin + cit->dim.wid; + // See comment above. + cit->str = rtrim(cit->str); + cit->endpos = cit->pos + cit->str.length(); + dim_.wid = wid + cit->dim.wid; // If there are other elements, they should be removed. elements_.erase(next(cit, 1), end); } diff --git a/src/Row.h b/src/Row.h index 9039ff8860..92521ef4ba 100644 --- a/src/Row.h +++ b/src/Row.h @@ -29,18 +29,6 @@ namespace lyx { class DocIterator; class Inset; -/** - * FIXME: Change Row object to operate only on integers and not doubles. - * - * This use of double is only useful to distribute the extra - * horizontal space between separators in justified text. If we do - * integer arithmetic, then it is possible to have two groups of - * separators, with size s or s+1. Then strings can be drawn without - * cutting at separators in justfied text, as it is done in - * non-justified text. This will improve performance. - */ - - /** * An on-screen row of text. A paragraph is broken into a RowList for * display. Each Row contains a tokenized description of the contents @@ -58,8 +46,6 @@ public: * correspond to any paragraph contents */ VIRTUAL, - // A stretchable space, basically - SEPARATOR, // An inset INSET, // Some spacing described by its width, not a string @@ -76,7 +62,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. */ @@ -86,10 +75,12 @@ public: * adjusted to the actual pixel position. */ pos_type x2pos(int &x) const; - /** Break the element if possible, so that its width is - * less then \param w. Returns true on success. + /** Break the element if possible, so that its width is less + * than \param w. Returns true on success. When \param force + * is true, the string is cut at any place, other wise it + * respects the row breaking rules of characters. */ - bool breakAt(int w); + bool breakAt(int w, bool force); // Returns the position on left side of the element. pos_type left_pos() const; @@ -109,10 +100,10 @@ public: // Non-zero only if element is an inset Inset const * inset; - // Only non-null for separator elements + // Only non-null for justified rows double extra; - // Non-empty if element is a string or separator + // Non-empty if element is a string or is virtual docstring str; // Font font; @@ -172,6 +163,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); @@ -182,9 +178,6 @@ public: 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); - /// void addSpace(pos_type pos, int width, Font const & f, Change const & ch); /// @@ -239,7 +232,7 @@ public: friend std::ostream & operator<<(std::ostream & os, Row const & row); - /// width of a separator (i.e. space) + /// additional width for separators in justified rows (i.e. space) double separator; /// width of hfills in the label double label_hfill; diff --git a/src/RowPainter.cpp b/src/RowPainter.cpp index 70c32aa9b7..2a25482b34 100644 --- a/src/RowPainter.cpp +++ b/src/RowPainter.cpp @@ -64,8 +64,6 @@ RowPainter::RowPainter(PainterInfo & pi, solid_line_thickness_(1), solid_line_offset_(1), dotted_line_thickness_(1), dotted_line_offset_(2) { - bidi_.computeTables(par_, pi_.base.bv->buffer(), row_); - if (lyxrc.zoom >= 200) { // derive the line thickness from zoom factor // the zoom is given in percent @@ -103,32 +101,33 @@ FontInfo RowPainter::labelFont() const } -int RowPainter::leftMargin() const -{ - return text_metrics_.leftMargin(text_metrics_.width(), pit_, - row_.pos()); -} - // If you want to debug inset metrics uncomment the following line: //#define DEBUG_METRICS // This draws green lines around each inset. -void RowPainter::paintInset(Inset const * inset, pos_type const pos) +void RowPainter::paintInset(Inset const * inset, Font const & font, + Change const & change, + pos_type const pos) { - Font const font = text_metrics_.displayFont(pit_, pos); + // Handle selection + bool const pi_selected = pi_.selected; + Cursor const & cur = pi_.base.bv->cursor(); + if (cur.selection() && cur.text() == &text_ + && cur.normalAnchor().text() == &text_) + pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; LASSERT(inset, return); // Backup full_repaint status because some insets (InsetTabular) // requires a full repaint bool const pi_full_repaint = pi_.full_repaint; bool const pi_do_spellcheck = pi_.do_spellcheck; + Change const pi_change = pi_.change_; pi_.base.font = inset->inheritFont() ? font.fontInfo() : pi_.base.bv->buffer().params().getFont().fontInfo(); - pi_.ltr_pos = (bidi_.level(pos) % 2 == 0); - Change prev_change = change_; - pi_.change_ = change_.changed() ? change_ : par_.lookupChange(pos); + pi_.ltr_pos = !font.isVisibleRightToLeft(); + pi_.change_ = change_.changed() ? change_ : change; pi_.do_spellcheck &= inset->allowSpellCheck(); int const x1 = int(x_); @@ -148,8 +147,9 @@ void RowPainter::paintInset(Inset const * inset, pos_type const pos) // Restore full_repaint status. pi_.full_repaint = pi_full_repaint; - pi_.change_ = prev_change; + pi_.change_ = pi_change; pi_.do_spellcheck = pi_do_spellcheck; + pi_.selected = pi_selected; #ifdef DEBUG_METRICS int const x2 = x1 + dim.wid; @@ -163,14 +163,6 @@ void RowPainter::paintInset(Inset const * inset, pos_type const pos) } -void RowPainter::paintSeparator(double orig_x, double width, - FontInfo const & font) -{ - pi_.pain.textDecoration(font, int(orig_x), yo_, int(width)); - x_ += width; -} - - void RowPainter::paintForeignMark(double orig_x, Language const * lang, int desc) const { if (!lyxrc.mark_foreign_language) @@ -187,14 +179,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. @@ -211,8 +201,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; @@ -227,12 +217,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); @@ -244,120 +234,45 @@ 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(); } -void RowPainter::paintFromPos(pos_type & vpos, bool changed) +void RowPainter::paintChange(double orig_x, Font const & font, + Change const & change) const { - pos_type pos = bidi_.vis2log(vpos); - pos_type start_pos = pos; - // first character - docstring str; - str.reserve(100); - char_type const c = par_.getChar(pos); - str.push_back(c); - - double const orig_x = x_; - - Font const font = text_metrics_.displayFont(pit_, pos); - FontSpan const font_span = par_.fontSpan(pos); - // Track-change status. - Change const & change_running = par_.lookupChange(pos); - - // collect as much similar chars as we can - pos_type const end = row_.endpos(); - for (++vpos ; vpos < end ; ++vpos) { - pos = bidi_.vis2log(vpos); - - if (!font_span.contains(pos)) - break; - - Change const & change = par_.lookupChange(pos); - if (!change_running.isSimilarTo(change)) - // Track change type or author has changed. - break; - - char_type const c = par_.getChar(pos); - - if (c == '\t') - break; - - if (!isPrintableNonspace(c)) - break; - - str.push_back(c); - } - - // Make pos point to the last character in the string. - // Using "pos = bidi_.vis2log(vpos)" does not work for some reason. - if (vpos < end) - pos = bidi_.vis2log(vpos - 1); - - // Now make pos point to the position _after_ the string. - // Using vis2log for that is not a good idea in general, we - // want logical ordering. - if (font.isVisibleRightToLeft()) - --pos; - else - ++pos; - - if (str[0] == '\t') - str.replace(0,1,from_ascii(" ")); - - /* Because we do our own bidi, at this point the strings are - * already in visual order. However, Qt also applies its own - * bidi algorithm to strings that it paints to the screen. - * Therefore, if we were to paint Hebrew/Arabic words as a - * single string, the letters in the words would get reversed - * again. In order to avoid that, we reverse our string. - * See also http://thread.gmane.org/gmane.editors.lyx.devel/79740 - * for an earlier thread on the subject - */ - if (font.isVisibleRightToLeft()) { - reverse(str.begin(), str.end()); - // If the string is reversed, the positions need to be adjusted - ++pos; - ++start_pos; - swap(start_pos, pos); - } - - // Actually paint the text, taking care about the selection - paintStringAndSel(str, font, change_running, start_pos, pos); - - // The line that indicates word in a different language - paintForeignMark(orig_x, font.language()); - - // Paint the spelling mark if needed. - if (lyxrc.spellcheck_continuously && pi_.do_spellcheck - && par_.isMisspelled(start_pos)) { - paintMisspelledMark(orig_x, str, font, start_pos, changed); - } + if (!change.changed()) + return; + // Calculate 1/3 height of font + FontMetrics const & fm = theFontMetrics(font); + int const y_bar = change.deleted() ? yo_ - fm.maxAscent() / 3 + : yo_ + 2 * solid_line_offset_ + solid_line_thickness_; + pi_.pain.line(int(orig_x), y_bar, int(x_), y_bar, + change.color(), Painter::line_solid, solid_line_thickness_); } @@ -535,13 +450,10 @@ void RowPainter::paintLabel() const FontMetrics const & fm = theFontMetrics(font); double x = x_; - if (is_rtl) { - x = width_ - leftMargin() - + fm.width(layout.labelsep); - } else { - x = x_ - fm.width(layout.labelsep) - - fm.width(str); - } + if (is_rtl) + x = width_ - row_.right_margin + fm.width(layout.labelsep); + else + x = x_ - fm.width(layout.labelsep) - fm.width(str); pi_.pain.text(int(x), yo_, str, font); } @@ -575,12 +487,10 @@ void RowPainter::paintTopLevelLabel() const double x = x_; if (layout.labeltype == LABEL_CENTERED) { - if (is_rtl) - x = leftMargin(); - x += (width_ - text_metrics_.rightMargin(pm_) - leftMargin()) / 2; + x = row_.left_margin + (width_ - row_.left_margin - row_.right_margin) / 2; x -= fm.width(str) / 2; } else if (is_rtl) { - x = width_ - leftMargin() - fm.width(str); + x = width_ - row_.right_margin - fm.width(str); } pi_.pain.text(int(x), yo_ - maxdesc - labeladdon, str, font); } @@ -655,7 +565,7 @@ void RowPainter::paintLast() int const y = yo_ - size; int const max_row_width = width_ - size - Inset::TEXT_TO_INSET_OFFSET; int x = is_rtl ? nestMargin() + changebarMargin() - : max_row_width - text_metrics_.rightMargin(pm_); + : max_row_width - row_.right_margin; // If needed, move the box a bit to avoid overlapping with text. int const rem = max_row_width - row_.width(); @@ -679,13 +589,6 @@ void RowPainter::paintLast() } case END_LABEL_NO_LABEL: - if (lyxrc.paragraph_markers && size_type(pit_ + 1) < pars_.size()) { - docstring const s = docstring(1, char_type(0x00B6)); - FontInfo f = FontInfo(text_.layoutFont(pit_)); - f.setColor(Color_paragraphmarker); - pi_.pain.text(int(x_), yo_, s, f); - x_ += theFontMetrics(f).width(s); - } break; } } @@ -693,194 +596,65 @@ void RowPainter::paintLast() void RowPainter::paintOnlyInsets() { - CoordCache const & cache = pi_.base.bv->coordCache(); - pos_type const end = row_.endpos(); - for (pos_type pos = row_.pos(); pos != end; ++pos) { - // If outer row has changed, nested insets are repaint completely. - Inset const * inset = par_.getInset(pos); - bool const nested_inset = inset && - ((inset->asInsetMath() && - !inset->asInsetMath()->asMacroTemplate()) - || inset->asInsetText() - || inset->asInsetTabular()); - if (!nested_inset) - continue; - if (x_ > pi_.base.bv->workWidth() - || !cache.getInsets().has(inset)) - continue; - x_ = cache.getInsets().x(inset); - - bool const pi_selected = pi_.selected; - Cursor const & cur = pi_.base.bv->cursor(); - if (cur.selection() && cur.text() == &text_ - && cur.normalAnchor().text() == &text_) - pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; - paintInset(inset, pos); - pi_.selected = pi_selected; + Row::const_iterator cit = row_.begin(); + Row::const_iterator const & end = row_.end(); + for ( ; cit != end ; ++cit) { + Row::Element const & e = *cit; + if (e.type == Row::INSET) { + // If outer row has changed, nested insets are repainted completely. + pi_.base.bv->coordCache().insets().add(e.inset, int(x_), yo_); + bool const nested_inset = + (e.inset->asInsetMath() && !e.inset->asInsetMath()->asMacroTemplate()) + || e.inset->asInsetText() || e.inset->asInsetTabular(); + if (!nested_inset) { + x_ += e.full_width(); + continue; + } + paintInset(e.inset, e.font, e.change, e.pos); + } else + x_ += e.full_width(); } } void RowPainter::paintText() { - //LYXERR0("-------------------------------------------------------"); - pos_type const end = row_.endpos(); - // Spaces at logical line breaks in bidi text must be skipped during - // painting. However, they may appear visually in the middle - // of a row; they must be skipped, wherever they are... - // * logically "abc_[HEBREW_\nHEBREW]" - // * visually "abc_[_WERBEH\nWERBEH]" - pos_type skipped_sep_vpos = -1; - pos_type body_pos = par_.beginOfBody(); - if (body_pos > 0 && - (body_pos > end || !par_.isLineSeparator(body_pos - 1))) { - body_pos = 0; - } + Row::const_iterator cit = row_.begin(); + Row::const_iterator const & end = row_.end(); + for ( ; cit != end ; ++cit) { + double const orig_x = x_; + Row::Element const & e = *cit; + int foreign_descent = 0; - Layout const & layout = par_.layout(); + switch (e.type) { + case Row::STRING: + case Row::VIRTUAL: + paintStringAndSel(e); - Change change_running; - int change_last_x = 0; - - // check for possible inline completion - DocIterator const & inlineCompletionPos = pi_.base.bv->inlineCompletionPos(); - pos_type inlineCompletionVPos = -1; - if (inlineCompletionPos.inTexted() - && inlineCompletionPos.text() == &text_ - && inlineCompletionPos.pit() == pit_ - && inlineCompletionPos.pos() - 1 >= row_.pos() - && inlineCompletionPos.pos() - 1 < row_.endpos()) { - // draw logically behind the previous character - inlineCompletionVPos = bidi_.log2vis(inlineCompletionPos.pos() - 1); - } - - // Use font span to speed things up, see below - FontSpan font_span; - Font font; - - // If the last logical character is a separator, don't paint it, unless - // it's in the last row of a paragraph; see skipped_sep_vpos declaration - if (end > 0 && end < par_.size() && par_.isSeparator(end - 1)) - skipped_sep_vpos = bidi_.log2vis(end - 1); - - for (pos_type vpos = row_.pos(); vpos < end; ) { - if (x_ > pi_.base.bv->workWidth()) + // Paint the spelling mark if needed. + if (lyxrc.spellcheck_continuously && pi_.do_spellcheck + && par_.isMisspelled(e.pos)) { + paintMisspelledMark(orig_x, e); + } break; - - // Skip the separator at the logical end of the row - if (vpos == skipped_sep_vpos) { - ++vpos; - continue; - } - - pos_type const pos = bidi_.vis2log(vpos); - - if (pos >= par_.size()) { - ++vpos; - continue; - } - - // Use font span to speed things up, see above - if (!font_span.contains(pos)) { - font_span = par_.fontSpan(pos); - font = text_metrics_.displayFont(pit_, pos); - - // split font span if inline completion is inside - if (inlineCompletionVPos != -1 - && font_span.contains(inlineCompletionPos.pos())) - font_span.last = inlineCompletionPos.pos(); - } - - // Note that this value will only be used in - // situations where no ligature of composition of - // characters is needed. (see comments in uses of width_pos). - const int width_pos = pm_.singleWidth(pos, font); - - Change const & change = par_.lookupChange(pos); - if (change.changed() && !change_running.changed()) { - change_running = change; - change_last_x = int(x_); - } - - Inset const * inset = par_.getInset(pos); - bool const highly_editable_inset = inset - && inset->editable(); - - // If we reach the end of a change or if the author changes, paint it. - // We also don't paint across things like tables - if (change_running.changed() && (highly_editable_inset - || !change.changed() || !change_running.isSimilarTo(change))) { - // Calculate 1/3 height of the buffer's default font - FontMetrics const & fm - = theFontMetrics(pi_.base.bv->buffer().params().getFont()); - int const y_bar = change_running.deleted() ? - yo_ - fm.maxAscent() / 3 : yo_ + 2 * solid_line_offset_ + solid_line_thickness_; - pi_.pain.line(change_last_x, y_bar, int(x_), y_bar, - change_running.color(), Painter::line_solid, solid_line_thickness_); - - // Change might continue with a different author or type - if (change.changed() && !highly_editable_inset) { - change_running = change; - change_last_x = int(x_); - } else - change_running.setUnchanged(); - } - - if (body_pos > 0 && pos == body_pos - 1) { - int const lwidth = theFontMetrics(labelFont()) - .width(layout.labelsep); - - // width_pos is either the width of a space or an inset - x_ += row_.label_hfill + lwidth - width_pos; - } - - // Is the inline completion in front of character? - if (font.isRightToLeft() && vpos == inlineCompletionVPos) - paintInlineCompletion(font); - - if (par_.isSeparator(pos)) { - Font const orig_font = text_metrics_.displayFont(pit_, pos); - double const orig_x = x_; - // width_pos is the width of a space - double separator_width = width_pos; - if (pos >= body_pos) - separator_width += row_.separator; - paintSeparator(orig_x, separator_width, orig_font.fontInfo()); - paintForeignMark(orig_x, orig_font.language()); - ++vpos; - - } else if (inset) { + case Row::INSET: { // If outer row has changed, nested insets are repaint completely. - pi_.base.bv->coordCache().insets().add(inset, int(x_), yo_); - - bool const pi_selected = pi_.selected; - Cursor const & cur = pi_.base.bv->cursor(); - if (cur.selection() && cur.text() == &text_ - && cur.normalAnchor().text() == &text_) - pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; - paintInset(inset, pos); - pi_.selected = pi_selected; - ++vpos; - - } else { - // paint as many characters as possible. - paintFromPos(vpos, change_running.changed()); + pi_.base.bv->coordCache().insets().add(e.inset, int(x_), yo_); + paintInset(e.inset, e.font, e.change, e.pos); + foreign_descent = e.dim.descent(); + } + break; + case Row::SPACE: + pi_.pain.textDecoration(e.font.fontInfo(), int(x_), yo_, int(e.full_width())); + x_ += e.full_width(); } - // Is the inline completion after character? - if (!font.isRightToLeft() && vpos - 1 == inlineCompletionVPos) - paintInlineCompletion(font); - } + // The line that indicates word in a different language + paintForeignMark(orig_x, e.font.language(), foreign_descent); - // if we reach the end of a struck out range, paint it - if (change_running.changed()) { - FontMetrics const & fm - = theFontMetrics(pi_.base.bv->buffer().params().getFont()); - int const y_bar = change_running.deleted() ? - yo_ - fm.maxAscent() / 3 : yo_ + 2 * solid_line_offset_ + solid_line_thickness_; - pi_.pain.line(change_last_x, y_bar, int(x_), y_bar, - change_running.color(), Painter::line_solid, solid_line_thickness_); - change_running.setUnchanged(); + // change tracking (not for insets that track their own changes) + if (e.type != Row::INSET || ! e.inset->canTrackChanges()) + paintChange(orig_x, e.font, e.change); } } @@ -983,38 +757,4 @@ void RowPainter::paintSelection() const } -void RowPainter::paintInlineCompletion(Font const & font) -{ - docstring completion = pi_.base.bv->inlineCompletion(); - FontInfo f = font.fontInfo(); - bool rtl = font.isRightToLeft(); - - // draw the unique and the non-unique completion part - // Note: this is not time-critical as it is - // only done once per screen. - size_t uniqueTo = pi_.base.bv->inlineCompletionUniqueChars(); - docstring s1 = completion.substr(0, uniqueTo); - docstring s2 = completion.substr(uniqueTo); - ColorCode c1 = Color_inlinecompletion; - ColorCode c2 = Color_nonunique_inlinecompletion; - - // right to left? - if (rtl) { - swap(s1, s2); - swap(c1, c2); - } - - if (!s1.empty()) { - f.setColor(c1); - pi_.pain.text(int(x_), yo_, s1, f); - x_ += theFontMetrics(font).width(s1); - } - - if (!s2.empty()) { - f.setColor(c2); - pi_.pain.text(int(x_), yo_, s2, f); - x_ += theFontMetrics(font).width(s2); - } -} - } // namespace lyx diff --git a/src/RowPainter.h b/src/RowPainter.h index 95c64269e5..aa48f46672 100644 --- a/src/RowPainter.h +++ b/src/RowPainter.h @@ -14,8 +14,8 @@ #ifndef ROWPAINTER_H #define ROWPAINTER_H -#include "Bidi.h" #include "Changes.h" +#include "Row.h" #include "support/types.h" @@ -30,27 +30,11 @@ class PainterInfo; class Paragraph; class ParagraphList; class ParagraphMetrics; -class Row; class Text; class TextMetrics; namespace frontend { class Painter; } -/** - * FIXME: Re-implement row painting using row elements. - * - * This is not difficult in principle, but the code is intricate and - * needs some careful analysis. The first thing that needs to be done - * is to break row elements with the same criteria. Currently breakRow - * does not consider on-the-fly spell-checking, but it is not clear to - * me that it is required. Moreover, this thing would only work if we - * are sure that the Row object is up-to-date when drawing happens. - * This depends on the update machinery. - * - * This would allow to get rid of the Bidi class. - */ - - /** * A class used for painting an individual row of text. * FIXME: get rid of that class. @@ -74,21 +58,14 @@ public: void paintSelection() const; private: - void paintSeparator(double orig_x, double width, FontInfo const & font); + 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 paintFromPos(pos_type & vpos, bool changed); - void paintInset(Inset const * inset, pos_type const pos); - void paintInlineCompletion(Font const & font); - - /// return left margin - int leftMargin() const; + void paintInset(Inset const * inset, Font const & font, + Change const & change, pos_type const pos); /// return the label font for this row FontInfo labelFont() const; @@ -115,9 +92,6 @@ private: Paragraph const & par_; ParagraphMetrics const & pm_; - /// bidi cache - Bidi bidi_; - /// row changed? (change tracking) Change const change_; diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index 07064b6ca0..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) { @@ -569,12 +547,6 @@ void TextMetrics::computeRowMetrics(pit_type const pit, // FIXME: put back this assertion when the crash on new doc is solved. //LASSERT(w >= 0, /**/); - bool const is_rtl = text_->isRTL(par); - if (is_rtl) - row.left_margin = rightMargin(pit); - else - row.left_margin = leftMargin(max_width_, pit, row.pos()); - // is there a manual margin with a manual label Layout const & layout = par.layout(); @@ -609,15 +581,15 @@ 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 (is_rtl) { + } else if (text_->isRTL(par)) { row.dimension().wid = width; row.left_margin += w; } @@ -785,13 +757,19 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit Paragraph const & par = text_->getPar(pit); pos_type const end = par.size(); pos_type const pos = row.pos(); - int const width = max_width_ - right_margin; pos_type const body_pos = par.beginOfBody(); + bool const is_rtl = text_->isRTL(par); + row.clear(); - // This make get changed in computeRowMetrics depending on RTL row.left_margin = leftMargin(max_width_, pit, pos); - row.dimension().wid = row.left_margin; row.right_margin = right_margin; + if (is_rtl) + swap(row.left_margin, row.right_margin); + // Remember that the row width takes into account the left_margin + // but not the right_margin. + row.dimension().wid = row.left_margin; + // the width available for the row. + int const width = max_width_ - row.right_margin; if (pos >= end || row.width() > width) { row.endpos(end); @@ -819,7 +797,7 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit // or the end of the par, then build a representation of the row. pos_type i = pos; FontIterator fi = FontIterator(*this, par, pit, pos); - while (i < end && row.width() < width) { + while (i < end && row.width() <= width) { char_type c = par.getChar(i); // The most special cases are handled first. if (par.isInset(i)) { @@ -837,13 +815,6 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit int const add = max(fm.width(par.layout().labelsep), labelEnd(pit) - row.width()); row.addSpace(i, add, *fi, par.lookupChange(i)); - } else if (par.isLineSeparator(i)) { - // In theory, no inset has this property. If - // this is done, a new addSeparator which - // takes an inset as parameter should be - // added. - LATTEST(!par.isInset(i)); - row.addSeparator(i, c, *fi, par.lookupChange(i)); } else if (c == '\t') row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")), *fi, par.lookupChange(i)); @@ -904,7 +875,8 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit row.shortenIfNeeded(body_pos, width); // make sure that the RTL elements are in reverse ordering - row.reverseRTL(text_->isRTL(par)); + row.reverseRTL(is_rtl); + //LYXERR0("breakrow: row is " << row); } @@ -1090,6 +1062,7 @@ void TextMetrics::setRowHeight(Row & row, pit_type const pit, pos_type TextMetrics::getPosNearX(Row const & row, int & x, bool & boundary) const { + //LYXERR0("getPosNearX(" << x << ") row=" << row); /// 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. @@ -1145,6 +1118,7 @@ pos_type TextMetrics::getPosNearX(Row const & row, int & x, boundary = true; x += xo; + //LYXERR0("getPosNearX ==> pos=" << pos << ", boundary=" << boundary); return pos; } diff --git a/src/frontends/FontMetrics.h b/src/frontends/FontMetrics.h index 53f57a2880..1c1a658f8a 100644 --- a/src/frontends/FontMetrics.h +++ b/src/frontends/FontMetrics.h @@ -84,15 +84,27 @@ 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 + * \param rtl is true for right-to-left layout + * \param force is false for breaking at word separator, true for + * arbitrary position. + */ + virtual bool breakAt(docstring & s, int & x, bool rtl, bool force) const = 0; /// return char dimension for the font. virtual Dimension const dimension(char_type c) const = 0; /** 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 b488b684eb..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)); @@ -183,6 +188,31 @@ int GuiFontMetrics::x2pos(docstring const & s, int & x, bool const rtl) const } +bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const force) const +{ + if (s.empty()) + return false; + QTextLayout tl; + tl.setText(toqstr(s)); + tl.setFont(font_); + // Note that both setFlags and the enums are undocumented + tl.setFlags(rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight); + QTextOption to; + to.setWrapMode(force ? QTextOption::WrapAnywhere : QTextOption::WordWrap); + tl.setTextOption(to); + tl.beginLayout(); + QTextLine line = tl.createLine(); + line.setLineWidth(x); + tl.createLine(); + tl.endLayout(); + if (int(line.naturalTextWidth()) > x) + return false; + x = int(line.naturalTextWidth()); + s = s.substr(0, line.textLength()); + return true; +} + + void GuiFontMetrics::rectText(docstring const & str, int & w, int & ascent, int & descent) const { diff --git a/src/frontends/qt4/GuiFontMetrics.h b/src/frontends/qt4/GuiFontMetrics.h index 7555929c9b..a382253281 100644 --- a/src/frontends/qt4/GuiFontMetrics.h +++ b/src/frontends/qt4/GuiFontMetrics.h @@ -43,8 +43,9 @@ 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; virtual void rectText(docstring const & str, 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);