X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FTextMetrics.cpp;h=0b3bfc1d16a79ce057eb979d2231dda30cfd9974;hb=dd1a85a2baf0aeb867a634ade47275b952d38354;hp=d908ef30aa2609e79905804535ec09d6c3e4d86b;hpb=e52af398649d33825cd41d63db83b41d770ce88e;p=lyx.git diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index d908ef30aa..0b3bfc1d16 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -20,36 +20,31 @@ #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/RefChanger.h" +#include "support/Changer.h" #include #include @@ -185,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 const & last = *par_metrics_.rbegin(); @@ -215,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); @@ -382,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| // | | ) @@ -406,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()); @@ -511,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); @@ -550,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(); @@ -622,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; } } @@ -686,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; @@ -795,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. @@ -886,6 +880,10 @@ 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(); @@ -902,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; @@ -920,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)) { @@ -976,23 +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); - // We will force a row creation after either - // - a newline; - // - a display inset followed by a end label. - need_new_row = - par.isNewline(i) - || (inset && inset->display() && i + 1 == end - && text_->getEndLabel(row.pit()) != END_LABEL_NO_LABEL); + // 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; } @@ -1015,9 +1021,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const // 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); } @@ -1156,6 +1160,14 @@ void TextMetrics::setRowHeight(Row & row) const 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) { if (e.inset) { @@ -1174,6 +1186,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(); } @@ -1442,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); @@ -1489,7 +1504,7 @@ InsetList::Element * TextMetrics::checkInsetHit(pit_type pit, int x, int y) } LYXERR(Debug::DEBUG, "No inset hit. "); - return 0; + return nullptr; } @@ -1607,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()); } @@ -1619,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(); @@ -1734,25 +1754,10 @@ 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()); @@ -1771,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 @@ -1821,8 +1826,8 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const return; size_t const nrows = pm.rows().size(); // Remember left and right margin for drawing math numbers - Changer changeleft = make_change(pi.leftx, x + leftMargin(pit)); - Changer changeright = make_change(pi.rightx, x + width() - rightMargin(pit)); + 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()) { @@ -1872,6 +1877,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]; @@ -1916,6 +1924,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; @@ -1927,16 +1938,8 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const LYXERR(Debug::PAINTING, "Clear rect@(" << max(row_x, 0) << ", " << y - row.ascent() << ")=" << width() << " x " << row.height()); - // FIXME: this is a hack. We clear an amount equal to - // cursor width. This will not work if the caret has a - // ridiculous width like 6. (see ticket #10797) - // This is the same formula as in GuiWorkArea. - int const caret_width = lyxrc.cursor_width - ? lyxrc.cursor_width - : 1 + int((lyxrc.currentZoom + 50) / 200.0); - pi.pain.fillRectangle(max(row_x, 0), y - row.ascent(), - width() + caret_width, - 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 @@ -1966,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 @@ -1997,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(); - - // get word in front of cursor - docstring word = text_->previousWord(bvcur.top()); - DocIterator wordStart = bvcur; - wordStart.pos() -= word.length(); + DocIterator from = cur.bv().cursor(); + DocIterator to = from; + text_->getWord(from.top(), to.top(), PREVIOUS_WORD); - // 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