#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 <stdlib.h>
#include <cmath>
}
+ParagraphMetrics & TextMetrics::parMetrics(pit_type pit)
+{
+ return parMetrics(pit, true);
+}
+
+
void TextMetrics::newParMetricsDown()
{
pair<pit_type, ParagraphMetrics> const & last = *par_metrics_.rbegin();
}
-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);
// << " 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);
|| !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|
// | | )
|| 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());
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);
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);
- // 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;
+ // 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;
}
- ParagraphList const & pars = text_->paragraphs();
- if (pit + 1 == pit_type(pars.size())) {
- pm.rows().back().dim().des += margin;
- pm.dim().des += margin;
+
+ for (Row & row : pm.rows()) {
+ row.left_margin += offset;
+ row.dim().wid += offset;
}
}
pm.dim().asc += pm.rows()[0].ascent();
pm.dim().des -= pm.rows()[0].ascent();
+ // 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();
+ }
+ }
+
changed |= old_dim.height() != pm.dim().height();
return changed;
// 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;
}
}
}
// 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;
}
-#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.
{
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();
// 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;
// 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.
+ 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)) {
}
// 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;
}
// 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);
}
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) {
{
ParagraphMetrics const & pm = par_metrics_[pit];
- int yy = pm.position() - pm.ascent();
+ int yy = pm.position() - pm.rows().front().ascent();
LBUFERR(!pm.rows().empty());
RowList::const_iterator rit = pm.rows().begin();
RowList::const_iterator rlast = pm.rows().end();
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);
}
LYXERR(Debug::DEBUG, "No inset hit. ");
- return 0;
+ return nullptr;
}
int TextMetrics::leftMargin(pit_type pit) const
{
- return leftMargin(pit, text_->paragraphs()[pit].size());
+ // the + 1 is useful when the paragraph is empty
+ return leftMargin(pit, text_->paragraphs()[pit].size() + 1);
}
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 actially 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();
}
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());
&& !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
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()) {
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
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