]> git.lyx.org Git - features.git/blobdiff - src/TextMetrics.cpp
Break the paragraph's big row according to margins
[features.git] / src / TextMetrics.cpp
index eb339442b95d7c270272a58c28d20239efb9ee64..fb6574d72ed8d6e01a3a3440f66f411d2a60a1ab 100644 (file)
@@ -106,14 +106,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 +211,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 +434,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<Cursor &>(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);
@@ -498,8 +493,9 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
                // substracted to the available width. The logic here is
                // almost the same as in breakRow, 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 +508,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);
@@ -536,12 +532,12 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
                setRowHeight(row);
                row.changed(true);
                if ((row_index || row.endpos() < par.size() || row.right_boundary())
-                   && par.inInset().lyxCode() != CELL_CODE) {
+                   && !tight_) {
                        /* 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.
+                        * We do nothing when tight insets are requested.
                         */
                        if (dim_.wid < max_width_)
                                dim_.wid = max_width_;
@@ -582,27 +578,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 +634,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)
+               if (inset->rowFlags() & Display) {
+                       if (inset->rowFlags() & AlignLeft)
                                align = LYX_ALIGN_BLOCK;
-                       else if (inset->rowFlags() & Inset::AlignRight)
+                       else if (inset->rowFlags() & AlignRight)
                                align = LYX_ALIGN_RIGHT;
                        else
                                align = LYX_ALIGN_CENTER;
@@ -702,7 +700,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) {
@@ -870,59 +868,255 @@ 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;
+       // 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() == 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 = 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.
+               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)
+                       break;
+
+               char_type c = par.getChar(i);
+               // The most special cases are handled first.
+               if (par.isInset(i)) {
+                       Inset const * ins = par.getInset(i);
+                       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
+                       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(pit) - row.width());
+                       row.addSpace(i, add, *fi, par.lookupChange(i));
+               } else if (c == '\t')
+                       row.addSpace(i, theFontMetrics(*fi).width(from_ascii("    ")),
+                                    *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
+                        * 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), i >= body_pos);
+               } else
+                       // row elements before body are unbreakable
+                       row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
+
+               // add inline completion width
+               // draw logically behind the previous character
+               if (ic_pos == i + 1 && !bv_->inlineCompletion().empty()) {
+                       docstring const comp = bv_->inlineCompletion();
+                       size_t const uniqueTo =bv_->inlineCompletionUniqueChars();
+                       Font f = *fi;
+
+                       if (uniqueTo > 0) {
+                               f.fontInfo().setColor(Color_inlinecompletion);
+                               row.addVirtual(i + 1, comp.substr(0, uniqueTo), f, Change());
+                       }
+                       f.fontInfo().setColor(Color_nonunique_inlinecompletion);
+                       row.addVirtual(i + 1, comp.substr(uniqueTo), f, Change());
+               }
+
+               ++i;
+               ++fi;
+       }
+       row.finalizeLast();
+       row.endpos(end);
+
+       // End of paragraph marker. 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())
+           && i == end && 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(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);
+       }
+
+       return row;
+}
+
+
+namespace {
+
+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);
        if (is_rtl)
