Insert a real empty row before display math at start of paragraph

In LaTeX, when a displayed equation is at the start of a paragraph,
there is an empty row in front of it. Up to now, this was mimicked in
LyX by increasing the metrics on top of the inset. This commit creates
a real empty row, accessible by the cursor.

To make this work, many small unrelated changes are needed.

* Introduce new AlwaysBreakBefore inset row flag that means "I want a
  break before myself, even if that means creating an empty row".

* Let InsetMathHull use that for display math.

* Remove the workaround that was added for InsetMathHull metrics. This
  means that MetricsInfo::vmode is not used anymore. I decided to keep it,
  since it may prove useful later.

* Handle the flag in TextMetrics::breakParagraph. This requires to add
  a new flag 'ignore_contents' to TextMetrics::leftMargin, because we
  want the empty row to have a normal left indentation, not the one of
  display math (which is also at pos==0).

* In the initial empty row, do not inherit from the centered alignment
  of the math inset, although both are at position 0.

* Concerning cursor positioning with mouse, two methods need fixing:

  For the vertical part, handle in TextMetrics::getRowIndex the cursor
  boundary at position 0 when it is set. Basically, with cursor
  boundary true, the cursor will be in the empty row, whereas it will
  be in font of the math inset otherwise.

  For the horizontal part, handle empty row in TextMetrics::getPosNearX.

Fixes bugs 11593 and 11093.
This commit is contained in:
Jean-Marc Lasgouttes 2024-11-22 14:09:39 +01:00
parent 6727022b05
commit 33442b17ee
6 changed files with 42 additions and 25 deletions

View File

@ -105,6 +105,7 @@ public:
/// The context to resolve macros
MacroContext const & macrocontext;
/// Are we at the start of a paragraph (vertical mode)?
/// This is not used anymore, but could be useful
bool vmode;
/// if true, do not expand insets to max width artificially
bool tight_insets;

View File

