commit bf56e2c8e1afa857cd5e313c19948040e41b8227
Author: Jean-Marc Lasgouttes <[email protected]>
Date:   Wed Nov 16 15:07:00 2016 +0100

    Set correctly the spacing between atoms in MathData
    
    * new MathRow class which contains the description of a MathData
      object in terms of math class and spacing
    
      + macros and their arguments used in the MathData object are
        linearized (replaced with their contents) so that all math insets
        are typeset as a string together. To this end, we introduce a
        method addToMathRow to InsetMath and MathData. This method allows
        to linearize recursively a MathData object.
    
      + It is then necessary to set manually the dimension and position of
        the macros and arguments.
    
      + the class class and spacing are computed using the MathClass helpers.
    
      The MathRow data is cached in the MathData object in a 
bufferview-dependent
      way (different dpi for different screens).
    
    * delegate most of the work MathData::metrics/draw to MathRow metrics/draw.
    
      The case of draw is trickier, since many draw() methods rely on their
      metrics without any spacing added.
---
 src/Makefile.am          |    2 +
 src/mathed/InsetMath.cpp |   11 ++
 src/mathed/InsetMath.h   |    8 +-
 src/mathed/MathData.cpp  |  116 ++++++-------------
 src/mathed/MathData.h    |   10 ++
 src/mathed/MathMacro.cpp |   51 ++++++++-
 src/mathed/MathMacro.h   |   12 ++-
 src/mathed/MathRow.cpp   |  292 ++++++++++++++++++++++++++++++++++++++++++++++
 src/mathed/MathRow.h     |  137 ++++++++++++++++++++++
 9 files changed, 553 insertions(+), 86 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index fc135af..5990df7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -458,6 +458,7 @@ SOURCEFILESMATHED = \
        mathed/MathMacroArgument.cpp \
        mathed/MathMacroTemplate.cpp \
        mathed/MathParser.cpp \
+       mathed/MathRow.cpp \
        mathed/MathStream.cpp \
        mathed/MathSupport.cpp \
        mathed/TextPainter.cpp
@@ -530,6 +531,7 @@ HEADERFILESMATHED = \
        mathed/MathMacroTemplate.h \
        mathed/MathParser.h \
        mathed/MathParser_flags.h \
+       mathed/MathRow.h \
        mathed/ReplaceData.h \
        mathed/MathStream.h \
        mathed/MathSupport.h \
diff --git a/src/mathed/InsetMath.cpp b/src/mathed/InsetMath.cpp
index aec53d0..a8642c1 100644
--- a/src/mathed/InsetMath.cpp
+++ b/src/mathed/InsetMath.cpp
@@ -13,6 +13,7 @@
 
 #include "InsetMath.h"
 #include "MathData.h"
+#include "MathRow.h"
 #include "MathStream.h"
 
 #include "support/debug.h"
@@ -55,6 +56,16 @@ MathClass InsetMath::mathClass() const
 }
 
 
+bool InsetMath::addToMathRow(MathRow & mrow, MetricsInfo const &) const
+{
+       MathRow::Element e;
+       e.inset = this;
+       e.mclass = mathClass();
+       mrow.push_back(e);
+       return true;
+}
+
+
 void InsetMath::dump() const
 {
        lyxerr << "---------------------------------------------" << endl;
diff --git a/src/mathed/InsetMath.h b/src/mathed/InsetMath.h
index e791099..3949010 100644
--- a/src/mathed/InsetMath.h
+++ b/src/mathed/InsetMath.h
@@ -53,7 +53,10 @@ inclusion in the "real LyX insets" FormulaInset and 
FormulaMacroInset.
 
 */
 
+class Cursor;
 class OutputParams;
+class MetricsInfo;
+
 class InsetMathArray;
 class InsetMathAMSArray;
 class InsetMathBrace;
@@ -72,7 +75,6 @@ class InsetMathSpace;
 class InsetMathSpecialChar;
 class InsetMathSymbol;
 class InsetMathUnknown;
-
 class InsetMathRef;
 
 class HtmlStream;
@@ -87,7 +89,7 @@ class WriteStream;
 class MathData;
 class MathMacroTemplate;
 class MathMacro;
-class Cursor;
+class MathRow;
 class TextPainter;
 class TextMetricsInfo;
 class ReplaceData;
@@ -164,6 +166,8 @@ public:
 
        /// The class of the math object (used primarily for spacing)
        virtual MathClass mathClass() const;
+       /// Add this inset to a math row. Return true if contents got added
+       virtual bool addToMathRow(MathRow &, MetricsInfo const & mi) const;
 
        /// identifies things that can get scripts
        virtual bool isScriptable() const { return false; }
diff --git a/src/mathed/MathData.cpp b/src/mathed/MathData.cpp
index bd4d53e..8b7a89d 100644
--- a/src/mathed/MathData.cpp
+++ b/src/mathed/MathData.cpp
@@ -30,12 +30,11 @@
 
 #include "mathed/InsetMathUnknown.h"
 
-#include "support/debug.h"
-#include "support/docstream.h"
-
 #include "frontends/FontMetrics.h"
 #include "frontends/Painter.h"
 
+#include "support/debug.h"
+#include "support/docstream.h"
 #include "support/gettext.h"
 #include "support/lassert.h"
 #include "support/lyxalgo.h"
@@ -218,6 +217,34 @@ void MathData::touch() const
 }
 
 