-               swap(row.left_margin, row.right_margin);
+               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;
+}
+
+}
+
+
+RowList TextMetrics::breakParagraph(Row const & row) const
+{
+       RowList rows;
+       bool const is_rtl = text_->isRTL(row.pit());
+
+       bool need_new_row = true;
+       pos_type pos = 0;
+       int width = 0;
+       Row::const_iterator cit = row.begin();
+       Row::const_iterator const end = row.end();
+       // This is a vector, but we use it like a pile putting and taking
+       // stuff at the back.
+       Row::Elements pile;
+       while (true) {
+               if (need_new_row) {
+                       if (!rows.empty())
+                               rows.back().endpos(pos);
+                       rows.push_back(newRow(*this, row.pit(), pos, is_rtl));
+                       // the width available for the row.
+                       width = max_width_ - rows.back().right_margin;
+                       need_new_row = false;
+               }
+
+               // The stopping condition is here because we may need a new
+               // empty row at the end.
+               if (cit == end && pile.empty())
+                       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 = pile.empty() ? *cit : pile.back();
+               //LYXERR0("elt=" << elt);
+               Row::Element next_elt = elt.splitAt(width - rows.back().width(),
+                                                   !elt.font.language()->wordWrap());
+               //LYXERR0("next_elt=" << next_elt);
+               // a new element in the row
+               rows.back().push_back(elt);
+               pos = elt.endpos;
+               // Go to next element
+               if (pile.empty())
+                       ++cit;
+               else
+                       pile.pop_back();
+               // Add a new next element on the pile
+               if (next_elt.isValid()) {
+                       pile.push_back(next_elt);
+                       need_new_row = true;
+               }
+       }
+
+       return rows;
+}
+
+/** 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
+{
+       LATTEST(row.empty());//
+       Paragraph const & par = text_->getPar(row.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;
+       row.dim().wid = row.left_margin;//
        // the width available for the row.
-       int const width = max_width_ - row.right_margin;
+       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())
-               ic_pos = ic_it.pos();
+       DocIterator const & ic_it = bv_->inlineCompletionPos();//
+       pos_type ic_pos = -1;//
+       if (ic_it.inTexted() && ic_it.text() == text_ && ic_it.pit() == row.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;
+       pos_type i = pos;//---------------------------------------------------vvv
        FontIterator fi = FontIterator(*this, par, row.pit(), pos);
        // 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 || (i != pos && row.width() > width))//^width
                        break;
 
                char_type c = par.getChar(i);
@@ -960,9 +1154,9 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
                        // ⤶ 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));
+                       row.add(i, screen_char, *fi, par.lookupChange(i), i >= body_pos);
                } else
-                       row.add(i, c, *fi, par.lookupChange(i));
+                       row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
 
                // add inline completion width
                // draw logically behind the previous character
@@ -977,15 +1171,16 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
                        }
                        f.fontInfo().setColor(Color_nonunique_inlinecompletion);
                        row.addVirtual(i + 1, comp.substr(uniqueTo), f, Change());
-               }
+               }//---------------------------------------------------------------^^^
 
+               // FIXME: Handle when breaking the rows
                // 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)) {
+               if ((nextInset && nextInset->rowFlags() & BreakBefore)
+                   || (prevInset && prevInset->rowFlags() & BreakAfter)) {
                        row.flushed(true);
                        // Force a row creation after this one if it is ended by
                        // an inset that either
@@ -993,8 +1188,8 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
                        // - 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
+                               (prevInset->rowFlags() & AlwaysBreakAfter
+                                || (prevInset->rowFlags() & BreakAfter && i + 1 == end
                                     && text_->getEndLabel(row.pit()) != END_LABEL_NO_LABEL));
                        ++i;
                        break;
@@ -1003,6 +1198,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
                ++i;
                ++fi;
        }
+       //--------------------------------------------------------------------vvv
        row.finalizeLast();
        row.endpos(i);
 
@@ -1011,7 +1207,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
        ParagraphList const & pars = text_->paragraphs();
        Change const & change = par.lookupChange(i);
        if ((lyxrc.paragraph_markers || change.changed())
-           && !need_new_row
+           && !need_new_row // not this
            && i == end && size_type(row.pit() + 1) < pars.size()) {
                // add a virtual element for the end-of-paragraph
                // marker; it is shown on screen, but does not exist
@@ -1026,13 +1222,15 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
        // Is there a end-of-paragaph change?
        if (i == end && par.lookupChange(end).changed() && !need_new_row)
                row.needsChangeBar(true);
+    //--------------------------------------------------------------------^^^
+       // FIXME : nothing below this
 
        // 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))
+       if (row.shortenIfNeeded(width, next_width))
                row.flushed(false);
        row.right_boundary(!row.empty() && row.endpos() < end
                           && row.back().endpos == row.endpos());
@@ -1183,6 +1381,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();
 }
 
 
@@ -1298,7 +1499,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 +1514,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 +1533,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 +1550,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 +1572,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 +1660,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 +1817,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 +1832,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();
@@ -1772,7 +1974,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 +2072,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.par().id());
+
        for (size_t i = 0; i != nrows; ++i) {
 
                Row const & row = pm.rows()[i];
@@ -1914,6 +2119,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;
@@ -1956,8 +2164,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