]> git.lyx.org Git - lyx.git/blobdiff - src/TextMetrics.cpp
MathML: more consistency between DocBook and XHTML.
[lyx.git] / src / TextMetrics.cpp
index 28d0d87b0eed8ee82146e1c7d1dc941f1117770f..0b3bfc1d16a79ce057eb979d2231dda30cfd9974 100644 (file)
 #include "TextMetrics.h"
 
 #include "Buffer.h"
-#include "buffer_funcs.h"
 #include "BufferParams.h"
 #include "BufferView.h"
 #include "CoordCache.h"
 #include "Cursor.h"
 #include "CutAndPaste.h"
-#include "InsetList.h"
-#include "Language.h"
 #include "Layout.h"
 #include "LyXRC.h"
 #include "MetricsInfo.h"
 #include "ParagraphParameters.h"
 #include "RowPainter.h"
+#include "Session.h"
 #include "Text.h"
 #include "TextClass.h"
 #include "VSpace.h"
 
 #include "insets/InsetText.h"
 
-#include "mathed/InsetMathMacroTemplate.h"
+#include "mathed/MacroTable.h"
 
 #include "frontends/FontMetrics.h"
-#include "frontends/Painter.h"
 #include "frontends/NullPainter.h"
 
-#include "support/convert.h"
 #include "support/debug.h"
 #include "support/lassert.h"
-#include "support/lyxlib.h"
+#include "support/Changer.h"
 
 #include <stdlib.h>
 #include <cmath>