@ -95,6 +95,10 @@ size_t ParagraphMetrics::getRowIndex(pos_type pos, bool boundary) const
{
LBUFERR(!rows().empty());
// This makes a difference when the first row is empty (e.g. before display math)
if (pos == 0 && boundary)
return 0;
// If boundary is set we should return the row on which
// the character before is inside.
if (pos > 0 && boundary)

View File

@ -27,30 +27,32 @@ enum RowFlags {
// Do not break before or after this element, except if really
// needed (between NoBreak* and CanBreak*).
Inline = 0,
// force (maybe empty) row before this element
AlwaysBreakBefore = 1 << 0,
// break row before this element if the row is not empty
BreakBefore = 1 << 0,
BreakBefore = 1 << 1,
// break row whenever needed before this element
CanBreakBefore = 1 << 1,
CanBreakBefore = 1 << 2,
// Avoid breaking row before this element
NoBreakBefore = 1 << 2,
NoBreakBefore = 1 << 3,
// flush the row before this element (useful with BreakBefore)
FlushBefore = 1 << 3,
FlushBefore = 1 << 4,
// force new (maybe empty) row after this element
AlwaysBreakAfter = 1 << 4,
AlwaysBreakAfter = 1 << 5,
// break row after this element if there are more elements
BreakAfter = 1 << 5,
BreakAfter = 1 << 6,
// break row whenever needed after this element
CanBreakAfter = 1 << 6,
CanBreakAfter = 1 << 7,
// Avoid breaking row after this element
NoBreakAfter = 1 << 7,
NoBreakAfter = 1 << 8,
// The contents of the row may be broken in two (e.g. string)
CanBreakInside = 1 << 8,
CanBreakInside = 1 << 9,
// Flush the row that ends with this element
Flush = 1 << 9,
Flush = 1 << 10,
// specify an alignment (left, right) for a display element
// (default is center)
AlignLeft = 1 << 10,
AlignRight = 1 << 11,
AlignLeft = 1 << 11,
AlignRight = 1 << 12,
// A display element breaks row at both ends
Display = FlushBefore | BreakBefore | BreakAfter,
// Flags that concern breaking after element

View File

@ -706,7 +706,8 @@ 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() & Display) {
// If we are in empty row, alignment of inset does not apply (it is in next row)
if (!row.empty() && inset->rowFlags() & Display) {
if (inset->rowFlags() & AlignLeft)
align = LYX_ALIGN_LEFT;
else if (inset->rowFlags() & AlignRight)
@ -1155,7 +1156,7 @@ void cleanupRow(Row & row, bool at_end)
// Implement the priorities described in RowFlags.h.
bool needsRowBreak(int f1, int f2)
{
if (f1 & AlwaysBreakAfter /*|| f2 & AlwaysBreakBefore*/)
if (f1 & AlwaysBreakAfter || f2 & AlwaysBreakBefore)
return true;
if (f1 & NoBreakAfter || f2 & NoBreakBefore)
return false;
@ -1183,13 +1184,21 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
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.
// e.g. a displayed inset.
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)) {
// Create an empty row before element
rows.push_back(newRow(*this, bigrow.pit(), 0, is_rtl));
Row & newrow = rows.back();
cleanupRow(newrow, false);
newrow.end_boundary(true);
newrow.left_margin = leftMargin(newrow.pit(), 0, true);
}
if (rows.empty() || needsRowBreak(f1, f2)) {
if (!rows.empty()) {
// Flush row as requested by row flags
@ -1468,14 +1477,15 @@ pos_type TextMetrics::getPosNearX(Row const & row, int & x,
boundary = true;
}
if (row.empty())
boundary = row.end_boundary();
/** This tests for the case where the cursor is set at the end
* of a row which has been broken due something else than a
* separator (a display inset or a forced breaking of the
* row). We know that there is a separator when the end of the
* row is larger than the end of its last element.
*/
if (!row.empty() && pos == row.back().endpos
&& row.back().endpos == row.endpos()) {
else if (pos == row.back().endpos && row.back().endpos == row.endpos()) {
Inset const * inset = row.back().inset;
if (inset && (inset->lyxCode() == NEWLINE_CODE
|| inset->lyxCode() == SEPARATOR_CODE))
@ -1838,7 +1848,7 @@ int TextMetrics::leftMargin(pit_type pit) const
}
int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const
int TextMetrics::leftMargin(pit_type const pit, pos_type const pos, bool ignore_contents) const
{
ParagraphList const & pars = text_->paragraphs();
@ -1998,7 +2008,8 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const
// in some insets, paragraphs are never indented
&& !text_->inset().neverIndent()
// display style insets do not need indentation
&& !(!par.empty()
&& !(!ignore_contents
&& !par.empty()
&& par.isInset(0)
&& par.getInset(0)->rowFlags() & Display)
&& (!(tclass.isDefaultLayout(par.layout())

View File

@ -138,8 +138,10 @@ public:
* This information cannot be taken from the layout object, because
* in LaTeX the beginning of the text fits in some cases
* (for example sections) exactly the label-width.
* When \c ignore_contents is true, alignment properties related
* to insets in paragraph are not taken into account.
*/
int leftMargin(pit_type pit, pos_type pos) const;
int leftMargin(pit_type pit, pos_type pos, bool ignore_contents = false) const;
/// Return the left beginning of a row which is not the first one.
/// This is the left margin when there is no indentation.
int leftMargin(pit_type pit) const;

View File

@ -525,9 +525,6 @@ void InsetMathHull::metrics(MetricsInfo & mi, Dimension & dim) const
*/
int const bottom_display_margin = mi.base.bv->zoomedPixels(6);
int top_display_margin = bottom_display_margin;
// at start of paragraph, add an empty line
if (mi.vmode)
top_display_margin += theFontMetrics(mi.base.font).maxHeight() + 2;
int const ind = indent(*mi.base.bv);
mi.extrawidth = ind;
@ -1029,9 +1026,9 @@ int InsetMathHull::rowFlags() const
case hullMultline:
case hullGather:
if (buffer().params().is_math_indent)
return Display | AlignLeft;
return AlwaysBreakBefore | Display | AlignLeft;
else
return Display;
return AlwaysBreakBefore | Display;
}
// avoid warning
return Display;