From 33442b17ee15da37c1087d6a4f1f9884ff8531f7 Mon Sep 17 00:00:00 2001 From: Jean-Marc Lasgouttes Date: Fri, 22 Nov 2024 14:09:39 +0100 Subject: [PATCH] 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. --- src/MetricsInfo.h | 1 + src/ParagraphMetrics.cpp | 4 ++++ src/RowFlags.h | 26 ++++++++++++++------------ src/TextMetrics.cpp | 25 ++++++++++++++++++------- src/TextMetrics.h | 4 +++- src/mathed/InsetMathHull.cpp | 7 ++----- 6 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/MetricsInfo.h b/src/MetricsInfo.h index 48d313ba71..3732e17481 100644 --- a/src/MetricsInfo.h +++ b/src/MetricsInfo.h @@ -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; diff --git a/src/ParagraphMetrics.cpp b/src/ParagraphMetrics.cpp index 470c765350..b6058a897e 100644 --- a/src/ParagraphMetrics.cpp +++ b/src/ParagraphMetrics.cpp @@ -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) diff --git a/src/RowFlags.h b/src/RowFlags.h index 953d7f92ee..ccc7f5b46c 100644 --- a/src/RowFlags.h +++ b/src/RowFlags.h @@ -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 diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index c5f2bf498a..84e43c1d7d 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -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()) diff --git a/src/TextMetrics.h b/src/TextMetrics.h index d5606b610a..02b27cf067 100644 --- a/src/TextMetrics.h +++ b/src/TextMetrics.h @@ -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; diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp index de20771e38..32a0d81bf0 100644 --- a/src/mathed/InsetMathHull.cpp +++ b/src/mathed/InsetMathHull.cpp @@ -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;