@@ -62,10 +58,6 @@ using frontend::FontMetrics;
 
 namespace {
 
-// the somewhat arbitrary leading added between rows. This is 20% of
-// the characters height, inluding the possible leading of the font.
-// 20% is a standard value used by LaTeX and word processors.
-double const extra_leading = 0.2;
 
 int numberOfLabelHfills(Paragraph const & par, Row const & row)
 {
@@ -159,6 +151,16 @@ bool TextMetrics::isFirstRow(Row const & row) const
 }
 
 
+void TextMetrics::setRowChanged(pit_type pit, pos_type pos)
+{
+       for (auto & pm_pair : par_metrics_)
+               if (pm_pair.first == pit)
+                       for (Row & row : pm_pair.second.rows())
+                               if (row.pos() == pos)
+                                       row.changed(true);
+}
+
+
 ParagraphMetrics & TextMetrics::parMetrics(pit_type pit, bool redo)
 {
        ParMetricsCache::iterator pmc_it = par_metrics_.find(pit);
@@ -178,6 +180,12 @@ ParagraphMetrics const & TextMetrics::parMetrics(pit_type pit) const
 }
 
 
+ParagraphMetrics & TextMetrics::parMetrics(pit_type pit)
+{
+       return parMetrics(pit, true);
+}
+
+
 void TextMetrics::newParMetricsDown()
 {
        pair<pit_type, ParagraphMetrics> const & last = *par_metrics_.rbegin();
@@ -208,7 +216,7 @@ void TextMetrics::newParMetricsUp()
 }
 
 
-bool TextMetrics::metrics(MetricsInfo & mi, Dimension & dim, int min_width,
+bool TextMetrics::metrics(MetricsInfo const & mi, Dimension & dim, int min_width,
                          bool const expand_on_multipars)
 {
        LBUFERR(mi.base.textwidth > 0);
@@ -228,7 +236,7 @@ bool TextMetrics::metrics(MetricsInfo & mi, Dimension & dim, int min_width,
        //      << " maxWidth: " << max_width_ << "\nfont: " << mi.base.font << endl;
 
        bool changed = false;
-       unsigned int h = 0;
+       int h = 0;
        for (pit_type pit = 0; pit != npar; ++pit) {
                // create rows, but do not set alignment yet
                changed |= redoParagraph(pit, false);
@@ -375,13 +383,13 @@ bool TextMetrics::isRTLBoundary(pit_type pit, pos_type pos,
            || !contains(pit))
                return false;
 
-       ParagraphMetrics & pm = par_metrics_[pit];
+       ParagraphMetrics const & pm = par_metrics_[pit];
        // no RTL boundary in empty paragraph
        if (pm.rows().empty())
                return false;
 
-       pos_type endpos = pm.getRow(pos - 1, false).endpos();
-       pos_type startpos = pm.getRow(pos, false).pos();
+       pos_type const endpos = pm.getRow(pos - 1, false).endpos();
+       pos_type const startpos = pm.getRow(pos, false).pos();
        // no RTL boundary at line start:
        // abc\n   -> toggle to RTL ->    abc\n     (and not:    abc\n|
        // |                              |                               )
@@ -399,7 +407,7 @@ bool TextMetrics::isRTLBoundary(pit_type pit, pos_type pos,
                        || par.isSeparator(pos - 1)))
                return false;
 
-       bool left = font.isVisibleRightToLeft();
+       bool const left = font.isVisibleRightToLeft();
        bool right;
        if (pos == par.size())
                right = par.isRTL(bv_->buffer().params());
@@ -426,8 +434,9 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
        // FIXME: This check ought to be done somewhere else. It is the reason
        // why text_ is not const. But then, where else to do it?
        // Well, how can you end up with either (a) a biblio environment that
-       // has no InsetBibitem or (b) a biblio environment with more than one
-       // InsetBibitem? I think the answer is: when paragraphs are merged;
+       // has no InsetBibitem, (b) a biblio environment with more than one
+       // InsetBibitem or (c) a paragraph that has a bib item but is no biblio
+       // 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());
@@ -503,7 +512,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);
+               MetricsInfo mi(bv_, font.fontInfo(), w, mc, e.pos == 0);
                e.inset->metrics(mi, dim);
                if (!insetCache.has(e.inset) || insetCache.dim(e.inset) != dim) {
                        insetCache.add(e.inset, dim);
@@ -518,10 +527,11 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
        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);
-               row.pit(pit);
                need_new_row = breakRow(row, right_margin);
                setRowHeight(row);
                row.changed(true);
@@ -541,44 +551,59 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
                first = row.endpos();
                ++row_index;
 
-               pm.dim().wid = max(pm.dim().wid, row.width());
+               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) {
+               int offset = 0;
+               if (par.isRTL(buffer.params())) {
+                       // globally align the paragraph to the left.
+                       int minleft = max_width_;
+                       for (Row const & row : pm.rows())
+                               minleft = min(minleft, row.left_margin);
+                       offset = right_margin - minleft;
+               } else {
+                       // globally align the paragraph to the right.
+                       int maxwid = 0;
+                       for (Row const & row : pm.rows())
+                               maxwid = max(maxwid, row.width());
+                       offset = max_width_ - right_margin - maxwid;
+               }
+
+               for (Row & row : pm.rows()) {
+                       row.left_margin += offset;
+                       row.dim().wid += offset;
+               }
+       }
+
+       // The space above and below the paragraph.
+       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.
-       // Top and bottom margin of the document (only at top-level)
        if (text_->isMainText()) {
-               // original value was 20px, which is 0.2in at 100dpi
-               int const margin = bv_->zoomedPixels(20);
-               if (pit == 0) {
-                       pm.rows().front().dim().asc += margin;
-                       /* 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 += margin;
-               }
-               ParagraphList const & pars = text_->paragraphs();
-               if (pit + 1 == pit_type(pars.size())) {
-                       pm.rows().back().dim().des += margin;
-                       pm.dim().des += margin;
+               if (pit == 0)
+                       top += bv_->topMargin();
+               if (pit + 1 == pit_type(text_->paragraphs().size())) {
+                       bottom += bv_->bottomMargin();
                }
        }
 
-       // The space above and below the paragraph.
-       int const top = parTopSpacing(pit);
+       // Add the top/bottom space to rows and paragraph metrics
        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();
+       // 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();
 
@@ -588,7 +613,7 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
 
 LyXAlignment TextMetrics::getAlign(Paragraph const & par, Row const & row) const
 {
-       LyXAlignment align = par.getAlign();
+       LyXAlignment align = par.getAlign(bv_->buffer().params());
 
        // handle alignment inside tabular cells
        Inset const & owner = text_->inset();
@@ -613,19 +638,13 @@ 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())) {
-               switch (inset->display()) {
-               case Inset::AlignLeft:
-                       align = LYX_ALIGN_BLOCK;
-                       break;
-               case Inset::AlignCenter:
-                       align = LYX_ALIGN_CENTER;
-                       break;
-               case Inset::Inline:
-                       // unchanged (use align)
-                       break;
-               case Inset::AlignRight:
-                       align = LYX_ALIGN_RIGHT;
-                       break;
+               if (inset->rowFlags() & Inset::Display) {
+                       if (inset->rowFlags() & Inset::AlignLeft)
+                               align = LYX_ALIGN_BLOCK;
+                       else if (inset->rowFlags() & Inset::AlignRight)
+                               align = LYX_ALIGN_RIGHT;
+                       else
+                               align = LYX_ALIGN_CENTER;
                }
        }
 
@@ -677,7 +696,7 @@ void TextMetrics::setRowAlignment(Row & row, int width) const
        }
 
        // are there any hfills in the row?
-       ParagraphMetrics & pm = par_metrics_[row.pit()];
+       ParagraphMetrics const & pm = par_metrics_[row.pit()];
        int nh = numberOfHfills(row, pm, par.beginOfBody());
        int hfill = 0;
        int hfill_rem = 0;
@@ -786,22 +805,6 @@ int TextMetrics::labelFill(Row const & row) const
 }
 
 
-#if 0
-// Not used, see TextMetrics::breakRow
-// this needs special handling - only newlines count as a break point
-static pos_type addressBreakPoint(pos_type i, Paragraph const & par)
-{
-       pos_type const end = par.size();
-
-       for (; i < end; ++i)
-               if (par.isNewline(i))
-                       return i + 1;
-
-       return end;
-}
-#endif
-
-
 int TextMetrics::labelEnd(pit_type const pit) const
 {
        // labelEnd is only needed if the layout fills a flushleft label.
@@ -875,14 +878,18 @@ private:
  */
 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(par);
+       bool const is_rtl = text_->isRTL(row.pit());
        bool need_new_row = false;
 
-       row.clear();
        row.left_margin = leftMargin(row.pit(), pos);
        row.right_margin = right_margin;
        if (is_rtl)
@@ -893,14 +900,6 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
        // the width available for the row.
        int const width = max_width_ - row.right_margin;
 
-#if 0
-       //FIXME: As long as leftMargin() is not correctly implemented for
-       // MARGIN_RIGHT_ADDRESS_BOX, we should also not do this here.
-       // Otherwise, long rows will be painted off the screen.
-       if (par.layout().margintype == MARGIN_RIGHT_ADDRESS_BOX)
-               return addressBreakPoint(pos, par);
-#endif
-
        // check for possible inline completion
        DocIterator const & ic_it = bv_->inlineCompletionPos();
        pos_type ic_pos = -1;
@@ -911,7 +910,24 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
        // 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);
-       while (i < end && (i == pos || row.width() <= width)) {
+       // 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 || (i != pos && row.width() > width))
+                       break;
+
                char_type c = par.getChar(i);
                // The most special cases are handled first.
                if (par.isInset(i)) {
@@ -967,17 +983,22 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
                }
 
                // Handle some situations that abruptly terminate the row
-               // - A newline inset
-               // - Before a display inset
-               // - After a display inset
-               Inset const * inset = 0;
-               if (par.isNewline(i) || par.isEnvSeparator(i)
-                   || (i + 1 < end && (inset = par.getInset(i + 1))
-                       && inset->display())
-                   || (!row.empty() && row.back().inset
-                       && row.back().inset->display())) {
+               // - 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);
-                       need_new_row = par.isNewline(i);
+                       // 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;
                }
@@ -991,18 +1012,18 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
        // End of paragraph marker. The logic here is almost the
        // same as in redoParagraph, remember keep them in sync.
        ParagraphList const & pars = text_->paragraphs();
-       if (lyxrc.paragraph_markers && !need_new_row
+       Change const & change = par.lookupChange(i);
+       if ((lyxrc.paragraph_markers || change.changed())
+           && !need_new_row
            && 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
                // in the paragraph.
                Font f(text_->layoutFont(row.pit()));
                f.fontInfo().setColor(Color_paragraphmarker);
-               BufferParams const & bparams
-                       = text_->inset().buffer().params();
-               f.setLanguage(par.getParLanguage(bparams));
+               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, change);
        }
 
        // Is there a end-of-paragaph change?
@@ -1136,19 +1157,38 @@ void TextMetrics::setRowHeight(Row & row) const
        // Initial value for ascent (useful if row is empty).
        Font const font = displayFont(row.pit(), row.pos());
        FontMetrics const & fm = theFontMetrics(font);
-       int maxasc = fm.maxAscent() + fm.leading();
-       int maxdes = fm.maxDescent();
+       int maxasc = int(fm.maxAscent() * spacing_val);
+       int maxdes = int(fm.maxDescent() * spacing_val);
+
+       // Take label string into account (useful if labelfont is large)
+       if (row.pos() == 0 && layout.labelIsInline()) {
+               FontInfo const labelfont = text_->labelFont(par);
+               FontMetrics const & lfm = theFontMetrics(labelfont);
+               maxasc = max(maxasc, int(lfm.maxAscent() * spacing_val));
+               maxdes = max(maxdes, int(lfm.maxDescent() * spacing_val));
+       }
 
        // Find the ascent/descent of the row contents
        for (Row::Element const & e : row) {
-               maxasc = max(maxasc, e.dim.ascent());
-               maxdes = max(maxdes, e.dim.descent());
+               if (e.inset) {
+                       maxasc = max(maxasc, e.dim.ascent());
+                       maxdes = max(maxdes, e.dim.descent());
+               } else {
+                       FontMetrics const & fm2 = theFontMetrics(e.font);
+                       maxasc = max(maxasc, int(fm2.maxAscent() * spacing_val));
+                       maxdes = max(maxdes, int(fm2.maxDescent() * spacing_val));
+               }
        }
 
-       // Add some leading (split between before and after)
-       int const leading = support::iround(extra_leading * (maxasc + maxdes));
-       row.dim().asc = int((maxasc + leading - leading / 2) * spacing_val);
-       row.dim().des = int((maxdes + leading / 2) * spacing_val);
+       // This is nicer with box insets
+       ++maxasc;
+       ++maxdes;
+
+       row.dim().asc = maxasc;
+       row.dim().des = maxdes;
+
+       // This is useful for selections
+       row.contents_dim() = row.dim();
 }
 
 
@@ -1417,7 +1457,7 @@ void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, int const
 
        ParagraphMetrics const & pm = par_metrics_[pit];
 
-       int yy = pm.position() - pm.ascent();
+       int yy = pm.position() - pm.rows().front().ascent();
        LYXERR(Debug::DEBUG, "x: " << x << " y: " << y <<
                " pit: " << pit << " yy: " << yy);
 
@@ -1464,7 +1504,7 @@ InsetList::Element * TextMetrics::checkInsetHit(pit_type pit, int x, int y)
        }
 
        LYXERR(Debug::DEBUG, "No inset hit. ");
-       return 0;
+       return nullptr;
 }
 
 
@@ -1582,6 +1622,8 @@ void TextMetrics::deleteLineForward(Cursor & cur)
 
 int TextMetrics::leftMargin(pit_type pit) const
 {
+       // FIXME: what is the semantics? It depends on whether the
+       // paragraph is empty!
        return leftMargin(pit, text_->paragraphs()[pit].size());
 }
 
@@ -1594,7 +1636,10 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const
        LASSERT(pit < int(pars.size()), return 0);
        Paragraph const & par = pars[pit];
        LASSERT(pos >= 0, return 0);
-       LASSERT(pos <= par.size(), return 0);
+       // We do not really care whether pos > par.size(), since we do not
+       // access the data. It can be actually useful, when querying the
+       // margin without indentation (see leftMargin(pit_type).
+
        Buffer const & buffer = bv_->buffer();
        //lyxerr << "TextMetrics::leftMargin: pit: " << pit << " pos: " << pos << endl;
        DocumentClass const & tclass = buffer.params().documentClass();
@@ -1709,30 +1754,15 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const
                }
                break;
 
-       case MARGIN_RIGHT_ADDRESS_BOX: {
-#if 0
-               // The left margin depends on the widest row in this paragraph.
-               // This code is wrong because it depends on the rows, but at the
-               // same time this function is used in redoParagraph to construct
-               // the rows.
-               ParagraphMetrics const & pm = par_metrics_[pit];
-               int minfill = max_width_;
-               for (row : pm.rows())
-                       if (row.fill() < minfill)
-                               minfill = row.fill();
-               l_margin += bfm.signedWidth(layout.leftmargin);
-               l_margin += minfill;
-#endif
-               // also wrong, but much shorter.
-               l_margin += max_width_ / 2;
+       case MARGIN_RIGHT_ADDRESS_BOX:
+               // This is handled globally in redoParagraph().
                break;
        }
-       }
 
        if (!par.params().leftIndent().zero())
                l_margin += par.params().leftIndent().inPixels(max_width_, lfm.em());
 
