X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FTextMetrics.cpp;h=f4241181c295e15f440d78a48f05dbf6d7621d5f;hb=4ed0312c51704780af1c452d3a82a84171b3725a;hp=eb339442b95d7c270272a58c28d20239efb9ee64;hpb=08a3405181aed8b4c3d60af8cc3ac785b7e189cd;p=lyx.git diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index eb339442b9..f4241181c2 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -35,6 +35,7 @@ #include "TextClass.h" #include "VSpace.h" +#include "insets/InsetSeparator.h" #include "insets/InsetText.h" #include "mathed/MacroTable.h" @@ -106,14 +107,9 @@ int numberOfHfills(Row const & row, ParagraphMetrics const & pm, TextMetrics::TextMetrics(BufferView * bv, Text * text) - : bv_(bv), text_(text) -{ - LBUFERR(bv_); - max_width_ = bv_->workWidth(); - dim_.wid = max_width_; - dim_.asc = 10; - dim_.des = 10; -} + : bv_(bv), text_(text), dim_(bv_->workWidth(), 10, 10), + max_width_(dim_.wid), tight_(false) +{} bool TextMetrics::contains(pit_type pit) const @@ -216,18 +212,18 @@ void TextMetrics::newParMetricsUp() } -bool TextMetrics::metrics(MetricsInfo const & mi, Dimension & dim, int min_width, - bool const expand_on_multipars) +bool TextMetrics::metrics(MetricsInfo const & mi, Dimension & dim, int min_width) { LBUFERR(mi.base.textwidth > 0); max_width_ = mi.base.textwidth; + tight_ = mi.tight_insets; // backup old dimension. Dimension const old_dim = dim_; // reset dimension. dim_ = Dimension(); dim_.wid = min_width; pit_type const npar = text_->paragraphs().size(); - if (npar > 1 && expand_on_multipars) + if (npar > 1 && !tight_) // If there is more than one row, expand the text to // the full allowable width. dim_.wid = max_width_; @@ -439,7 +435,7 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows) // environment? I think the answer is: when paragraphs are merged; // when layout is set; when material is pasted. if (par.brokenBiblio()) { - Cursor & cur = const_cast(bv_->cursor()); + Cursor & cur = bv_->cursor(); // In some cases, we do not know how to record undo if (&cur.inset() == &text_->inset()) cur.recordUndo(pit, pit); @@ -496,10 +492,11 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows) // If there is an end of paragraph marker, its size should be // substracted to the available width. The logic here is - // almost the same as in breakRow, remember keep them in sync. + // almost the same as in tokenizeParagraph, remember keep them in sync. int eop = 0; - if (lyxrc.paragraph_markers && e.pos + 1 == par.size() - && size_type(pit + 1) < text_->paragraphs().size()) { + if (e.pos + 1 == par.size() + && (lyxrc.paragraph_markers || par.lookupChange(par.size()).changed()) + && size_type(pit + 1) < text_->paragraphs().size()) { Font f(text_->layoutFont(pit)); // ¶ U+00B6 PILCROW SIGN eop = theFontMetrics(f).width(char_type(0x00B6)); @@ -512,7 +509,7 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows) Font const & font = e.inset->inheritFont() ? displayFont(pit, e.pos) : bufferfont; MacroContext mc(&buffer, parPos); - MetricsInfo mi(bv_, font.fontInfo(), w, mc, e.pos == 0); + MetricsInfo mi(bv_, font.fontInfo(), w, mc, e.pos == 0, tight_); e.inset->metrics(mi, dim); if (!insetCache.has(e.inset) || insetCache.dim(e.inset) != dim) { insetCache.add(e.inset, dim); @@ -520,43 +517,28 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows) } } - pos_type first = 0; - size_t row_index = 0; - bool need_new_row = false; - // maximum pixel width of a row - do { - if (row_index == pm.rows().size()) - pm.rows().push_back(Row()); - else - pm.rows()[row_index] = Row(); - Row & row = pm.rows()[row_index]; - row.pit(pit); - row.pos(first); - need_new_row = breakRow(row, right_margin); + // Transform the paragraph into a single row containing all the elements. + Row const bigrow = tokenizeParagraph(pit); + // Split the row in several rows fitting in available width + pm.rows() = breakParagraph(bigrow); + + /* If there is more than one row, expand the text to the full + * allowable width. This setting here is needed for the + * setRowAlignment() below. We do nothing when tight insets are + * requested. + */ + if (pm.rows().size() > 1 && !tight_ && dim_.wid < max_width_) + dim_.wid = max_width_; + + // Compute height and alignment of the rows. + for (Row & row : pm.rows()) { setRowHeight(row); - row.changed(true); - if ((row_index || row.endpos() < par.size() || row.right_boundary()) - && par.inInset().lyxCode() != CELL_CODE) { - /* If there is more than one row or the row has been - * broken by a display inset or a newline, expand the text - * to the full allowable width. This setting here is - * needed for the setRowAlignment() below. - * We do nothing when inside a table cell. - */ - if (dim_.wid < max_width_) - dim_.wid = max_width_; - } if (align_rows) setRowAlignment(row, max(dim_.wid, row.width())); - first = row.endpos(); - ++row_index; pm.dim().wid = max(pm.dim().wid, row.width() + row.right_margin); pm.dim().des += row.height(); - } while (first < par.size() || need_new_row); - - if (row_index < pm.rows().size()) - pm.rows().resize(row_index); + } // This type of margin can only be handled at the global paragraph level if (par.layout().margintype == MARGIN_RIGHT_ADDRESS_BOX) { @@ -582,27 +564,29 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows) } // The space above and below the paragraph. - int const top = parTopSpacing(pit); - pm.rows().front().dim().asc += top; - int const bottom = parBottomSpacing(pit); - pm.rows().back().dim().des += bottom; - pm.dim().des += top + bottom; - - pm.dim().asc += pm.rows()[0].ascent(); - pm.dim().des -= pm.rows()[0].ascent(); + int top = parTopSpacing(pit); + int bottom = parBottomSpacing(pit); // Top and bottom margin of the document (only at top-level) // FIXME: It might be better to move this in another method // specially tailored for the main text. if (text_->isMainText()) { if (pit == 0) - pm.dim().asc += bv_->topMargin(); - ParagraphList const & pars = text_->paragraphs(); - if (pit + 1 == pit_type(pars.size())) { - pm.dim().des += bv_->bottomMargin(); + top += bv_->topMargin(); + if (pit + 1 == pit_type(text_->paragraphs().size())) { + bottom += bv_->bottomMargin(); } } + // Add the top/bottom space to rows and paragraph metrics + pm.rows().front().dim().asc += top; + pm.rows().back().dim().des += bottom; + pm.dim().des += top + bottom; + + // Move the pm ascent to be the same as the first row ascent + pm.dim().asc += pm.rows().front().ascent(); + pm.dim().des -= pm.rows().front().ascent(); + changed |= old_dim.height() != pm.dim().height(); return changed; @@ -636,10 +620,10 @@ LyXAlignment TextMetrics::getAlign(Paragraph const & par, Row const & row) const // Display-style insets should always be on a centered row if (Inset const * inset = par.getInset(row.pos())) { - if (inset->rowFlags() & Inset::Display) { - if (inset->rowFlags() & Inset::AlignLeft) - align = LYX_ALIGN_BLOCK; - else if (inset->rowFlags() & Inset::AlignRight) + if (inset->rowFlags() & Display) { + if (inset->rowFlags() & AlignLeft) + align = LYX_ALIGN_LEFT; + else if (inset->rowFlags() & AlignRight) align = LYX_ALIGN_RIGHT; else align = LYX_ALIGN_CENTER; @@ -702,7 +686,7 @@ void TextMetrics::setRowAlignment(Row & row, int width) const // We don't have to look at the alignment if the row is already // larger then the permitted width as then we force the // LEFT_ALIGN'edness! - if (int(row.width()) >= max_width_) + if (row.width() >= max_width_) return; if (nh == 0) { @@ -803,15 +787,6 @@ int TextMetrics::labelFill(Row const & row) const } -int TextMetrics::labelEnd(pit_type const pit) const -{ - // labelEnd is only needed if the layout fills a flushleft label. - if (text_->getPar(pit).layout().margintype != MARGIN_MANUAL) - return 0; - // return the beginning of the body - return leftMargin(pit); -} - namespace { /** @@ -870,59 +845,45 @@ private: } // namespace -/** This is the function where the hard work is done. The code here is - * very sensitive to small changes :) Note that part of the - * intelligence is also in Row::shortenIfNeeded. - */ -bool TextMetrics::breakRow(Row & row, int const right_margin) const + +Row TextMetrics::tokenizeParagraph(pit_type const pit) const { - LATTEST(row.empty()); - Paragraph const & par = text_->getPar(row.pit()); + Row row; + row.pit(pit); + Paragraph const & par = text_->getPar(pit); Buffer const & buf = text_->inset().buffer(); BookmarksSection::BookmarkPosList bpl = theSession().bookmarks().bookmarksInPar(buf.fileName(), par.id()); pos_type const end = par.size(); - pos_type const pos = row.pos(); pos_type const body_pos = par.beginOfBody(); - bool const is_rtl = text_->isRTL(row.pit()); - bool need_new_row = false; - - row.left_margin = leftMargin(row.pit(), pos); - 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.dim().wid = row.left_margin; - // the width available for the row. - int const width = max_width_ - row.right_margin; // check for possible inline completion DocIterator const & ic_it = bv_->inlineCompletionPos(); pos_type ic_pos = -1; - if (ic_it.inTexted() && ic_it.text() == text_ && ic_it.pit() == row.pit()) + if (ic_it.inTexted() && ic_it.text() == text_ && ic_it.pit() == pit) ic_pos = ic_it.pos(); // Now we iterate through until we reach the right margin // or the end of the par, then build a representation of the row. - pos_type i = pos; - FontIterator fi = FontIterator(*this, par, row.pit(), pos); + pos_type i = 0; + FontIterator fi = FontIterator(*this, par, pit, 0); // The real stopping condition is a few lines below. while (true) { // Firstly, check whether there is a bookmark here. - for (auto const & bp_p : bpl) - if (bp_p.second == i) { - Font f = *fi; - f.fontInfo().setColor(Color_bookmark); - // ❶ U+2776 DINGBAT NEGATIVE CIRCLED DIGIT ONE - char_type const ch = 0x2775 + bp_p.first; - row.addVirtual(i, docstring(1, ch), f, Change()); - } + if (lyxrc.bookmarks_visibility == LyXRC::BMK_INLINE) + for (auto const & bp_p : bpl) + if (bp_p.second == i) { + Font f = *fi; + f.fontInfo().setColor(Color_bookmark); + // ❶ U+2776 DINGBAT NEGATIVE CIRCLED DIGIT ONE + char_type const ch = 0x2775 + bp_p.first; + row.addVirtual(i, docstring(1, ch), f, Change()); + } // The stopping condition is here so that the display of a // bookmark can take place at paragraph start too. - if (i >= end || (i != pos && row.width() > width)) + if (i >= end) break; char_type c = par.getChar(i); @@ -932,26 +893,22 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const Dimension dim = bv_->coordCache().insets().dim(ins); row.add(i, ins, dim, *fi, par.lookupChange(i)); } else if (c == ' ' && i + 1 == body_pos) { - // There is a space at i, but it should not be - // added as a separator, because it is just - // before body_pos. Instead, insert some spacing to - // align text + // This space is an \item separator. Represent it with a + // special space element, which dimension will be computed + // in breakRow. FontMetrics const & fm = theFontMetrics(text_->labelFont(par)); - // this is needed to make sure that the row width is correct - row.finalizeLast(); - int const add = max(fm.width(par.layout().labelsep), - labelEnd(row.pit()) - row.width()); - row.addSpace(i, add, *fi, par.lookupChange(i)); + int const wid = fm.width(par.layout().labelsep); + row.addMarginSpace(i, wid, *fi, par.lookupChange(i)); } else if (c == '\t') row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")), - *fi, par.lookupChange(i)); + *fi, par.lookupChange(i)); 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 + * lines/pragraphs. Not handling them leads to trouble wrt * Qt QTextLayout formatting. We add a visible character * on screen so that the user can see that something is * happening. @@ -962,6 +919,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const char_type const screen_char = (c == 0x2028) ? 0x2936 : 0x00B6; row.add(i, screen_char, *fi, par.lookupChange(i)); } else + // row elements before body are unbreakable row.add(i, c, *fi, par.lookupChange(i)); // add inline completion width @@ -979,74 +937,225 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const row.addVirtual(i + 1, comp.substr(uniqueTo), f, Change()); } - // Handle some situations that abruptly terminate the row - // - Before an inset with BreakBefore - // - After an inset with BreakAfter - Inset const * prevInset = !row.empty() ? row.back().inset : 0; - Inset const * nextInset = (i + 1 < end) ? par.getInset(i + 1) : 0; - if ((nextInset && nextInset->rowFlags() & Inset::BreakBefore) - || (prevInset && prevInset->rowFlags() & Inset::BreakAfter)) { - row.flushed(true); - // Force a row creation after this one if it is ended by - // an inset that either - // - has row flag RowAfter that enforces that; - // - or (1) did force the row breaking, (2) is at end of - // paragraph and (3) the said paragraph has an end label. - need_new_row = prevInset && - (prevInset->rowFlags() & Inset::RowAfter - || (prevInset->rowFlags() & Inset::BreakAfter && i + 1 == end - && text_->getEndLabel(row.pit()) != END_LABEL_NO_LABEL)); - ++i; - break; - } - ++i; ++fi; } row.finalizeLast(); - row.endpos(i); + row.endpos(end); - // End of paragraph marker. The logic here is almost the + // End of paragraph marker, either if LyXRc requires it, or there + // is an end of paragraph change. The logic here is almost the // same as in redoParagraph, remember keep them in sync. ParagraphList const & pars = text_->paragraphs(); - Change const & change = par.lookupChange(i); - if ((lyxrc.paragraph_markers || change.changed()) - && !need_new_row - && i == end && size_type(row.pit() + 1) < pars.size()) { + Change const & endchange = par.lookupChange(end); + if (endchange.changed()) + row.needsChangeBar(true); + if ((lyxrc.paragraph_markers || endchange.changed()) + && size_type(pit + 1) < pars.size()) { // add a virtual element for the end-of-paragraph // marker; it is shown on screen, but does not exist // in the paragraph. - Font f(text_->layoutFont(row.pit())); + Font f(text_->layoutFont(pit)); f.fontInfo().setColor(Color_paragraphmarker); f.setLanguage(par.getParLanguage(buf.params())); // ¶ U+00B6 PILCROW SIGN - row.addVirtual(end, docstring(1, char_type(0x00B6)), f, change); + row.addVirtual(end, docstring(1, char_type(0x00B6)), f, endchange); } - // Is there a end-of-paragaph change? - if (i == end && par.lookupChange(end).changed() && !need_new_row) - row.needsChangeBar(true); + return row; +} - // if the row is too large, try to cut at last separator. In case - // of success, reset indication that the row was broken abruptly. - int const next_width = max_width_ - leftMargin(row.pit(), row.endpos()) - - rightMargin(row.pit()); - if (row.shortenIfNeeded(body_pos, width, next_width)) - row.flushed(false); - 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); +namespace { + +/** Helper template flexible_const_iterator + * A way to iterate over a const container, but insert fake elements in it. + * In the case of a row, we will have to break some elements, which + * create new ones. This class allows to abstract this. + * Only the required parts are implemented for now. + */ +template +class flexible_const_iterator { + typedef typename T::value_type value_type; +public: + + // + flexible_const_iterator & operator++() { + if (pile_.empty()) + ++cit_; + else + pile_.pop_back(); + return *this; + } + + value_type operator*() const { return pile_.empty() ? *cit_ : pile_.back(); } + + value_type const * operator->() const { return pile_.empty() ? &*cit_ : &pile_.back(); } + + void put(value_type const & e) { pile_.push_back(e); } + + // Put a sequence of elements on the pile (in reverse order!) + void put(vector const & elts) { + pile_.insert(pile_.end(), elts.rbegin(), elts.rend()); + } + +// This should be private, but declaring the friend functions is too much work +//private: + typename T::const_iterator cit_; + // A vector that is used as like a pile to store the elements to + // consider before incrementing the underlying iterator. + vector pile_; +}; + + +template +flexible_const_iterator flexible_begin(T const & t) +{ + return { t.begin(), vector() }; +} + +template +flexible_const_iterator flexible_end(T const & t) +{ + return { t.end(), vector() }; +} + + +// Equality is only possible if respective piles are empty +template +bool operator==(flexible_const_iterator const & t1, + flexible_const_iterator const & t2) +{ + return t1.cit_ == t2.cit_ && t1.pile_.empty() && t2.pile_.empty(); +} + + +Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl) +{ + Row nrow; + nrow.pit(pit); + nrow.pos(pos); + nrow.left_margin = tm.leftMargin(pit, pos); + nrow.right_margin = tm.rightMargin(pit); + nrow.setRTL(is_rtl); + if (is_rtl) + swap(nrow.left_margin, nrow.right_margin); + // Remember that the row width takes into account the left_margin + // but not the right_margin. + nrow.dim().wid = nrow.left_margin; + return nrow; +} + + +void cleanupRow(Row & row, bool at_end) +{ + if (row.empty()) { + row.endpos(row.pos()); + return; + } + + row.endpos(row.back().endpos); + // remove trailing spaces on row break + if (!at_end && !row.flushed()) + row.back().rtrim(); + // boundary exists when there was no space at the end of row + row.end_boundary(!at_end && row.back().endpos == row.endpos()); // make sure that the RTL elements are in reverse ordering - row.reverseRTL(is_rtl); - //LYXERR0("breakrow: row is " << row); + row.reverseRTL(); +} + - return need_new_row; +// Implement the priorities described in RowFlags.h. +bool needsRowBreak(int f1, int f2) +{ + if (f1 & AlwaysBreakAfter /*|| f2 & AlwaysBreakBefore*/) + return true; + if (f1 & NoBreakAfter || f2 & NoBreakBefore) + return false; + if (f1 & BreakAfter || f2 & BreakBefore) + return true; + return false; } + +} + + +RowList TextMetrics::breakParagraph(Row const & bigrow) const +{ + RowList rows; + bool const is_rtl = text_->isRTL(bigrow.pit()); + bool const end_label = text_->getEndLabel(bigrow.pit()) != END_LABEL_NO_LABEL; + int const next_width = max_width_ - leftMargin(bigrow.pit(), bigrow.endpos()) + - rightMargin(bigrow.pit()); + + int width = 0; + flexible_const_iterator fcit = flexible_begin(bigrow); + flexible_const_iterator const end = flexible_end(bigrow); + while (true) { + bool const row_empty = rows.empty() || rows.back().empty(); + // The row flags of previous element, if there is one. + // Otherwise we use NoBreakAfter to avoid an empty row before + // e.g. a displayed equation. + int const f1 = row_empty ? NoBreakAfter : rows.back().back().row_flags; + // The row flags of next element, if there is one. + // Otherwise we use NoBreakBefore (see above), unless the + // paragraph has an end label (for which an empty row is OK). + int const f2 = (fcit == end) ? (end_label ? Inline : NoBreakBefore) + : fcit->row_flags; + if (rows.empty() || needsRowBreak(f1, f2)) { + if (!rows.empty()) { + // Flush row as requested by row flags + rows.back().flushed((f1 & Flush) || (f2 & FlushBefore)); + cleanupRow(rows.back(), false); + } + pos_type pos = rows.empty() ? 0 : rows.back().endpos(); + rows.push_back(newRow(*this, bigrow.pit(), pos, is_rtl)); + // the width available for the row. + width = max_width_ - rows.back().right_margin; + } + + // The stopping condition is here because we may need a new + // empty row at the end. + if (fcit == end) + break; + + // Next element to consider is either the top of the temporary + // pile, or the place when we were in main row + Row::Element elt = *fcit; + Row::Elements tail; + elt.splitAt(width - rows.back().width(), next_width, false, tail); + Row & rb = rows.back(); + if (elt.type == Row::MARGINSPACE) + elt.dim.wid = max(elt.dim.wid, leftMargin(bigrow.pit()) - rb.width()); + rb.push_back(elt); + rb.finalizeLast(); + if (rb.width() > width) { + // Keep the tail for later; this ought to be rare, but play safe. + if (!tail.empty()) + fcit.put(tail); + // if the row is too large, try to cut at last separator. + tail = rb.shortenIfNeeded(width, next_width); + } + + // Go to next element + ++fcit; + + // Handle later the elements returned by splitAt or shortenIfNeeded. + fcit.put(tail); + } + + if (!rows.empty()) { + // Last row in paragraph is flushed + rows.back().flushed(true); + cleanupRow(rows.back(), true); + } + + return rows; +} + + int TextMetrics::parTopSpacing(pit_type const pit) const { Paragraph const & par = text_->getPar(pit); @@ -1096,7 +1205,10 @@ int TextMetrics::parTopSpacing(pit_type const pit) const && prevpar.getLabelWidthString() == par.getLabelWidthString()) { layoutasc = layout.itemsep * dh; } else if (pit != 0 && layout.topsep > 0) - layoutasc = layout.topsep * dh; + // combine the separation between different layouts (with same depth) + layoutasc = max(0.0, + prevpar.getDepth() != par.getDepth() ? layout.topsep + : layout.topsep - prevpar.layout().bottomsep) * dh; asc += int(layoutasc * 2 / (2 + pars[pit].getDepth())); @@ -1183,6 +1295,9 @@ void TextMetrics::setRowHeight(Row & row) const row.dim().asc = maxasc; row.dim().des = maxdes; + + // This is useful for selections + row.contents_dim() = row.dim(); } @@ -1256,7 +1371,7 @@ pos_type TextMetrics::getPosNearX(Row const & row, int & x, || inset->lyxCode() == SEPARATOR_CODE)) pos = row.back().pos; else - boundary = row.right_boundary(); + boundary = row.end_boundary(); } x += xo - offset; @@ -1298,7 +1413,7 @@ pit_type TextMetrics::getPitNearY(int y) ParagraphMetrics const & pm = it->second; - if (y < it->second.position() - int(pm.ascent())) { + if (y < it->second.position() - pm.ascent()) { // We are looking for a position that is before the first paragraph in // the cache (which is in priciple off-screen, that is before the // visible part. @@ -1313,7 +1428,7 @@ pit_type TextMetrics::getPitNearY(int y) ParagraphMetrics const & pm_last = par_metrics_[last->first]; - if (y >= last->second.position() + int(pm_last.descent())) { + if (y >= last->second.position() + pm_last.descent()) { // We are looking for a position that is after the last paragraph in // the cache (which is in priciple off-screen), that is before the // visible part. @@ -1332,7 +1447,7 @@ pit_type TextMetrics::getPitNearY(int y) ParagraphMetrics const & pm2 = par_metrics_[it->first]; - if (it->first >= pit && int(it->second.position()) - int(pm2.ascent()) <= y) { + if (it->first >= pit && it->second.position() - pm2.ascent() <= y) { pit = it->first; yy = it->second.position(); } @@ -1349,7 +1464,7 @@ Row const & TextMetrics::getPitAndRowNearY(int & y, pit_type & pit, { ParagraphMetrics const & pm = par_metrics_[pit]; - int yy = pm.position() - pm.rows().front().ascent(); + int yy = pm.position() - pm.ascent(); LBUFERR(!pm.rows().empty()); RowList::const_iterator rit = pm.rows().begin(); RowList::const_iterator rlast = pm.rows().end(); @@ -1371,7 +1486,7 @@ Row const & TextMetrics::getPitAndRowNearY(int & y, pit_type & pit, --rit; y = yy; } - } else if (up && yy < y) { + } else if (up && yy != y) { if (rit != rlast) { y = yy + rit->height(); ++rit; @@ -1459,7 +1574,7 @@ void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, int const LBUFERR(pm.rows().size()); for (; r < int(pm.rows().size()) - 1; ++r) { Row const & row = pm.rows()[r]; - if (int(yy + row.height()) > y) + if (yy + row.height() > y) break; yy += row.height(); } @@ -1616,8 +1731,9 @@ void TextMetrics::deleteLineForward(Cursor & cur) int TextMetrics::leftMargin(pit_type pit) const { - // the + 1 is useful when the paragraph is empty - return leftMargin(pit, text_->paragraphs()[pit].size() + 1); + // FIXME: what is the semantics? It depends on whether the + // paragraph is empty! + return leftMargin(pit, text_->paragraphs()[pit].size()); } @@ -1630,7 +1746,7 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const Paragraph const & par = pars[pit]; LASSERT(pos >= 0, return 0); // We do not really care whether pos > par.size(), since we do not - // access the data. It can be actially useful, when querying the + // access the data. It can be actually useful, when querying the // margin without indentation (see leftMargin(pit_type). Buffer const & buffer = bv_->buffer(); @@ -1677,11 +1793,13 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const } } - // This happens after sections or environments in standard classes. - // We have to check the previous layout at same depth. + // Check for reasons to remove indentation. + // First, at document level. if (buffer.params().paragraph_separation == BufferParams::ParagraphSkipSeparation) parindent.erase(); + // This happens after sections or environments in standard classes. + // We have to check the previous layout at same depth. else if (pit > 0 && pars[pit - 1].getDepth() >= par.getDepth()) { pit_type prev = text_->depthHook(pit, par.getDepth()); if (par.layout() == pars[prev].layout()) { @@ -1691,6 +1809,15 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const } else if (pars[prev].layout().nextnoindent) parindent.erase(); } + // The previous paragraph may have ended with a separator inset. + if (pit > 0) { + Paragraph const & ppar = pars[pit - 1]; + if (ppar.size() > 0) { + auto * in = dynamic_cast(ppar.getInset(ppar.size() - 1)); + if (in != nullptr && in->nextnoindent()) + parindent.erase(); + } + } FontInfo const labelfont = text_->labelFont(par); FontMetrics const & lfm = theFontMetrics(labelfont); @@ -1772,7 +1899,7 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const // display style insets do not need indentation && !(!par.empty() && par.isInset(0) - && par.getInset(0)->rowFlags() & Inset::Display) + && par.getInset(0)->rowFlags() & Display) && (!(tclass.isDefaultLayout(par.layout()) || tclass.isPlainLayout(par.layout())) || buffer.params().paragraph_separation @@ -1870,6 +1997,9 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const if (text_->isRTL(pit)) swap(pi.leftx, pi.rightx); + BookmarksSection::BookmarkPosList bpl = + theSession().bookmarks().bookmarksInPar(bv_->buffer().fileName(), pm.id()); + for (size_t i = 0; i != nrows; ++i) { Row const & row = pm.rows()[i]; @@ -1914,6 +2044,9 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const // Paint only the insets if the text itself is // unchanged. rp.paintOnlyInsets(); + rp.paintTooLargeMarks( + row_x + row.left_x() < bv_->leftMargin(), + row_x + row.right_x() > bv_->workWidth() - bv_->rightMargin()); row.changed(false); y += row.descent(); continue; @@ -1923,9 +2056,9 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const // already cleared because of a full repaint. if (!pi.full_repaint && row.changed()) { LYXERR(Debug::PAINTING, "Clear rect@(" - << max(row_x, 0) << ", " << y - row.ascent() << ")=" + << x << ", " << y - row.ascent() << ")=" << width() << " x " << row.height()); - pi.pain.fillRectangle(row_x, y - row.ascent(), + pi.pain.fillRectangle(x, y - row.ascent(), width(), row.height(), pi.background_color); } @@ -1956,8 +2089,15 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const if (i == nrows - 1) rp.paintLast(); rp.paintText(); - rp.paintTooLargeMarks(row_x + row.left_x() < 0, - row_x + row.right_x() > bv_->workWidth()); + rp.paintTooLargeMarks( + row_x + row.left_x() < bv_->leftMargin(), + row_x + row.right_x() > bv_->workWidth() - bv_->rightMargin()); + // indicate bookmarks presence in margin + if (lyxrc.bookmarks_visibility == LyXRC::BMK_MARGIN) + for (auto const & bp_p : bpl) + if (bp_p.second >= row.pos() && bp_p.second < row.endpos()) + rp.paintBookmark(bp_p.first); + y += row.descent(); #if 0