+bool MathData::addToMathRow(MathRow & mrow, MetricsInfo const & mi) const
+{
+       bool has_contents = false;
+       BufferView * bv = mi.base.bv;
+       MathData * ar = const_cast<MathData*>(this);
+       ar->updateMacros(&bv->cursor(), mi.macrocontext,
+                        InternalUpdate);
+
+       // FIXME: for completion, try to insert the relevant data in the
+       // mathrow (like is done for text rows). We could add a pair of
+       // InsetMathColor inset, but these come with extra spacing of
+       // their own.
+       DocIterator const & inlineCompletionPos = bv->inlineCompletionPos();
+       bool const has_completion = inlineCompletionPos.inMathed()
+               && &inlineCompletionPos.cell() == this;
+       size_t const compl_pos = has_completion ? inlineCompletionPos.pos() : 0;
+
+       for (size_t i = 0 ; i < size() ; ++i) {
+               has_contents |= (*this)[i]->addToMathRow(mrow, mi);
+               if (i + 1 == compl_pos) {
+                       mrow.back().compl_text = bv->inlineCompletion();
+                       mrow.back().compl_unique_to = 
bv->inlineCompletionUniqueChars();
+               }
+       }
+       return has_contents;
+}
+
+
 #if 0
 namespace {
 
@@ -247,7 +274,6 @@ void MathData::metrics(MetricsInfo & mi, Dimension & dim) 
const
        mindes_ = (3 * xascent) / 4;
        slevel_ = (4 * xascent) / 5;
        sshift_ = xascent / 4;
-       kerning_ = 0;
 
        if (empty()) {
                // Cache the dimension.
@@ -255,45 +281,17 @@ void MathData::metrics(MetricsInfo & mi, Dimension & dim) 
const
                return;
        }
 
-       Cursor & cur = mi.base.bv->cursor();
-       const_cast<MathData*>(this)->updateMacros(&cur, mi.macrocontext, 
InternalUpdate);
-
-       DocIterator const & inlineCompletionPos = 
mi.base.bv->inlineCompletionPos();
-       MathData const * inlineCompletionData = 0;
-       if (inlineCompletionPos.inMathed())
-               inlineCompletionData = &inlineCompletionPos.cell();
-
-       dim.asc = 0;
-       dim.wid = 0;
-       Dimension d;
-       CoordCache::Insets & coords = mi.base.bv->coordCache().insets();
-       for (pos_type i = 0, n = size(); i != n; ++i) {
-               MathAtom const & at = operator[](i);
-               at->metrics(mi, d);
-               coords.add(at.nucleus(), d);
-               dim += d;
-               if (i == n - 1)
-                       kerning_ = at->kerning(mi.base.bv);
-
-               // HACK to draw completion suggestion inline
-               if (inlineCompletionData != this
-                   || size_t(inlineCompletionPos.pos()) != i + 1)
-                       continue;
+       MathRow mrow(mi, this);
+       mrow_cache_[mi.base.bv] = mrow;
+       mrow.metrics(mi, dim);
+       kerning_ = mrow.kerning(mi.base.bv);
 
-               docstring const & completion = mi.base.bv->inlineCompletion();
-               if (completion.length() == 0)
-                       continue;
-
-               FontInfo font = mi.base.font;
-               augmentFont(font, "mathnormal");
-               dim.wid += mathed_string_width(font, completion);
-       }
        // Cache the dimension.
        mi.base.bv->coordCache().arrays().add(this, dim);
 }
 
 