-       LyXAlignment align = par.getAlign();
+       LyXAlignment align = par.getAlign(bv_->buffer().params());
 
        // set the correct parindent
        if (pos == 0
@@ -1746,10 +1776,10 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const
            && !par.params().noindent()
            // in some insets, paragraphs are never indented
            && !text_->inset().neverIndent()
-           // display style insets are always centered, omit indentation
+           // display style insets do not need indentation
            && !(!par.empty()
-                && par.isInset(pos)
-                && par.getInset(pos)->display())
+                && par.isInset(0)
+                && par.getInset(0)->rowFlags() & Inset::Display)
            && (!(tclass.isDefaultLayout(par.layout())
                || tclass.isPlainLayout(par.layout()))
                || buffer.params().paragraph_separation
@@ -1795,6 +1825,9 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
        if (pm.rows().empty())
                return;
        size_t const nrows = pm.rows().size();
+       // Remember left and right margin for drawing math numbers
+       Changer changeleft = changeVar(pi.leftx, x + leftMargin(pit));
+       Changer changeright = changeVar(pi.rightx, x + width() - rightMargin(pit));
 
        // Use fast lane in nodraw stage.
        if (pi.pain.isNull()) {
@@ -1841,6 +1874,12 @@ 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];
@@ -1872,12 +1911,8 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
                                row.change(row.end_margin_sel, sel_end.pit() > pit);
                }
 
