#include "TextClass.h"
#include "VSpace.h"
+#include "insets/InsetSeparator.h"
#include "insets/InsetText.h"
#include "mathed/MacroTable.h"
}
+void TextMetrics::forget(pit_type pit)
+{
+ par_metrics_.erase(pit);
+}
+
+
pair<pit_type, ParagraphMetrics const *> TextMetrics::first() const
{
ParMetricsCache::const_iterator it = par_metrics_.begin();
}
+pair<pit_type, ParagraphMetrics const *> TextMetrics::firstVisible() const
+{
+ // This only works in the main text, I think (bottom > 0)
+ LASSERT(text_->isMainText(), return first());
+ auto it = find_if(par_metrics_.begin(), par_metrics_.end(),
+ [] (ParMetricsCache::value_type const & p) {
+ return p.second.hasPosition() && p.second.bottom() > 0;
+ });
+ return make_pair(it->first, &it->second);
+}
+
pair<pit_type, ParagraphMetrics const *> TextMetrics::last() const
{
LBUFERR(!par_metrics_.empty());
// do it and update its position.
redoParagraph(pit);
- par_metrics_[pit].setPosition(last.second.position()
- + last.second.descent() + par_metrics_[pit].ascent());
+ par_metrics_[pit].setPosition(last.second.bottom() + par_metrics_[pit].ascent());
updatePosCache(pit);
}
pit_type const pit = first.first - 1;
// do it and update its position.
redoParagraph(pit);
- par_metrics_[pit].setPosition(first.second.position()
- - first.second.ascent() - par_metrics_[pit].descent());
+ par_metrics_[pit].setPosition(first.second.top() - par_metrics_[pit].descent());
updatePosCache(pit);
}
+void TextMetrics::updateMetrics(pit_type const anchor_pit, int const anchor_ypos,
+ int const bv_height)
+{
+ LASSERT(text_->isMainText(), return);
+
+ // Forget existing positions
+ for (auto & pm_pair : par_metrics_)
+ pm_pair.second.resetPosition();
+
+ if (!contains(anchor_pit))
+ // Rebreak anchor paragraph.
+ redoParagraph(anchor_pit);
+ ParagraphMetrics & anchor_pm = parMetrics(anchor_pit);
+ anchor_pm.setPosition(anchor_ypos);
+
+ // Redo paragraphs above anchor if necessary.
+ int y1 = anchor_ypos - anchor_pm.ascent();
+ // We are now just above the anchor paragraph.
+ pit_type pit1 = anchor_pit - 1;
+ for (; pit1 >= 0 && y1 > 0; --pit1) {
+ if (!contains(pit1))
+ redoParagraph(pit1);
+ ParagraphMetrics & pm = parMetrics(pit1);
+ y1 -= pm.descent();
+ // Save the paragraph position in the cache.
+ pm.setPosition(y1);
+ y1 -= pm.ascent();
+ }
+
+ // Redo paragraphs below the anchor if necessary.
+ int y2 = anchor_ypos + anchor_pm.descent();
+ // We are now just below the anchor paragraph.
+ pit_type pit2 = anchor_pit + 1;
+ pit_type const npit = pit_type(text_->paragraphs().size());
+ for (; pit2 < npit && y2 < bv_height; ++pit2) {
+ if (!contains(pit2))
+ redoParagraph(pit2);
+ ParagraphMetrics & pm = parMetrics(pit2);
+ y2 += pm.ascent();
+ // Save the paragraph position in the cache.
+ pm.setPosition(y2);
+ y2 += pm.descent();
+ }
+
+ LYXERR(Debug::PAINTING, "TextMetrics::updateMetrics "
+ << " anchor pit = " << anchor_pit
+ << " anchor ypos = " << anchor_ypos
+ << " y1 = " << y1
+ << " y2 = " << y2
+ << " pit1 = " << pit1
+ << " pit2 = " << pit2);
+}
+
+
bool TextMetrics::metrics(MetricsInfo const & mi, Dimension & dim, int min_width)
{
LBUFERR(mi.base.textwidth > 0);
}
+// This is an arbitrary magic value
+static const pos_type force_label = -12345;
+
+// if pos == force_label, this function will return the label font instead.
+// This undocumented and only for use by labelDisplayFont()
Font TextMetrics::displayFont(pit_type pit, pos_type pos) const
{
- LASSERT(pos >= 0, { static Font f; return f; });
+ LASSERT(pos >= 0 || pos == force_label, { static Font f; return f; });
ParagraphList const & pars = text_->paragraphs();
Paragraph const & par = pars[pit];
Layout const & layout = par.layout();
Buffer const & buffer = bv_->buffer();
// FIXME: broken?
- BufferParams const & params = buffer.params();
- pos_type const body_pos = par.beginOfBody();
+ BufferParams const & bparams = buffer.params();
+ bool const label = pos < par.beginOfBody() || pos == force_label;
+
+ Font f = (pos == force_label) ? Font(inherit_font, bparams.language)
+ : par.getFontSettings(bparams, pos);
+ FontInfo const & lf = label ? layout.labelfont : layout.font;
// We specialize the 95% common case:
if (!par.getDepth()) {
- Font f = par.getFontSettings(params, pos);
if (!text_->isMainText())
applyOuterFont(f);
- bool lab = layout.labeltype == LABEL_MANUAL && pos < body_pos;
- FontInfo const & lf = lab ? layout.labelfont : layout.font;
- FontInfo rlf = lab ? layout.reslabelfont : layout.resfont;
+ FontInfo rlf = label ? layout.reslabelfont : layout.resfont;
// In case the default family has been customized
if (lf.family() == INHERIT_FAMILY)
- rlf.setFamily(params.getFont().fontInfo().family());
+ rlf.setFamily(bparams.getFont().fontInfo().family());
f.fontInfo().realize(rlf);
return f;
}
- // The uncommon case need not be optimized as much
- FontInfo const & layoutfont = pos < body_pos ?
- layout.labelfont : layout.font;
-
- Font font = par.getFontSettings(params, pos);
- font.fontInfo().realize(layoutfont);
+ // The uncommon case need not be optimized as much.
+ // FIXME : the two code paths seem different in function.
+ f.fontInfo().realize(lf);
if (!text_->isMainText())
- applyOuterFont(font);
+ applyOuterFont(f);
// Realize against environment font information
// NOTE: the cast to pit_type should be removed when pit_type
// changes to a unsigned integer.
if (pit < pit_type(pars.size()))
- font.fontInfo().realize(text_->outerFont(pit).fontInfo());
+ f.fontInfo().realize(text_->outerFont(pit).fontInfo());
// Realize with the fonts of lesser depth.
- font.fontInfo().realize(params.getFont().fontInfo());
+ f.fontInfo().realize(bparams.getFont().fontInfo());
- return font;
+ return f;
+}
+
+
+Font TextMetrics::labelDisplayFont(pit_type pit) const
+{
+ return displayFont(pit, force_label);
}
par.setBeginOfBody();
Font const bufferfont = buffer.params().getFont();
CoordCache::Insets & insetCache = bv_->coordCache().insets();
+ map <Inset const *, int> extrawidths;
for (auto const & e : par.insetList()) {
// FIXME Doesn't this HAVE to be non-empty?
// position already initialized?
// If there is an end of paragraph marker, its size should be
// substracted to the available width. The logic here is
- // almost the same as in breakRow, remember keep them in sync.
+ // almost the same as in tokenizeParagraph, remember keep them in sync.
int eop = 0;
if (e.pos + 1 == par.size()
&& (lyxrc.paragraph_markers || par.lookupChange(par.size()).changed())
displayFont(pit, e.pos) : bufferfont;
MacroContext mc(&buffer, parPos);
MetricsInfo mi(bv_, font.fontInfo(), w, mc, e.pos == 0, tight_);
+ mi.base.outer_font = displayFont(pit, e.pos).fontInfo();
e.inset->metrics(mi, dim);
+ /* FIXME: This is a hack. This allows InsetMathHull to state
+ * that it needs some elbow room beyond its width, in order to
+ * fit the numbering and/or the left margin (with left
+ * alignment), which are outside of the inset itself.
+ *
+ * To this end, InsetMathHull::metrics() sets a value in
+ * MetricsInfo::extrawidth and this value is added later to
+ * the width of the row that contains the inset (when this row
+ * is tight or shorter than the max allowed width).
+ *
+ * See ticket #12320 for details.
+ */
+ extrawidths[e.inset] = mi.extrawidth;
+
if (!insetCache.has(e.inset) || insetCache.dim(e.inset) != dim) {
insetCache.add(e.inset, dim);
changed = true;
// Split the row in several rows fitting in available width
pm.rows() = breakParagraph(bigrow);
+ // Add the needed extra width to the rows that contain the insets that request it
+ for (Row & row : pm.rows())
+ for (Row::Element & e : row)
+ if (e.type == Row::INSET && (row.width() < max_width_ || tight_))
+ row.dim().wid += extrawidths[e.inset];
+
/* If there is more than one row, expand the text to the full
* allowable width. This setting here is needed for the
* setRowAlignment() below. We do nothing when tight insets are
if (Inset const * inset = par.getInset(row.pos())) {
if (inset->rowFlags() & Display) {
if (inset->rowFlags() & AlignLeft)
- align = LYX_ALIGN_BLOCK;
+ align = LYX_ALIGN_LEFT;
else if (inset->rowFlags() & AlignRight)
align = LYX_ALIGN_RIGHT;
else
}
-int TextMetrics::labelEnd(pit_type const pit) const
-{
- // labelEnd is only needed if the layout fills a flushleft label.
- if (text_->getPar(pit).layout().margintype != MARGIN_MANUAL)
- return 0;
- // return the beginning of the body
- return leftMargin(pit);
-}
-
namespace {
/**
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
+ // This space is an \item separator. Represent it with a
+ // special space element, which dimension will be computed
+ // in breakRow.
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));
+ int const wid = fm.width(par.layout().labelsep);
+ row.addMarginSpace(i, wid, *fi, par.lookupChange(i));
} else if (c == '\t')
row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")),
*fi, par.lookupChange(i));
* U+2029 PARAGRAPH SEPARATOR
* These are special unicode characters that break
- * lines/pragraphs. Not handling them lead to trouble wrt
+ * lines/pragraphs. Not handling them leads to trouble wrt
* Qt QTextLayout formatting. We add a visible character
* on screen so that the user can see that something is
* happening.
// ⤶ 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);
+ row.add(i, screen_char, *fi, par.lookupChange(i));
} else
// row elements before body are unbreakable
- row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
+ row.add(i, c, *fi, par.lookupChange(i));
// add inline completion width
// draw logically behind the previous character
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;
-}
-
-
/** Helper template flexible_const_iterator<T>
* A way to iterate over a const container, but insert fake elements in it.
* In the case of a row, we will have to break some elements, which
public:
//
- flexible_const_iterator operator++() {
+ flexible_const_iterator & operator++() {
if (pile_.empty())
++cit_;
else
value_type operator*() const { return pile_.empty() ? *cit_ : pile_.back(); }
+ value_type const * operator->() const { return pile_.empty() ? &*cit_ : &pile_.back(); }
+
void put(value_type const & e) { pile_.push_back(e); }
+ // Put a sequence of elements on the pile (in reverse order!)
+ void put(vector<value_type> const & elts) {
+ pile_.insert(pile_.end(), elts.rbegin(), elts.rend());
+ }
+
// This should be private, but declaring the friend functions is too much work
//private:
typename T::const_iterator cit_;
return t1.cit_ == t2.cit_ && t1.pile_.empty() && t2.pile_.empty();
}
+
+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);
+ nrow.setRTL(is_rtl);
+ 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;
+}
+
+
+void cleanupRow(Row & row, bool at_end)
+{
+ if (row.empty()) {
+ row.endpos(row.pos());
+ return;
+ }
+
+ row.endpos(row.back().endpos);
+ // remove trailing spaces on row break
+ if (!at_end && !row.flushed())
+ row.back().rtrim();
+ // boundary exists when there was no space at the end of row
+ row.end_boundary(!at_end && row.back().endpos == row.endpos());
+ // make sure that the RTL elements are in reverse ordering
+ row.reverseRTL();
+}
+
+
+// Implement the priorities described in RowFlags.h.
+bool needsRowBreak(int f1, int f2)
+{
+ if (f1 & AlwaysBreakAfter /*|| f2 & AlwaysBreakBefore*/)
+ return true;
+ if (f1 & NoBreakAfter || f2 & NoBreakBefore)
+ return false;
+ if (f1 & BreakAfter || f2 & BreakBefore)
+ return true;
+ return false;
+}
+
+
}
{
RowList rows;
bool const is_rtl = text_->isRTL(bigrow.pit());
+ bool const end_label = text_->getEndLabel(bigrow.pit()) != END_LABEL_NO_LABEL;
+ int const next_width = max_width_ - leftMargin(bigrow.pit(), bigrow.endpos())
+ - rightMargin(bigrow.pit());
- bool need_new_row = true;
- pos_type pos = 0;
int width = 0;
flexible_const_iterator<Row> fcit = flexible_begin(bigrow);
flexible_const_iterator<Row> const end = flexible_end(bigrow);
while (true) {
- if (need_new_row) {
+ bool const row_empty = rows.empty() || rows.back().empty();
+ // The row flags of previous element, if there is one.
+ // Otherwise we use NoBreakAfter to avoid an empty row before
+ // e.g. a displayed equation.
+ int const f1 = row_empty ? NoBreakAfter : rows.back().back().row_flags;
+ // The row flags of next element, if there is one.
+ // Otherwise we use NoBreakBefore (see above), unless the
+ // paragraph has an end label (for which an empty row is OK).
+ int const f2 = (fcit == end) ? (end_label ? Inline : NoBreakBefore)
+ : fcit->row_flags;
+ if (rows.empty() || needsRowBreak(f1, f2)) {
if (!rows.empty()) {
- Row & rb = rows.back();
- rb.endpos(pos);
- rb.right_boundary(!rb.empty() && rb.endpos() < bigrow.endpos()
- && rb.back().endpos == rb.endpos());
- // make sure that the RTL elements are in reverse ordering
- rb.reverseRTL(is_rtl);
+ // Flush row as requested by row flags
+ rows.back().flushed((f1 & Flush) || (f2 & FlushBefore));
+ cleanupRow(rows.back(), false);
}
+ pos_type pos = rows.empty() ? 0 : rows.back().endpos();
rows.push_back(newRow(*this, bigrow.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
// Next element to consider is either the top of the temporary
// pile, or the place when we were in main row
Row::Element elt = *fcit;
- Row::Element next_elt = elt.splitAt(width - rows.back().width(),
- !elt.font.language()->wordWrap());
- // a new element in the row
- rows.back().push_back(elt);
- pos = elt.endpos;
+ Row::Elements tail;
+ elt.splitAt(width - rows.back().width(), next_width, Row::FIT, tail);
+ Row & rb = rows.back();
+ if (elt.type == Row::MARGINSPACE)
+ elt.dim.wid = max(elt.dim.wid, leftMargin(bigrow.pit()) - rb.width());
+ rb.push_back(elt);
+ rb.finalizeLast();
+ if (rb.width() > width) {
+ // Keep the tail for later; this ought to be rare, but play safe.
+ if (!tail.empty())
+ fcit.put(tail);
+ // if the row is too large, try to cut at last separator.
+ tail = rb.shortenIfNeeded(width, next_width);
+ }
// Go to next element
++fcit;
- // Add a new next element on the pile
- if (next_elt.isValid()) {
- // do as if we inserted this element in the original row
- fcit.put(next_elt);
- need_new_row = true;
- }
+ // Handle later the elements returned by splitAt or shortenIfNeeded.
+ fcit.put(tail);
}
if (!rows.empty()) {
- Row & rb = rows.back();
// Last row in paragraph is flushed
- rb.flushed(true);
- rb.endpos(bigrow.endpos());
- rb.right_boundary(false);
- // make sure that the RTL elements are in reverse ordering
- rb.reverseRTL(is_rtl);
+ rows.back().flushed(true);
+ cleanupRow(rows.back(), true);
+ // Is there an end-of-paragraph change?
+ if (bigrow.needsChangeBar())
+ rows.back().needsChangeBar(true);
}
return rows;
}
-/** This is the function where the hard work is done. The code here is
- * very sensitive to small changes :) Note that part of the
- * intelligence is also in Row::shortenIfNeeded.
- */
-bool TextMetrics::breakRow(Row & row, int const right_margin) const
-{
- LATTEST(row.empty());//
- Paragraph const & par = text_->getPar(row.pit());//
- Buffer const & buf = text_->inset().buffer();//
- BookmarksSection::BookmarkPosList bpl =//
- theSession().bookmarks().bookmarksInPar(buf.fileName(), par.id());//
-
- pos_type const end = par.size();//
- pos_type const pos = row.pos();//
- pos_type const body_pos = par.beginOfBody();//
- bool const is_rtl = text_->isRTL(row.pit());//
- bool need_new_row = false;//
-
- row.left_margin = leftMargin(row.pit(), pos);//
- row.right_margin = right_margin;//
- if (is_rtl)//
- swap(row.left_margin, row.right_margin);//
- // Remember that the row width takes into account the left_margin
- // but not the right_margin.
- row.dim().wid = row.left_margin;//
- // the width available for the row.
- 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();//
-
- // 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;//---------------------------------------------------vvv
- FontIterator fi = FontIterator(*this, par, row.pit(), pos);
- // The real stopping condition is a few lines below.
- while (true) {
- // Firstly, check whether there is a bookmark here.
- 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)) {
- 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(row.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.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());
- }//---------------------------------------------------------------^^^
-
- // 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() & BreakBefore)
- || (prevInset && prevInset->rowFlags() & BreakAfter)) {
- row.flushed(true);
- // 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() & AlwaysBreakAfter
- || (prevInset->rowFlags() & BreakAfter && i + 1 == end
- && text_->getEndLabel(row.pit()) != END_LABEL_NO_LABEL));
- ++i;
- break;
- }
-
- ++i;
- ++fi;
- }
- //--------------------------------------------------------------------vvv
- row.finalizeLast();
- row.endpos(i);
-
- // 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())
- && !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);
- f.setLanguage(par.getParLanguage(buf.params()));
- // ¶ U+00B6 PILCROW SIGN
- row.addVirtual(end, docstring(1, char_type(0x00B6)), f, change);
- }
-
- // 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(width, next_width))
- row.flushed(false);
- row.right_boundary(!row.empty() && row.endpos() < end//
- && row.back().endpos == row.endpos());//
- // Last row in paragraph is flushed
- if (row.endpos() == end)//
- row.flushed(true);//
-
- // make sure that the RTL elements are in reverse ordering
- row.reverseRTL(is_rtl);//
- //LYXERR0("breakrow: row is " << row);
-
- return need_new_row;
-}
int TextMetrics::parTopSpacing(pit_type const pit) const
{
&& prevpar.getLabelWidthString() == par.getLabelWidthString()) {
layoutasc = layout.itemsep * dh;
} else if (pit != 0 && layout.topsep > 0)
- layoutasc = layout.topsep * dh;
+ // combine the separation between different layouts (with same depth)
+ layoutasc = max(0.0,
+ prevpar.getDepth() != par.getDepth() ? layout.topsep
+ : layout.topsep - prevpar.layout().bottomsep) * dh;
asc += int(layoutasc * 2 / (2 + pars[pit].getDepth()));
{
Paragraph const & par = text_->getPar(row.pit());
Layout const & layout = par.layout();
- double const spacing_val = layout.spacing.getValue() * text_->spacing(par);
+ // leading space (line spacing) factor based on current paragraph
+ double spacing_val = layout.spacing.getValue() * text_->spacing(par);
+
+ // if this is the first row but not the first paragraph, take into
+ // account the spacing of the previous paragraph.
+ if (row.pos() == 0 && row.pit() > 0) {
+ // for the first row in the paragraph,
+ // use previous paragraphs line spacing if it is larger
+ Paragraph const & previousPar = text_->getPar(row.pit() - 1);
+ Layout const & previousLayout = previousPar.layout();
+ // leading space factor based on previous paragraph
+ double const previous_spacing_val
+ = previousLayout.spacing.getValue() * text_->spacing(previousPar);
+ if (previous_spacing_val > spacing_val)
+ spacing_val = previous_spacing_val;
+ }
// Initial value for ascent (useful if row is empty).
Font const font = displayFont(row.pit(), row.pos());
FontMetrics const & fm = theFontMetrics(font);
- int maxasc = int(fm.maxAscent() * spacing_val);
- int maxdes = int(fm.maxDescent() * spacing_val);
+ int maxasc = int(fm.maxAscent()
+ // add leading space
+ + fm.maxHeight() * (spacing_val - 1));
+ int maxdes = int(fm.maxDescent());
// 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));
+ maxasc = max(maxasc, int(lfm.maxAscent()
+ // add leading space
+ + lfm.maxHeight() * (spacing_val - 1)));
+ maxdes = max(maxdes, int(lfm.maxDescent()));
}
// Find the ascent/descent of the row contents
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));
+ maxasc = max(maxasc, int(fm2.maxAscent()
+ // add leading space
+ + fm2.maxHeight() * (spacing_val - 1)));
+ maxdes = max(maxdes, int(fm2.maxDescent()));
}
}
|| inset->lyxCode() == SEPARATOR_CODE))
pos = row.back().pos;
else
- boundary = row.right_boundary();
+ boundary = row.end_boundary();
}
x += xo - offset;
{
LASSERT(!text_->paragraphs().empty(), return -1);
LASSERT(!par_metrics_.empty(), return -1);
- LYXERR(Debug::DEBUG, "y: " << y << " cache size: " << par_metrics_.size());
+ LYXERR(Debug::PAINTING, "y: " << y << " cache size: " << par_metrics_.size());
// look for highest numbered paragraph with y coordinate less than given y
pit_type pit = -1;
ParMetricsCache::const_iterator last = et;
--last;
- ParagraphMetrics const & pm = it->second;
-
- if (y < it->second.position() - pm.ascent()) {
+ if (y < it->second.top()) {
// 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.
return pit;
}
- ParagraphMetrics const & pm_last = par_metrics_[last->first];
-
- if (y >= last->second.position() + pm_last.descent()) {
+ if (y >= par_metrics_[last->first].bottom()) {
// 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.
}
for (; it != et; ++it) {
- LYXERR(Debug::DEBUG, "examining: pit: " << it->first
- << " y: " << it->second.position());
-
- ParagraphMetrics const & pm2 = par_metrics_[it->first];
+ // LYXERR(Debug::PAINTING, "examining: pit: " << it->first
+ // << " y: " << it->second.position());
- if (it->first >= pit && it->second.position() - pm2.ascent() <= y) {
+ if (it->first >= pit && it->second.top() <= y) {
pit = it->first;
yy = it->second.position();
}
}
- LYXERR(Debug::DEBUG, "found best y: " << yy << " for pit: " << pit);
+ LYXERR(Debug::PAINTING, "found best y: " << yy << " for pit: " << pit);
return pit;
}
{
ParagraphMetrics const & pm = par_metrics_[pit];
- int yy = pm.position() - pm.ascent();
+ int yy = pm.top();
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.rows().front().ascent();
- LYXERR(Debug::DEBUG, "x: " << x << " y: " << y <<
+ LYXERR(Debug::PAINTING, "x: " << x << " y: " << y <<
" pit: " << pit << " yy: " << yy);
int r = 0;
Row const & row = pm.rows()[r];
- LYXERR(Debug::DEBUG, "row " << r << " from pos: " << row.pos());
+ LYXERR(Debug::PAINTING, "row " << r << " from pos: " << row.pos());
bool bound = false;
int xx = x;
pos_type const pos = getPosNearX(row, xx, bound);
- LYXERR(Debug::DEBUG, "setting cursor pit: " << pit << " pos: " << pos);
+ LYXERR(Debug::PAINTING, "setting cursor pit: " << pit << " pos: " << pos);
text_->setCursor(cur, pit, pos, true, bound);
// remember new position.
Paragraph const & par = text_->paragraphs()[pit];
CoordCache::Insets const & insetCache = bv_->coordCache().getInsets();
- LYXERR(Debug::DEBUG, "x: " << x << " y: " << y << " pit: " << pit);
+ LYXERR(Debug::PAINTING, "x: " << x << " y: " << y << " pit: " << pit);
for (InsetList::Element const & e : par.insetList()) {
- LYXERR(Debug::DEBUG, "examining inset " << e.inset);
+ LYXERR(Debug::PAINTING, "examining inset " << e.inset);
if (insetCache.covers(e.inset, x, y)) {
- LYXERR(Debug::DEBUG, "Hit inset: " << e.inset);
+ LYXERR(Debug::PAINTING, "Hit inset: " << e.inset);
return const_cast<InsetList::Element *>(&e);
}
}
- LYXERR(Debug::DEBUG, "No inset hit. ");
+ LYXERR(Debug::PAINTING, "No inset hit. ");
return nullptr;
}
}
}
- // This happens after sections or environments in standard classes.
- // We have to check the previous layout at same depth.
+ // Check for reasons to remove indentation.
+ // First, at document level.
if (buffer.params().paragraph_separation ==
BufferParams::ParagraphSkipSeparation)
parindent.erase();
+ // This happens after sections or environments in standard classes.
+ // We have to check the previous layout at same depth.
else if (pit > 0 && pars[pit - 1].getDepth() >= par.getDepth()) {
pit_type prev = text_->depthHook(pit, par.getDepth());
if (par.layout() == pars[prev].layout()) {
} else if (pars[prev].layout().nextnoindent)
parindent.erase();
}
+ // The previous paragraph may have ended with a separator inset.
+ if (pit > 0) {
+ Paragraph const & ppar = pars[pit - 1];
+ if (ppar.size() > 0) {
+ auto * in = dynamic_cast<InsetSeparator const *>(ppar.getInset(ppar.size() - 1));
+ if (in != nullptr && in->nextnoindent())
+ parindent.erase();
+ }
+ }
FontInfo const labelfont = text_->labelFont(par);
FontMetrics const & lfm = theFontMetrics(labelfont);
if (pm.rows().empty())
return;
size_t const nrows = pm.rows().size();
+ int const wh = bv_->workHeight();
// 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));
+ Changer changeleft, changeright;
+ if (text_->isRTL(pit)) {
+ changeleft = changeVar(pi.leftx, x + rightMargin(pit));
+ changeright = changeVar(pi.rightx, x + width() - leftMargin(pit));
+ } else {
+ changeleft = changeVar(pi.leftx, x + leftMargin(pit));
+ changeright = changeVar(pi.rightx, x + width() - rightMargin(pit));
+ }
// Use fast lane in nodraw stage.
if (pi.pain.isNull()) {
if (i)
y += row.ascent();
- RowPainter rp(pi, *text_, row, row_x, y);
-
- rp.paintOnlyInsets();
+ // It is not needed to draw on screen if we are not inside
+ bool const inside = (y + row.descent() >= 0 && y - row.ascent() < wh);
+ if (inside) {
+ RowPainter rp(pi, *text_, row, row_x, y);
+ rp.paintOnlyInsets();
+ }
y += row.descent();
}
return;
}
- int const ww = bv_->workHeight();
Cursor const & cur = bv_->cursor();
DocIterator sel_beg = cur.selectionBegin();
DocIterator sel_end = cur.selectionEnd();
}
}
- if (text_->isRTL(pit))
- swap(pi.leftx, pi.rightx);
-
BookmarksSection::BookmarkPosList bpl =
- theSession().bookmarks().bookmarksInPar(bv_->buffer().fileName(), pm.par().id());
+ theSession().bookmarks().bookmarksInPar(bv_->buffer().fileName(), pm.id());
for (size_t i = 0; i != nrows; ++i) {
// It is not needed to draw on screen if we are not inside.
bool const inside = (y + row.descent() >= 0
- && y - row.ascent() < ww);
+ && y - row.ascent() < wh);
if (!inside) {
// Inset positions have already been set in nodraw stage.
y += row.descent();
// already cleared because of a full repaint.
if (!pi.full_repaint && row.changed()) {
LYXERR(Debug::PAINTING, "Clear rect@("
- << max(row_x, 0) << ", " << y - row.ascent() << ")="
+ << x << ", " << y - row.ascent() << ")="
<< width() << " x " << row.height());
- pi.pain.fillRectangle(row_x, y - row.ascent(),
+ pi.pain.fillRectangle(x, y - row.ascent(),
width(), row.height(), pi.background_color);
}