-void MathData::draw(PainterInfo & pi, int x, int y) const
+void MathData::draw(PainterInfo & pi, int const x, int const y) const
 {
        //lyxerr << "MathData::draw: x: " << x << " y: " << y << endl;
        BufferView & bv  = *pi.base.bv;
@@ -313,48 +311,8 @@ void MathData::draw(PainterInfo & pi, int x, int y) const
                || x >= bv. workWidth())
                return;
 
-       DocIterator const & inlineCompletionPos = bv.inlineCompletionPos();
-       MathData const * inlineCompletionData = 0;
-       if (inlineCompletionPos.inMathed())
-               inlineCompletionData = &inlineCompletionPos.cell();
-
-       CoordCache::Insets & coords = pi.base.bv->coordCache().insets();
-       for (size_t i = 0, n = size(); i != n; ++i) {
-               MathAtom const & at = operator[](i);
-               coords.add(at.nucleus(), x, y);
-               at->drawSelection(pi, x, y);
-               at->draw(pi, x, y);
-               x += coords.dim(at.nucleus()).wid;
-
-               // Is the inline completion here?
-               if (inlineCompletionData != this
-                   || size_t(inlineCompletionPos.pos()) != i + 1)
-                       continue;
-               docstring const & completion = bv.inlineCompletion();
-               if (completion.length() == 0)
-                       continue;
-               FontInfo f = pi.base.font;
-               augmentFont(f, "mathnormal");
-
-               // draw the unique and the non-unique completion part
-               // Note: this is not time-critical as it is
-               // only done once per screen.
-               size_t uniqueTo = bv.inlineCompletionUniqueChars();
-               docstring s1 = completion.substr(0, uniqueTo);
-               docstring s2 = completion.substr(uniqueTo);
-
-               if (!s1.empty()) {
-                       f.setColor(Color_inlinecompletion);
-                       pi.pain.text(x, y, s1, f);
-                       x += mathed_string_width(f, s1);
-               }
-
-               if (!s2.empty()) {
-                       f.setColor(Color_nonunique_inlinecompletion);
-                       pi.pain.text(x, y, s2, f);
-                       x += mathed_string_width(f, s2);
-               }
-       }
+       MathRow const & mrow = mrow_cache_[pi.base.bv];
+       mrow.draw(pi, x, y);
 }
 
 
diff --git a/src/mathed/MathData.h b/src/mathed/MathData.h
index 4b79f80..fa82ee9 100644
--- a/src/mathed/MathData.h
+++ b/src/mathed/MathData.h
@@ -16,7 +16,9 @@
 #define MATH_DATA_H
 
 #include "Dimension.h"
+
 #include "MathAtom.h"
+#include "MathRow.h"
 
 #include "OutputEnums.h"
 
@@ -24,6 +26,7 @@
 
 #include <cstddef>
 #include <vector>
