]> 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 1ee6a51b38bec5bd832579320ff74e6c7b520f3d..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_;
@@ -638,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;
@@ -704,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) {
@@ -872,41 +868,257 @@ private:
 
 } // namespace
 
+
+Row TextMetrics::tokenizeParagraph(pit_type const pit) const
+{
+       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 body_pos = par.beginOfBody();
+
+       // 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(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());
-       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);
+       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);
-       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))//^width
+                       break;
+
                char_type c = par.getChar(i);
                // The most special cases are handled first.
                if (par.isInset(i)) {
@@ -942,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
@@ -959,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
@@ -975,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;
@@ -985,6 +1198,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
                ++i;
                ++fi;
        }
+       //--------------------------------------------------------------------vvv
        row.finalizeLast();
        row.endpos(i);
 
@@ -993,16 +1207,14 @@ 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
                // 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);
        }
@@ -1010,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());
@@ -1285,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.
@@ -1300,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.
@@ -1319,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();
                }
@@ -1446,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();
        }
@@ -1760,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
@@ -1954,9 +2168,10 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const
                        row_x + row.left_x() < bv_->leftMargin(),
                        row_x + row.right_x() > bv_->workWidth() - bv_->rightMargin());
                // indicate bookmarks presence in margin
-               for (auto const & bp_p : bpl)
-                       if (bp_p.second >= row.pos() && bp_p.second < row.endpos())
-                               rp.paintBookmark(bp_p.first);
+               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();