X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FTextMetrics.cpp;h=3dd052059b44e39d86c07b5eaba743f2631ed59e;hb=2a0e4c199c4f18d80ec5a2ab452f3cf18eafc56c;hp=66e100b2d90202684ed5317f2cdd3eaaf24be116;hpb=508518ad95a7fd7581bd4affd389dbe7bd6d09f1;p=lyx.git diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index 66e100b2d9..3dd052059b 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -48,6 +48,7 @@ #include "support/debug.h" #include "support/lassert.h" +#include #include using namespace std; @@ -401,6 +402,7 @@ bool TextMetrics::redoParagraph(pit_type const pit) } // redo insets + par.setBeginOfBody(); Font const bufferfont = buffer.params().getFont(); CoordCache::Insets & insetCache = bv_->coordCache().insets(); InsetList::const_iterator ii = par.insetList().begin(); @@ -434,7 +436,6 @@ bool TextMetrics::redoParagraph(pit_type const pit) } } - par.setBeginOfBody(); pos_type first = 0; size_t row_index = 0; bool need_new_row = false; @@ -481,6 +482,11 @@ bool TextMetrics::redoParagraph(pit_type const pit) if (text_->isMainText()) { if (pit == 0) { pm.rows().front().dimension().asc += 20; + /* coverity thinks that we should update pm.dim().asc + * below, but all the rows heights are actually counted as + * part of the paragraph metric descent see loop above). + */ + // coverity[copy_paste_error] pm.dim().des += 20; } ParagraphList const & pars = text_->paragraphs(); @@ -508,13 +514,7 @@ bool TextMetrics::redoParagraph(pit_type const pit) LyXAlignment TextMetrics::getAlign(Paragraph const & par, Row const & row) const { - Layout const & layout = par.layout(); - - LyXAlignment align; - if (par.params().align() == LYX_ALIGN_LAYOUT) - align = layout.align; - else - align = par.params().align(); + LyXAlignment align = par.getAlign(); // handle alignment inside tabular cells Inset const & owner = text_->inset(); @@ -561,10 +561,9 @@ LyXAlignment TextMetrics::getAlign(Paragraph const & par, Row const & row) const // not justify stuff, then don't stretch. // A forced block alignment can only be overridden the 'no // justification on screen' setting. - if (((row.right_boundary() || row.endpos() == par.size()) - && !forced_block) + if ((row.flushed() && !forced_block) || !bv_->buffer().params().justification) - align = text_->isRTL(par) ? LYX_ALIGN_RIGHT : LYX_ALIGN_LEFT; + align = row.isRTL() ? LYX_ALIGN_RIGHT : LYX_ALIGN_LEFT; } return align; @@ -619,18 +618,14 @@ void TextMetrics::computeRowMetrics(Row & row, int width) const // Common case : there is no hfill, and the alignment will be // meaningful switch (getAlign(par, row)) { - case LYX_ALIGN_BLOCK: { - int const ns = row.countSeparators(); - // If we have separators, then stretch the row - if (ns) { - row.setSeparatorExtraWidth(double(w) / ns); - row.dimension().wid += w; - } else if (text_->isRTL(par)) { + case LYX_ALIGN_BLOCK: + // Expand expanding characters by a total of w + if (!row.setExtraWidth(w) && row.isRTL()) { + // Justification failed and the text is RTL: align to the right row.left_margin += w; row.dimension().wid += w; } break; - } case LYX_ALIGN_RIGHT: row.left_margin += w; row.dimension().wid += w; @@ -649,6 +644,7 @@ void TextMetrics::computeRowMetrics(Row & row, int width) const return; } + // Case nh > 0. There are hfill separators. hfill = w / nh; hfill_rem = w % nh; row.dimension().wid += w; @@ -861,7 +857,23 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const } else if (c == '\t') row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")), *fi, par.lookupChange(i)); - else { + else if (c == 0x2028 || c == 0x2029) { + /** + * U+2028 LINE SEPARATOR + * U+2029 PARAGRAPH SEPARATOR + + * These are special unicode characters that break + * lines/pragraphs. Not handling them lead to trouble wrt + * Qt QTextLayout formatting. We add a visible character + * on screen so that the user can see that something is + * happening. + */ + row.finalizeLast(); + // ⤶ U+2936 ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS + // ¶ U+00B6 PILCROW SIGN + char_type const screen_char = (c == 0x2028) ? 0x2936 : 0x00B6; + row.add(i, screen_char, *fi, par.lookupChange(i)); + } else { // FIXME: please someone fix the Hebrew/Arabic parenthesis mess! // see also Paragraph::getUChar. if (fi->language()->lang() == "hebrew") { @@ -898,7 +910,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const && inset->display()) || (!row.empty() && row.back().inset && row.back().inset->display())) { - row.right_boundary(true); + row.flushed(true); need_new_row = par.isNewline(i); ++i; break; @@ -922,13 +934,21 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const BufferParams const & bparams = text_->inset().buffer().params(); f.setLanguage(par.getParLanguage(bparams)); + // ¶ U+00B6 PILCROW SIGN row.addVirtual(end, docstring(1, char_type(0x00B6)), f, Change()); } // if the row is too large, try to cut at last separator. In case // of success, reset indication that the row was broken abruptly. - if (row.shortenIfNeeded(body_pos, width)) - row.right_boundary(false); + int const next_width = max_width_ - leftMargin(max_width_, row.pit(), row.endpos()) + - rightMargin(row.pit()); + + row.shortenIfNeeded(body_pos, width, next_width); + row.right_boundary(!row.empty() && row.endpos() < end + && row.back().endpos == row.endpos()); + // Last row in paragraph is flushed + if (row.endpos() == end) + row.flushed(true); // make sure that the RTL elements are in reverse ordering row.reverseRTL(is_rtl); @@ -1074,7 +1094,7 @@ void TextMetrics::setRowHeight(Row & row) const // returns the column near the specified x-coordinate of the row // x is set to the real beginning of this column pos_type TextMetrics::getPosNearX(Row const & row, int & x, - bool & boundary) const + bool & boundary, bool const select) const { //LYXERR0("getPosNearX(" << x << ") row=" << row); /// For the main Text, it is possible that this pit is not @@ -1104,7 +1124,7 @@ pos_type TextMetrics::getPosNearX(Row const & row, int & x, for ( ; cit != cend; ++cit) { if (w <= x && w + cit->full_width() > x) { int x_offset = int(x - w); - pos = cit->x2pos(x_offset); + pos = cit->x2pos(x_offset, select); x = int(x_offset + w); break; } @@ -1341,18 +1361,23 @@ Inset * TextMetrics::editXY(Cursor & cur, int x, int y, if (edited == inset && cur.pos() == it->pos) { // non-editable inset, set cursor after the inset if x is // nearer to that position (bug 9628) + // TODO: This should be replaced with an improvement of + // Cursor::moveToClosestEdge that handles rtl text. (Which could not + // be tested because of #10569.) CoordCache::Insets const & insetCache = bv_->coordCache().getInsets(); - Dimension const & dim = insetCache.dim(inset); - Point p = insetCache.xy(inset); - bool const is_rtl = text_->isRTL(text_->getPar(pit)); - if (is_rtl) { - // "in front of" == "right of" - if (abs(p.x_ - x) < abs(p.x_ + dim.wid - x)) - cur.posForward(); - } else { - // "in front of" == "left of" - if (abs(p.x_ + dim.wid - x) < abs(p.x_ - x)) - cur.posForward(); + if (insetCache.has(inset)) { + Dimension const & dim = insetCache.dim(inset); + Point p = insetCache.xy(inset); + bool const is_rtl = text_->isRTL(text_->getPar(pit)); + if (is_rtl) { + // "in front of" == "right of" + if (abs(p.x_ - x) < abs(p.x_ + dim.wid - x)) + cur.posForward(); + } else { + // "in front of" == "left of" + if (abs(p.x_ + dim.wid - x) < abs(p.x_ - x)) + cur.posForward(); + } } } @@ -1362,7 +1387,8 @@ Inset * TextMetrics::editXY(Cursor & cur, int x, int y, } -void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, int const y) +void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, + int const y, bool const select) { LASSERT(text_ == cur.text(), return); pit_type const pit = getPitNearY(y); @@ -1389,7 +1415,7 @@ void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, int const bool bound = false; int xx = x; - pos_type const pos = getPosNearX(row, xx, bound); + pos_type const pos = getPosNearX(row, xx, bound, select); LYXERR(Debug::DEBUG, "setting cursor pit: " << pit << " pos: " << pos); @@ -1407,28 +1433,12 @@ InsetList::InsetTable * TextMetrics::checkInsetHit(pit_type pit, int x, int y) LYXERR(Debug::DEBUG, "x: " << x << " y: " << y << " pit: " << pit); - InsetList::const_iterator iit = par.insetList().begin(); - InsetList::const_iterator iend = par.insetList().end(); - for (; iit != iend; ++iit) { - Inset * inset = iit->inset; - - LYXERR(Debug::DEBUG, "examining inset " << inset); + for (auto const & it : par.insetList()) { + LYXERR(Debug::DEBUG, "examining inset " << it.inset); - if (!insetCache.has(inset)) { - LYXERR(Debug::DEBUG, "inset has no cached position"); - return 0; - } - - Dimension const & dim = insetCache.dim(inset); - Point p = insetCache.xy(inset); - - LYXERR(Debug::DEBUG, "xo: " << p.x_ << "..." << p.x_ + dim.wid - << " yo: " << p.y_ - dim.asc << "..." << p.y_ + dim.des); - - if (x >= p.x_ && x <= p.x_ + dim.wid - && y >= p.y_ - dim.asc && y <= p.y_ + dim.des) { - LYXERR(Debug::DEBUG, "Hit inset: " << inset); - return const_cast(&(*iit)); + if (insetCache.covers(it.inset, x, y)) { + LYXERR(Debug::DEBUG, "Hit inset: " << it.inset); + return const_cast(&it); } } @@ -1451,56 +1461,6 @@ Inset * TextMetrics::checkInsetHit(int x, int y) } -Row::const_iterator const -TextMetrics::findRowElement(Row const & row, pos_type const pos, - bool const boundary, double & x) const -{ - /** - * When boundary is true, position i is in the row element (pos, endpos) - * if - * pos < i <= endpos - * whereas, when boundary is false, the test is - * pos <= i < endpos - * The correction below allows to handle both cases. - */ - int const boundary_corr = (boundary && pos) ? -1 : 0; - - x = row.left_margin; - - /** Early return in trivial cases - * 1) the row is empty - * 2) the position is the left-most position of the row; there - * is a quirk here however: if the first element is virtual - * (end-of-par marker for example), then we have to look - * closer - */ - if (row.empty() - || (pos == row.begin()->left_pos() && !boundary - && !row.begin()->isVirtual())) - return row.begin(); - - Row::const_iterator cit = row.begin(); - for ( ; cit != row.end() ; ++cit) { - /** Look whether the cursor is inside the element's - * span. Note that it is necessary to take the - * boundary into account, and to accept virtual - * elements, which have pos == endpos. - */ - if (pos + boundary_corr >= cit->pos - && (pos + boundary_corr < cit->endpos || cit->isVirtual())) { - x += cit->pos2x(pos); - break; - } - x += cit->full_width(); - } - - if (cit == row.end()) - --cit; - - return cit; -} - - int TextMetrics::cursorX(CursorSlice const & sl, bool boundary) const { @@ -1513,7 +1473,7 @@ int TextMetrics::cursorX(CursorSlice const & sl, pos_type const pos = sl.pos(); double x = 0; - findRowElement(row, pos, boundary, x); + row.findElement(pos, boundary, x); return int(x); } @@ -1768,12 +1728,7 @@ int TextMetrics::leftMargin(int max_width, if (!par.params().leftIndent().zero()) l_margin += par.params().leftIndent().inPixels(max_width, lfm.em()); - LyXAlignment align; - - if (par.params().align() == LYX_ALIGN_LAYOUT) - align = layout.align; - else - align = par.params().align(); + LyXAlignment align = par.getAlign(); // set the correct parindent if (pos == 0 @@ -1973,15 +1928,14 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const rp.paintAppendix(); rp.paintDepthBar(); rp.paintChangeBar(); - bool const is_rtl = text_->isRTL(text_->getPar(pit)); - if (i == 0 && !is_rtl) + if (i == 0 && !row.isRTL()) rp.paintFirst(); - if (i == nrows - 1 && is_rtl) + if (i == nrows - 1 && row.isRTL()) rp.paintLast(); rp.paintText(); - if (i == nrows - 1 && !is_rtl) + if (i == nrows - 1 && !row.isRTL()) rp.paintLast(); - if (i == 0 && is_rtl) + if (i == 0 && row.isRTL()) rp.paintFirst(); rp.paintTooLargeMarks(row_x + row.left_x() < 0, row_x + row.right_x() > bv_->workWidth());