+#include <map>
 
 
 namespace lyx {
@@ -117,6 +120,10 @@ public:
        MathAtom & operator[](pos_type);
        /// checked read access
        MathAtom const & operator[](pos_type) const;
+
+       /// Add this array to a math row. Return true if contents got added
+       bool addToMathRow(MathRow &, MetricsInfo const & mi) const;
+
        /// rebuild cached metrics information
        void metrics(MetricsInfo & mi, Dimension & dim) const;
        ///
@@ -177,6 +184,9 @@ protected:
        mutable int kerning_;
        Buffer * buffer_;
 
+       /// cached object that describes typeset data
+       mutable std::map<BufferView*, MathRow> mrow_cache_;
+
 private:
        /// is this an exact match at this position?
        bool find1(MathData const & ar, size_type pos) const;
diff --git a/src/mathed/MathMacro.cpp b/src/mathed/MathMacro.cpp
index 4a94e03..c4401d2 100644
--- a/src/mathed/MathMacro.cpp
+++ b/src/mathed/MathMacro.cpp
@@ -68,6 +68,27 @@ public:
        ///
        InsetCode lyxCode() const { return ARGUMENT_PROXY_CODE; }
        ///
+       bool addToMathRow(MathRow & mrow, MetricsInfo const & mi) const
+       {
+               MathRow::Element e(MathRow::BEG_ARG);
+               e.macro = mathMacro_;
+               e.ar = &mathMacro_->cell(idx_);
+               mrow.push_back(e);
+
+               mathMacro_->macro()->unlock();
+               bool const has_contents = 
mathMacro_->cell(idx_).addToMathRow(mrow, mi);
+               mathMacro_->macro()->lock();
+
+               e.type = MathRow::END_ARG;
+               mrow.push_back(e);
+
+               if (has_contents)
+                       return true;
+               // if there was no contents, then we insert the empty macro 
inset
+               // instead.
+               return InsetMath::addToMathRow(mrow, mi);
+       }
+       ///
        void metrics(MetricsInfo & mi, Dimension & dim) const {
                mathMacro_->macro()->unlock();
                mathMacro_->cell(idx_).metrics(mi, dim);
@@ -266,6 +287,34 @@ MathMacro::~MathMacro()
 }
 
 
+bool MathMacro::addToMathRow(MathRow & mrow, MetricsInfo const & mi) const
+{
+       // set edit mode for which we will have calculated row.
+       // This is the same as what is done in metrics().
+       d->editing_[mi.base.bv] = editMode(mi.base.bv);
+
+       if (displayMode() == MathMacro::DISPLAY_NORMAL
+           && !d->editing_[mi.base.bv]) {
+               MathRow::Element e(MathRow::BEG_MACRO);
+               e.macro = this;
+               mrow.push_back(e);
+
+               d->macro_->lock();
+               bool const has_contents = d->expanded_.addToMathRow(mrow, mi);
+               d->macro_->unlock();
+
+               e.type = MathRow::END_MACRO;
+               mrow.push_back(e);
+
+               if (has_contents)
+                       return true;
+               // if there was no contents, then we insert the empty macro 
inset
+               // instead.
+       }
+       return InsetMath::addToMathRow(mrow, mi);
+}
+
+
 Inset * MathMacro::clone() const
 {
        MathMacro * copy = new MathMacro(*this);
@@ -344,7 +393,7 @@ bool MathMacro::editMode(BufferView const * bv) const {
 }
 
 
-MacroData const * MathMacro::macro()
+MacroData const * MathMacro::macro() const
 {
        return d->macro_;
 }
diff --git a/src/mathed/MathMacro.h b/src/mathed/MathMacro.h
index 1a8654a..9c6fdb1 100644
--- a/src/mathed/MathMacro.h
+++ b/src/mathed/MathMacro.h
@@ -37,6 +37,10 @@ public:
        ///
        virtual MathMacro const * asMacro() const { return this; }
        ///
+       /// If the macro is in normal edit mode, dissolve its contents in
+       /// the row. Otherwise, just insert the inset.
+       bool addToMathRow(MathRow &, MetricsInfo const & mi) const;
+       ///
        void draw(PainterInfo & pi, int x, int y) const;
        /// draw selection background
        void drawSelection(PainterInfo & pi, int x, int y) const;
@@ -45,6 +49,8 @@ public:
        { drawMarkers2(pi, x, y); }
        ///
        void metrics(MetricsInfo & mi, Dimension & dim) const;
+       /// was the macro in edit mode when computing metrics?
+       bool editMetrics(BufferView const * bv) const;
        ///
        int kerning(BufferView const * bv) const;
        /// get cursor position
@@ -117,6 +123,8 @@ public:
        ///
        docstring name() const;
        ///
+       MacroData const * macro() const;
+       ///
        docstring macroName() const;
        ///
        bool validName() const;
@@ -151,10 +159,6 @@ protected:
        /// attach arguments (maybe less than arity at the end of an MathData),
        /// including the optional ones (even if it can be empty here)
        void attachArguments(std::vector<MathData> const & args, size_t arity, 
int optionals);
-       ///
-       MacroData const * macro();
-       ///
-       bool editMetrics(BufferView const * bv) const;
 
 private:
        ///
diff --git a/src/mathed/MathRow.cpp b/src/mathed/MathRow.cpp
new file mode 100644
index 0000000..55513bb
--- /dev/null
+++ b/src/mathed/MathRow.cpp
@@ -0,0 +1,292 @@
+/**
+ * \file MathRow.cpp
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Jean-Marc Lasgouttes
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <config.h>
+
+#include "MathRow.h"
+
+#include "InsetMath.h"
+#include "MathClass.h"
+#include "MathData.h"
+#include "MathMacro.h"
+#include "MathSupport.h"
+
+#include "BufferView.h"
+#include "CoordCache.h"
+#include "MetricsInfo.h"
+
+#include "frontends/Painter.h"
+
+#include "support/debug.h"
+#include "support/docstring.h"
+#include "support/lassert.h"
+
+#include <ostream>
+
+using namespace std;
+
+namespace lyx {
+
+
+MathRow::Element::Element(Type t, MathClass const mc)
+       : type(t),
+         inset(0), mclass(mc), before(0), after(0), compl_unique_to(0),
+         macro(0)
+{}
+
+
+MathRow::MathRow(MetricsInfo const & mi, MathData const * ar)
+{
+       if (ar->empty())
+               return;
+
+       // First there is a dummy element of type "open"
+       push_back(Element(BEGIN, MC_OPEN));
+
+       // Then insert the MathData argument
+       ar->addToMathRow(*this, mi);
+
+       // Finally there is a dummy element of type "close"
+       push_back(Element(END, MC_CLOSE));
+
+       /* Do spacing only in math mode. This test is a bit clumsy,
+        * but it is used in other places for guessing the current mode.
+        */
+       if (!isMathFont(mi.base.fontname))
+               return;
+
+       // update classes
+       for (int i = 1 ; i != static_cast<int>(elements_.size()) - 1 ; ++i) {
+               if (elements_[i].type != INSET)
+                       continue;
+               update_class(elements_[i].mclass, elements_[before(i)].mclass,
+                            elements_[after(i)].mclass);
+       }
+
+       // set spacing
+       // We go to the end to handle spacing at the end of equation
+       for (int i = 1 ; i != static_cast<int>(elements_.size()) ; ++i) {
+               if (elements_[i].type != INSET)
+                       continue;
+               Element & bef = elements_[before(i)];
+               int spc = class_spacing(bef.mclass, elements_[i].mclass, 
mi.base);
+               bef.after = spc / 2;
+               // this is better than spc / 2 to avoid rounding problems
+               elements_[i].before = spc - spc / 2;
+       }
+       // Do not lose spacing allocated to extremities
+       if (!elements_.empty()) {
+               elements_[after(0)].before += elements_.front().after;
+               elements_[before(elements_.size() - 1)].after += 
elements_.back().before;
+       }
+}
+
+
+int MathRow::before(int i) const
+{
+       do
+               --i;
+       while (elements_[i].type != BEGIN
+                  && elements_[i].type != INSET);
+
+       return i;
+}
+
+
+int MathRow::after(int i) const
+{
+       do
+               ++i;
+       while (elements_[i].type != END
+                  && elements_[i].type != INSET);
+
+       return i;
+}
+
+
+void MathRow::metrics(MetricsInfo & mi, Dimension & dim) const
+{
+       dim.asc = 0;
+       dim.wid = 0;
+       // In order to compute the dimension of macros and their
+       // arguments, it is necessary to keep track of them.
+       map<MathMacro const *, Dimension> dim_macros;
+       map<MathData const *, Dimension> dim_arrays;
+       CoordCache & coords = mi.base.bv->coordCache();
+
+       for (Element const & e : elements_) {
+               Dimension d;
+               switch (e.type) {
+               case BEGIN:
+               case END:
+                       break;
+               case INSET:
+                       e.inset->metrics(mi, d);
+                       d.wid += e.before + e.after;
+                       coords.insets().add(e.inset, d);
+                       dim += d;
+                       // Now add the dimension to current macros and 
arguments.
+                       for (auto & dim_macro : dim_macros)
+                               dim_macro.second += d;
+                       for (auto & dim_array : dim_arrays)
+                               dim_array.second += d;
+                       break;
+               case BEG_MACRO:
+                       e.macro->macro()->lock();
+                       // Add a macro to current list
+                       dim_macros[e.macro] = Dimension();
+                       break;
+               case END_MACRO:
+                       LATTEST(dim_macros.find(e.macro) != dim_macros.end());
+                       e.macro->macro()->unlock();
+                       // Cache the dimension of the macro and remove it from
+                       // tracking map.
+                       coords.insets().add(e.macro, dim_macros[e.macro]);
+                       dim_macros.erase(e.macro);
+                       break;
+                       // This is basically like macros
+               case BEG_ARG:
+                       if (e.macro)
+                               e.macro->macro()->unlock();
+                       dim_arrays[e.ar] = Dimension();
+                       break;
+               case END_ARG:
+                       LATTEST(dim_arrays.find(e.ar) != dim_arrays.end());
+                       if (e.macro)
+                               e.macro->macro()->lock();
+                       coords.arrays().add(e.ar, dim_arrays[e.ar]);
+                       dim_arrays.erase(e.ar);
+                       break;
+               }
+
+               if (e.compl_text.empty())
+                       continue;
+               FontInfo font = mi.base.font;
+               augmentFont(font, "mathnormal");
+               dim.wid += mathed_string_width(font, e.compl_text);
+       }
+       LATTEST(dim_macros.empty() && dim_arrays.empty());
+}
+
+
+void MathRow::draw(PainterInfo & pi, int x, int const y) const
+{
+       CoordCache & coords = pi.base.bv->coordCache();
+       for (Element const & e : elements_) {
+               Dimension d;
+               switch (e.type) {
+               case INSET: {
+                       // This is hackish: the math inset does not know that 
space
+                       // has been added before and after it; we alter its 
dimension
+                       // while it is drawing, because it relies on this value.
+                       Dimension const d = coords.insets().dim(e.inset);
+                       Dimension d2 = d;
+                       d2.wid -= e.before + e.after;
+                       coords.insets().add(e.inset, d2);
+                       e.inset->drawSelection(pi, x + e.before, y);
+                       e.inset->draw(pi, x + e.before, y);
+                       coords.insets().add(e.inset, x, y);
+                       coords.insets().add(e.inset, d);
+                       x += d.wid;
+                       break;
+               }
+               case BEG_MACRO:
+                       coords.insets().add(e.macro, x, y);
+                       break;
+               case BEG_ARG:
+                       coords.arrays().add(e.ar, x, y);
+                       // if the macro is being edited, then the painter is in
+                       // monochrome mode.
+                       if (e.macro->editMetrics(pi.base.bv))
+                               pi.pain.leaveMonochromeMode();
+                       break;
+               case END_ARG:
+                       if (e.macro->editMetrics(pi.base.bv))
+                               pi.pain.enterMonochromeMode(Color_mathbg, 
Color_mathmacroblend);
+                       break;
+               case BEGIN:
+               case END:
+               case END_MACRO:
+                       break;
+               }
+
+               if (e.compl_text.empty())
+                       continue;
+               FontInfo f = pi.base.font;
+               augmentFont(f, "mathnormal");
+
+               // draw the unique and the non-unique completion part
+               // Note: this is not time-critical as it is
+               // only done once per screen.
+               docstring const s1 = e.compl_text.substr(0, e.compl_unique_to);
+               docstring const s2 = e.compl_text.substr(e.compl_unique_to);
+
+               if (!s1.empty()) {
+                       f.setColor(Color_inlinecompletion);
+                       pi.pain.text(x, y, s1, f);
+                       x += mathed_string_width(f, s1);
+               }
+               if (!s2.empty()) {
+                       f.setColor(Color_nonunique_inlinecompletion);
+                       pi.pain.text(x, y, s2, f);
+                       x += mathed_string_width(f, s2);
+               }
+       }
+}
+
+
+int MathRow::kerning(BufferView const * bv) const
+{
+       if (elements_.empty())
+               return 0;
+       InsetMath const * inset = elements_[before(elements_.size() - 1)].inset;
+       return inset ? inset->kerning(bv) : 0;
+}
+
+
+ostream & operator<<(ostream & os, MathRow::Element const & e)
+{
+       switch (e.type) {
+       case MathRow::BEGIN:
+               os << "{";
+               break;
+       case MathRow::END:
+               os << "}";
+               break;
+       case MathRow::INSET:
+               os << "<" << e.before << "-"
+                  << to_utf8(class_to_string(e.mclass))
+                  << "-" << e.after << ">";
+               break;
+       case MathRow::BEG_MACRO:
+               os << "\\" << to_utf8(e.macro->name()) << "[";
+               break;
+       case MathRow::END_MACRO:
+               os << "]";
+               break;
+       case MathRow::BEG_ARG:
+               os << "#(";
+               break;
+       case MathRow::END_ARG:
+               os << ")";
+               break;
+       }
+       return os;
+}
+
+
+ostream & operator<<(ostream & os, MathRow const & mrow)
+{
+       for (MathRow::Element const & e : mrow)
+               os << e << "  ";
+       return os;
+}
+
+} // namespace lyx
diff --git a/src/mathed/MathRow.h b/src/mathed/MathRow.h
new file mode 100644
index 0000000..d0260e2
--- /dev/null
+++ b/src/mathed/MathRow.h
@@ -0,0 +1,137 @@
+// -*- C++ -*-
+/**
+ * \file MathRow.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Jean-Marc Lasgouttes
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef MATH_ROW_H
+#define MATH_ROW_H
+
+#include "MathClass.h"
+
+#include "support/docstring.h"
+
+#include <vector>
+
+namespace lyx {
+
+class BufferView;
+class Dimension;
+class MetricsInfo;
+class PainterInfo;
+
+class InsetMath;
+class MathData;
+class MathMacro;
+
+/*
+ * While for editing purpose it is important that macros are counted
+ * as a single element, this is not the case for display. To get the
+ * spacing correct, it is necessary to dissolve all the macros that
+ * can be, along with their arguments. Then one obtains a
+ * representation of the MathData contents as a string of insets and
+ * then spacing can be done properly.
+ *
+ * This is the purpose of the MathRow class.
+ */
+class MathRow
+{
+public:
+       // What row elements can be
+       enum Type {
+               INSET, // this element is a plain inset
+               BEG_MACRO, // a macro begins here
+               END_MACRO, // a macro ends here
+               BEG_ARG, // a macro argument begins here
+               END_ARG, // a macro argument ends here
+               BEGIN, // dummy element before row
+               END, // dummy element after row
+       };
+
+       // An elements, together with its spacing
+       struct Element
+       {
+               ///
+               Element(Type t = INSET, MathClass const mc = MC_ORD);
+
+               /// Classifies the contents of the object
+               Type type;
+
+               /// When type is INSET
+               /// the math inset
+               InsetMath const * inset;
+               /// the class of the inset
+               MathClass mclass;
+               /// the spacing around the inset
+               int before, after;
+               // Non empty when there is a completion to draw
+               docstring compl_text;
+               // the number of characters forming the unique part.
+               size_t compl_unique_to;
+
+               /// When type is BEG_MACRO, END_MACRO, BEG_ARG, END_ARG
+               /// the math macro
+               MathMacro const * macro;
+
+               // type is BEG_ARG, END_ARG
+               MathData const * ar;
+       };
+
+       ///
+       MathRow() {};
+       ///
+       typedef std::vector<Element> Elements;
+       ///
+       typedef Elements::iterator iterator;
+       ///
+       typedef Elements::const_iterator const_iterator;
+       ///
+       iterator begin() { return elements_.begin(); }
+       ///
+       iterator end() { return elements_.end(); }
+       ///
+       const_iterator begin() const { return elements_.begin(); }
+       ///
+       const_iterator end() const { return elements_.end(); }
+       //
+       void push_back(Element const & e) { elements_.push_back(e); }
+       //
+       Element & back() { return elements_.back(); }
+
+       // create the math row by unwinding all macros in the MathData and
+       // compute the spacings.
+       MathRow(MetricsInfo const & mi, MathData const * ar);
+
+       //
+       void metrics(MetricsInfo & mi, Dimension & dim) const;
+       //
+       void draw(PainterInfo & pi, int const x, int const y) const;
+
+       /// superscript kerning
+       int kerning(BufferView const *) const;
+
+private:
+       // Index of the first inset element before position i
+       int before(int i) const;
+       // Index of the first inset element after position i
+       int after(int i) const;
+
+       ///
+       Elements elements_;
+};
+
+///
+std::ostream & operator<<(std::ostream & os, MathRow::Element const & elt);
+
+///
+std::ostream & operator<<(std::ostream & os, MathRow const & mrow);
+
+
+} // namespace lyx
+
+#endif

Reply via email to