-               // has row changed since last paint?
-               bool row_has_changed = row.changed()
-                       || bv_->hadHorizScrollOffset(text_, pit, row.pos());
-
                // Take this opportunity to spellcheck the row contents.
-               if (row_has_changed && pi.do_spellcheck && lyxrc.spellcheck_continuously) {
+               if (row.changed() && pi.do_spellcheck && lyxrc.spellcheck_continuously) {
                        text_->getPar(pit).spellCheck();
                }
 
@@ -1885,10 +1920,13 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
 
                // Don't paint the row if a full repaint has not been requested
                // and if it has not changed.
-               if (!pi.full_repaint && !row_has_changed) {
+               if (!pi.full_repaint && !row.changed()) {
                        // 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;
@@ -1896,32 +1934,24 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
 
                // Clear background of this row if paragraph background was not
                // already cleared because of a full repaint.
-               if (!pi.full_repaint && row_has_changed) {
+               if (!pi.full_repaint && row.changed()) {
                        LYXERR(Debug::PAINTING, "Clear rect@("
                               << max(row_x, 0) << ", " << y - row.ascent() << ")="
                               << width() << " x " << row.height());
-                       // FIXME: this is a hack. We know that at least this
-                       // amount of pixels can be cleared on right and left.
-                       // Doing so gets rid of caret ghosts when the cursor is at
-                       // the begining/end of row. However, it will not work if
-                       // the caret has a ridiculous width like 6. (see ticket
-                       // #10797)
-                       pi.pain.fillRectangle(max(row_x, 0) - Inset::TEXT_TO_INSET_OFFSET,
-                                             y - row.ascent(),
-                                             width() + 2 * Inset::TEXT_TO_INSET_OFFSET,
-                                             row.height(), pi.background_color);
+                       pi.pain.fillRectangle(row_x, y - row.ascent(),
+                                             width(), row.height(), pi.background_color);
                }
 
                // Instrumentation for testing row cache (see also
                // 12 lines lower):
                if (lyxerr.debugging(Debug::PAINTING)
-                   && (row.selection() || pi.full_repaint || row_has_changed)) {
+                   && (row.selection() || pi.full_repaint || row.changed())) {
                        string const foreword = text_->isMainText() ? "main text redraw "
                                : "inset text redraw: ";
                        LYXERR0(foreword << "pit=" << pit << " row=" << i
                                << (row.selection() ? " row_selection": "")
                                << (pi.full_repaint ? " full_repaint" : "")
-                               << (row_has_changed ? " row_has_changed" : ""));
+                               << (row.changed() ? " row.changed" : ""));
                }
 
                // Backup full_repaint status and force full repaint
@@ -1939,8 +1969,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
@@ -1952,7 +1989,7 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
                static int count = 0;
                ++count;
                FontInfo fi(sane_font);
-               fi.setSize(FONT_SIZE_TINY);
+               fi.setSize(TINY_SIZE);
                fi.setColor(Color_red);
                pi.pain.text(row_x, y, convert<docstring>(count), fi);
 #endif
@@ -1970,26 +2007,22 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
 void TextMetrics::completionPosAndDim(Cursor const & cur, int & x, int & y,
        Dimension & dim) const
 {
-       Cursor const & bvcur = cur.bv().cursor();
+       DocIterator from = cur.bv().cursor();
+       DocIterator to = from;
+       text_->getWord(from.top(), to.top(), PREVIOUS_WORD);
 
-       // get word in front of cursor
-       docstring word = text_->previousWord(bvcur.top());
-       DocIterator wordStart = bvcur;
-       wordStart.pos() -= word.length();
-
-       // calculate dimensions of the word
-       Row row;
-       row.pit(bvcur.pit());
-       row.pos(wordStart.pos());
-       row.endpos(bvcur.pos());
-       setRowHeight(row);
-       dim = row.dim();
+       // The vertical dimension of the word
+       Font const font = displayFont(cur.pit(), from.pos());
+       FontMetrics const & fm = theFontMetrics(font);
+       // the +1's below are related to the extra pixels added in setRowHeight
+       dim.asc = fm.maxAscent() + 1;
+       dim.des = fm.maxDescent() + 1;
 
        // get position on screen of the word start and end
        //FIXME: Is it necessary to explicitly set this to false?
-       wordStart.boundary(false);
-       Point lxy = cur.bv().getPos(wordStart);
-       Point rxy = cur.bv().getPos(bvcur);
+       from.boundary(false);
+       Point lxy = cur.bv().getPos(from);
+       Point rxy = cur.bv().getPos(to);
        dim.wid = abs(rxy.x_ - lxy.x_);
 
        // calculate position of word
@@ -2002,8 +2035,7 @@ void TextMetrics::completionPosAndDim(Cursor const & cur, int & x, int & y,
 
 int defaultRowHeight()
 {
-       FontMetrics const & fm = theFontMetrics(sane_font);
-       return support::iround(fm.maxHeight() * (1 + extra_leading) + fm.leading());
+       return int(theFontMetrics(sane_font).maxHeight() *  1.2);
 }
 
 } // namespace lyx