X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fmathed%2FInsetMathHull.cpp;h=537d6670c5ffa0d090ffb0b2acfd5c372a001f83;hb=4ed0312c51704780af1c452d3a82a84171b3725a;hp=61d078abe0d77c51588cd64ff70e3080f5f1459a;hpb=0920872ebd563ed3f82214e2bfd9dec80e3ca210;p=lyx.git diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp index 61d078abe0..537d6670c5 100644 --- a/src/mathed/InsetMathHull.cpp +++ b/src/mathed/InsetMathHull.cpp @@ -11,7 +11,6 @@ #include #include "InsetMathHull.h" - #include "InsetMathChar.h" #include "InsetMathColor.h" #include "InsetMathFrac.h" @@ -26,6 +25,7 @@ #include "BufferParams.h" #include "BufferView.h" #include "ColorSet.h" +#include "Cursor.h" #include "CutAndPaste.h" #include "Encoding.h" #include "Exporter.h" @@ -38,10 +38,9 @@ #include "InsetMathMacro.h" #include "InsetMathMacroTemplate.h" #include "MetricsInfo.h" -#include "output_xhtml.h" #include "Paragraph.h" #include "ParIterator.h" -#include "sgml.h" +#include "xml.h" #include "TexRow.h" #include "TextClass.h" #include "TextPainter.h" @@ -51,11 +50,11 @@ #include "insets/InsetRef.h" #include "insets/RenderPreview.h" -#include "graphics/GraphicsImage.h" #include "graphics/PreviewImage.h" #include "graphics/PreviewLoader.h" #include "frontends/alert.h" +#include "frontends/FontMetrics.h" #include "frontends/Painter.h" #include "support/convert.h" @@ -64,7 +63,7 @@ #include "support/filetools.h" #include "support/lassert.h" #include "support/lstrings.h" -#include "support/RefChanger.h" +#include "support/Changer.h" #include @@ -121,84 +120,38 @@ namespace { // writes a preamble for underlined or struck out math display - void writeMathdisplayPreamble(WriteStream & os) + void writeMathdisplayPreamble(TeXMathStream & os) { - if (os.strikeoutMath()) { - if (os.ulemCmd() == WriteStream::UNDERLINE) - os << "\\raisebox{-\\belowdisplayshortskip}{" - "\\lyxmathsout{\\parbox[b]{\\linewidth}{"; - else - os << "\\lyxmathsout{\\parbox{\\linewidth}{"; - } else if (os.ulemCmd() == WriteStream::UNDERLINE) + if (os.strikeoutMath()) + return; + + if (os.ulemCmd() == TeXMathStream::UNDERLINE) os << "\\raisebox{-\\belowdisplayshortskip}{" "\\parbox[b]{\\linewidth}{"; - else if (os.ulemCmd() == WriteStream::STRIKEOUT) + else if (os.ulemCmd() == TeXMathStream::STRIKEOUT) os << "\\parbox{\\linewidth}{"; } // writes a postamble for underlined or struck out math display - void writeMathdisplayPostamble(WriteStream & os) + void writeMathdisplayPostamble(TeXMathStream & os) { - if (os.strikeoutMath()) { - if (os.ulemCmd() == WriteStream::UNDERLINE) - os << "}"; - os << "}}\\\\\n"; - } else if (os.ulemCmd() == WriteStream::UNDERLINE) + if (os.strikeoutMath()) + return; + + if (os.ulemCmd() == TeXMathStream::UNDERLINE) os << "}}\\\\\n"; - else if (os.ulemCmd() == WriteStream::STRIKEOUT) + else if (os.ulemCmd() == TeXMathStream::STRIKEOUT) os << "}\\\\\n"; } - } // namespace -HullType hullType(docstring const & s) -{ - if (s == "none") return hullNone; - if (s == "simple") return hullSimple; - if (s == "equation") return hullEquation; - if (s == "eqnarray") return hullEqnArray; - if (s == "align") return hullAlign; - if (s == "alignat") return hullAlignAt; - if (s == "xalignat") return hullXAlignAt; - if (s == "xxalignat") return hullXXAlignAt; - if (s == "multline") return hullMultline; - if (s == "gather") return hullGather; - if (s == "flalign") return hullFlAlign; - if (s == "regexp") return hullRegexp; - lyxerr << "unknown hull type '" << to_utf8(s) << "'" << endl; - return hullUnknown; -} - - -docstring hullName(HullType type) -{ - switch (type) { - case hullNone: return from_ascii("none"); - case hullSimple: return from_ascii("simple"); - case hullEquation: return from_ascii("equation"); - case hullEqnArray: return from_ascii("eqnarray"); - case hullAlign: return from_ascii("align"); - case hullAlignAt: return from_ascii("alignat"); - case hullXAlignAt: return from_ascii("xalignat"); - case hullXXAlignAt: return from_ascii("xxalignat"); - case hullMultline: return from_ascii("multline"); - case hullGather: return from_ascii("gather"); - case hullFlAlign: return from_ascii("flalign"); - case hullRegexp: return from_ascii("regexp"); - case hullUnknown: - lyxerr << "unknown hull type" << endl; - break; - } - return from_ascii("none"); -} - -static InsetLabel * dummy_pointer = 0; +static InsetLabel * dummy_pointer = nullptr; InsetMathHull::InsetMathHull(Buffer * buf) - : InsetMathGrid(buf, 1, 1), type_(hullNone), numbered_(1, NUMBER), + : InsetMathGrid(buf, 1, 1), type_(hullNone), numbered_(1, NONUMBER), numbers_(1, empty_docstring()), label_(1, dummy_pointer), preview_(new RenderPreview(this)) { @@ -213,7 +166,7 @@ InsetMathHull::InsetMathHull(Buffer * buf) InsetMathHull::InsetMathHull(Buffer * buf, HullType type) - : InsetMathGrid(buf, getCols(type), 1), type_(type), numbered_(1, NUMBER), + : InsetMathGrid(buf, getCols(type), 1), type_(type), numbered_(1, NONUMBER), numbers_(1, empty_docstring()), label_(1, dummy_pointer), preview_(new RenderPreview(this)) { @@ -231,8 +184,8 @@ InsetMathHull::InsetMathHull(InsetMathHull const & other) : InsetMathGrid(other) InsetMathHull::~InsetMathHull() { - for (size_t i = 0; i < label_.size(); ++i) - delete label_[i]; + for (auto & i : label_) + delete i; } @@ -251,8 +204,8 @@ InsetMathHull & InsetMathHull::operator=(InsetMathHull const & other) numbered_ = other.numbered_; numbers_ = other.numbers_; buffer_ = other.buffer_; - for (size_t i = 0; i < label_.size(); ++i) - delete label_[i]; + for (auto & i : label_) + delete i; label_ = other.label_; for (size_t i = 0; i != label_.size(); ++i) { if (label_[i]) @@ -275,15 +228,7 @@ void InsetMathHull::setBuffer(Buffer & buffer) } -// FIXME This should really be controlled by the TOC level, or -// something of the sort. -namespace { - const char * counters_to_save[] = {"section", "chapter"}; - unsigned int const numcnts = sizeof(counters_to_save)/sizeof(char *); -} // namespace - - -void InsetMathHull::updateBuffer(ParIterator const & it, UpdateType utype) +void InsetMathHull::updateBuffer(ParIterator const & it, UpdateType utype, bool const deleted) { if (!buffer_) { //FIXME: buffer_ should be set at creation for this inset! Problem is @@ -300,21 +245,11 @@ void InsetMathHull::updateBuffer(ParIterator const & it, UpdateType utype) Counters & cnts = buffer_->masterBuffer()->params().documentClass().counters(); - // right now, we only need to do this at export time - if (utype == OutputUpdate) { - for (size_t i = 0; i < numcnts; ++i) { - docstring const cnt = from_ascii(counters_to_save[i]); - if (cnts.hasCounter(cnt)) - counter_map[cnt] = cnts.value(cnt); - } - } - // this has to be done separately docstring const eqstr = from_ascii("equation"); if (cnts.hasCounter(eqstr)) { - if (utype == OutputUpdate) - counter_map[eqstr] = cnts.value(eqstr); for (size_t i = 0; i != label_.size(); ++i) { + docstring const oldnumber = numbers_[i]; if (numbered(i)) { Paragraph const & par = it.paragraph(); if (!par.isDeleted(it.pos())) { @@ -324,6 +259,12 @@ void InsetMathHull::updateBuffer(ParIterator const & it, UpdateType utype) numbers_[i] = from_ascii("#"); } else numbers_[i] = empty_docstring(); + // If the numbering has changed, trigger a new preview + if (oldnumber != numbers_[i] && RenderPreview::previewMath()) { + // Do we need to remove it first? + //preview_->removePreview(*buffer_); + preparePreview(it); + } } } } @@ -331,10 +272,10 @@ void InsetMathHull::updateBuffer(ParIterator const & it, UpdateType utype) // now the labels for (size_t i = 0; i != label_.size(); ++i) { if (label_[i]) - label_[i]->updateBuffer(it, utype); + label_[i]->updateBuffer(it, utype, deleted); } // pass down - InsetMathGrid::updateBuffer(it, utype); + InsetMathGrid::updateBuffer(it, utype, deleted); } @@ -515,24 +456,28 @@ bool InsetMathHull::previewState(const BufferView *const bv) const namespace { const int ERROR_FRAME_WIDTH = 2; -bool previewTooSmall(MetricsBase const & mb, Dimension const & dim) +bool previewTooSmall(Dimension const & dim) { - // Value was hardcoded to 10 pixels - int const minval = mb.bv->zoomedPixels(10); - return dim.width() <= minval && dim.height() <= minval; + return dim.width() <= 10 && dim.height() <= 10; } } void InsetMathHull::metrics(MetricsInfo & mi, Dimension & dim) const { - // true value in LaTeX is 12pt plus 3pt minus 9pt - // FIXME: even better would be to handle the short skip case. - int const display_margin = display() ? mi.base.inPixels(Length(12, Length::PT)) : 0; + /* Compute \(above|below)displayskip + true value in LaTeX is 10pt plus 2pt minus 5pt (in normal size at 10pt) + But we use a fixed number of pixels and scale them with zoom. + */ + 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; if (previewState(mi.base.bv)) { preview_->metrics(mi, dim); - if (previewTooSmall(mi.base, dim)) { + if (previewTooSmall(dim)) { // preview image is too small dim.wid += 2 * ERROR_FRAME_WIDTH; dim.asc += 2 * ERROR_FRAME_WIDTH; @@ -541,34 +486,50 @@ void InsetMathHull::metrics(MetricsInfo & mi, Dimension & dim) const // value was hardcoded to 1 pixel dim.wid += mi.base.bv->zoomedPixels(1) ; if (display()) { - dim.asc += display_margin; - dim.des += display_margin; + dim.asc += top_display_margin; + dim.des += bottom_display_margin; } } return; } - Changer dummy1 = mi.base.changeFontSet(standardFont()); - Changer dummy2 = mi.base.font.changeStyle(display() ? LM_ST_DISPLAY - : LM_ST_TEXT); - - // let the cells adjust themselves - InsetMathGrid::metrics(mi, dim); + { + Changer dummy1 = mi.base.changeFontSet(standardFont()); + Changer dummy2 = mi.base.font.changeStyle(display() ? DISPLAY_STYLE + : TEXT_STYLE); - if (display()) { - dim.asc += display_margin; - dim.des += display_margin; + // let the cells adjust themselves + InsetMathGrid::metrics(mi, dim); } + // Check whether the numbering interferes with the equations if (numberedType()) { - Changer dummy = mi.base.changeFontSet("mathrm"); - int l = 0; - for (row_type row = 0; row < nrows(); ++row) - l = max(l, mathed_string_width(mi.base.font, nicelabel(row))); + BufferParams::MathNumber const math_number = buffer().params().getMathNumber(); + int extra_offset = 0; + for (row_type row = 0; row < nrows(); ++row) { + rowinfo(row).offset[mi.base.bv] += extra_offset; + docstring const nl = nicelabel(row); + if (nl.empty()) + continue; + Dimension dimnl; + mathed_string_dim(mi.base.font, nl, dimnl); + int const ind = indent(*mi.base.bv); + int const x = ind ? ind : (mi.base.textwidth - dim.wid) / 2; + // for some reason metrics does not trigger at the + // same point as draw, and therefore we use >= instead of > + if ((math_number == BufferParams::LEFT && dimnl.wid >= x) + || (math_number == BufferParams::RIGHT + && dimnl.wid >= mi.base.textwidth - x - dim.wid)) { + extra_offset += dimnl.height(); + } + } + dim.des += extra_offset; + } + - if (l) - // Value was hardcoded to 30 pixels - dim.wid += mi.base.bv->zoomedPixels(30) + l; + if (display()) { + dim.asc += top_display_margin; + dim.des += bottom_display_margin; } // reserve some space for marker. @@ -581,7 +542,7 @@ ColorCode InsetMathHull::backgroundColor(PainterInfo const & pi) const BufferView const * const bv = pi.base.bv; if (previewState(bv)) { Dimension const dim = dimension(*pi.base.bv); - if (previewTooSmall(pi.base, dim)) + if (previewTooSmall(dim)) return Color_error; return graphics::PreviewLoader::backgroundColor(); } @@ -594,7 +555,7 @@ void InsetMathHull::drawMarkers(PainterInfo & pi, int x, int y) const ColorCode pen_color = mouseHovered(pi.base.bv) || editing(pi.base.bv)? Color_mathframe : Color_mathcorners; // If the corners have the same color as the background, do not paint them. - if (lcolor.getX11Name(Color_mathbg) == lcolor.getX11Name(pen_color)) + if (lcolor.getX11HexName(Color_mathbg) == lcolor.getX11HexName(pen_color)) return; Inset::drawMarkers(pi, x, y); @@ -611,13 +572,14 @@ void InsetMathHull::drawMarkers(PainterInfo & pi, int x, int y) const void InsetMathHull::drawBackground(PainterInfo & pi, int x, int y) const { Dimension const dim = dimension(*pi.base.bv); - if (previewTooSmall(pi.base, dim)) { + if (previewTooSmall(dim)) { pi.pain.fillRectangle(x, y - 2 * ERROR_FRAME_WIDTH, dim.wid, dim.asc + dim.des, backgroundColor(pi)); return; } + pi.pain.fillRectangle(x + 1, y - dim.asc + 1, dim.wid - 2, - dim.asc + dim.des - 1, pi.backgroundColor(this)); + dim.height() - 1, pi.backgroundColor(this)); } @@ -633,9 +595,9 @@ void InsetMathHull::draw(PainterInfo & pi, int x, int y) const if (previewState(bv)) { // Do not draw change tracking cue if taken care of by RowPainter // already. - Changer dummy = !canPaintChange(*bv) ? make_change(pi.change_, Change()) - : Changer(); - if (previewTooSmall(pi.base, dim)) { + Changer dummy = !canPaintChange(*bv) ? changeVar(pi.change, Change()) + : noChange(); + if (previewTooSmall(dim)) { // we have an extra frame preview_->draw(pi, x + ERROR_FRAME_WIDTH, y); } else { @@ -647,49 +609,59 @@ void InsetMathHull::draw(PainterInfo & pi, int x, int y) const return; } - ColorCode color = pi.selected && lyxrc.use_system_colors - ? Color_selectiontext : standardColor(); - bool const really_change_color = pi.base.font.color() == Color_none; - Changer dummy0 = really_change_color ? pi.base.font.changeColor(color) - : Changer(); - Changer dummy1 = pi.base.changeFontSet(standardFont()); - Changer dummy2 = pi.base.font.changeStyle(display() ? LM_ST_DISPLAY - : LM_ST_TEXT); - - int xmath = x; - BufferParams::MathNumber const math_number = buffer().params().getMathNumber(); - if (numberedType() && math_number == BufferParams::LEFT) { - Changer dummy = pi.base.changeFontSet("mathrm"); - int l = 0; - for (row_type row = 0; row < nrows(); ++row) - l = max(l, mathed_string_width(pi.base.font, nicelabel(row))); - - if (l) - // Value was hardcoded to 30 pixels - xmath += pi.base.bv->zoomedPixels(30) + l; - } - - InsetMathGrid::draw(pi, xmath + 1, y); - drawMarkers(pi, x, y); - + // First draw the numbers if (numberedType()) { - Changer dummy = pi.base.changeFontSet("mathrm"); + BufferParams::MathNumber const math_number = buffer().params().getMathNumber(); for (row_type row = 0; row < nrows(); ++row) { - int const yy = y + rowinfo_[row].offset_; + int yy = y + rowinfo(row).offset[bv]; docstring const nl = nicelabel(row); - if (math_number == BufferParams::LEFT) - pi.draw(x, yy, nl); - else { - int l = mathed_string_width(pi.base.font, nl); - pi.draw(x + dim.wid - l, yy, nl); + Dimension dimnl; + mathed_string_dim(pi.base.font, nl, dimnl); + if (math_number == BufferParams::LEFT) { + ColorCode const col = pi.selected_left + ? Color_selectiontext + : pi.base.font.color(); + Changer dummy0 = pi.base.font.changeColor(col); + if (dimnl.wid > x - pi.leftx) + yy += rowinfo(row).descent + dimnl.asc; + pi.pain.fillRectangle(pi.leftx, yy - dimnl.asc, + dimnl.width(), dimnl.height(), + pi.selected_left ? Color_selection : pi.background_color); + pi.draw(pi.leftx, yy, nl); + } else { + ColorCode const col = pi.selected_right + ? Color_selectiontext + : pi.base.font.color(); + Changer dummy0 = pi.base.font.changeColor(col); + if (dimnl.wid > pi.rightx - x - dim.wid) + yy += rowinfo(row).descent + dimnl.asc; + pi.pain.fillRectangle(pi.rightx - dimnl.wid, yy - dimnl.asc, + dimnl.width(), dimnl.height(), + pi.selected_right ? Color_selection : pi.background_color); + pi.draw(pi.rightx - dimnl.wid, yy, nl); } } } + // Then the equations + ColorCode color = pi.selected && lyxrc.use_system_colors + ? Color_selectiontext : standardColor(); + bool const really_change_color = pi.base.font.color() == Color_none; + Changer dummy0 = really_change_color ? pi.base.font.changeColor(color) + : noChange(); + Changer dummy1 = pi.base.changeFontSet(standardFont()); + Changer dummy2 = pi.base.font.changeStyle(display() ? DISPLAY_STYLE + : TEXT_STYLE); + InsetMathGrid::draw(pi, x + 1, y); + drawMarkers(pi, x, y); + // drawing change line - if (canPaintChange(*bv)) - pi.change_.paintCue(pi, x + 1, y + 1 - dim.asc, - x + dim.wid, y + dim.des); + if (canPaintChange(*bv)) { + // like in metrics() + int const display_margin = display() ? pi.base.inPixels(Length(12, Length::PT)) : 0; + pi.change.paintCue(pi, x + 1, y + 1 - dim.asc + display_margin, + x + dim.wid, y + dim.des - display_margin); + } } @@ -700,7 +672,7 @@ void InsetMathHull::metricsT(TextMetricsInfo const & mi, Dimension & dim) const } else { odocstringstream os; otexrowstream ots(os); - WriteStream wi(ots, false, true, WriteStream::wsDefault); + TeXMathStream wi(ots, false, true, TeXMathStream::wsDefault); write(wi); dim.wid = os.str().size(); dim.asc = 1; @@ -716,7 +688,7 @@ void InsetMathHull::drawT(TextPainter & pain, int x, int y) const } else { odocstringstream os; otexrowstream ots(os); - WriteStream wi(ots, false, true, WriteStream::wsDefault); + TeXMathStream wi(ots, false, true, TeXMathStream::wsDefault); write(wi); pain.draw(x, y, os.str().c_str()); } @@ -731,11 +703,11 @@ static docstring latexString(InsetMathHull const & inset) // \newcommand{\xxx}{\text{$\phi$}}) gets processed twice. The // first time as a whole, and the second time only the inner math. // In this last case inset.buffer() would be invalid. - static Encoding const * encoding = 0; + static Encoding const * encoding = nullptr; if (inset.isBufferValid()) encoding = &(inset.buffer().params().encoding()); otexrowstream ots(ls); - WriteStream wi(ots, false, true, WriteStream::wsPreview, encoding); + TeXMathStream wi(ots, false, true, TeXMathStream::wsPreview, encoding); inset.write(wi); return ls.str(); } @@ -853,29 +825,7 @@ void InsetMathHull::preparePreview(DocIterator const & pos, if (lsize != "normalsize" && !prefixIs(lsize, "error")) setfont += from_ascii("\\" + lsize + '\n'); - docstring setcnt; - if (forexport && haveNumbers()) { - docstring eqstr = from_ascii("equation"); - CounterMap::const_iterator it = counter_map.find(eqstr); - if (it != counter_map.end()) { - int num = it->second; - if (num >= 0) - setcnt += from_ascii("\\setcounter{") + eqstr + '}' + - '{' + convert(num) + '}' + '\n'; - } - for (size_t i = 0; i != numcnts; ++i) { - docstring cnt = from_ascii(counters_to_save[i]); - it = counter_map.find(cnt); - if (it == counter_map.end()) - continue; - int num = it->second; - if (num > 0) - setcnt += from_ascii("\\setcounter{") + cnt + '}' + - '{' + convert(num) + '}'; - } - } - docstring const snippet = macro_preamble + setfont + setcnt - + latexString(*this) + endfont; + docstring const snippet = macro_preamble + setfont + latexString(*this) + endfont; LYXERR(Debug::MACROS, "Preview snippet: " << snippet); preview_->addPreview(snippet, *buffer, forexport); } @@ -941,10 +891,6 @@ void InsetMathHull::label(row_type row, docstring const & label) void InsetMathHull::numbered(row_type row, Numbered num) { numbered_[row] = num; - if (!numbered(row) && label_[row]) { - delete label_[row]; - label_[row] = 0; - } } @@ -974,8 +920,8 @@ bool InsetMathHull::ams() const case hullEqnArray: break; } - for (size_t row = 0; row < numbered_.size(); ++row) - if (numbered_[row] == NOTAG) + for (auto const & row : numbered_) + if (row == NOTAG) return true; return false; } @@ -1004,7 +950,7 @@ bool InsetMathHull::outerDisplay() const } -Inset::DisplayType InsetMathHull::display() const +int InsetMathHull::rowFlags() const { switch (type_) { case hullUnknown: @@ -1022,12 +968,12 @@ Inset::DisplayType InsetMathHull::display() const case hullMultline: case hullGather: if (buffer().params().is_math_indent) - return AlignLeft; + return Display | AlignLeft; else - return AlignCenter; + return Display; } // avoid warning - return AlignCenter; + return Display; } @@ -1035,7 +981,7 @@ int InsetMathHull::indent(BufferView const & bv) const { // FIXME: set this in the textclass. This value is what the article class uses. static Length default_indent(2.5, Length::EM); - if (display() != Inline && buffer().params().is_math_indent) { + if (display() && buffer().params().is_math_indent) { Length const & len = buffer().params().getMathIndent(); if (len.empty()) return bv.inPixels(default_indent); @@ -1088,9 +1034,9 @@ void InsetMathHull::validate(LaTeXFeatures & features) const + bgcol + "}{\\ensuremath{\\mathtt{#1}}}}"); features.addPreambleSnippet( from_ascii("\\newcommand{\\endregexp}{}")); - } else if (outerDisplay() && features.inDeletedInset() - && !features.mustProvide("ct-dvipost")) { - features.require("ct-tikz-math-sout"); + } else if (outerDisplay() && features.inDeletedInset()) { + features.require("tikz"); + features.require("ct-tikz-object-sout"); } // Validation is necessary only if not using AMS math. @@ -1113,7 +1059,38 @@ void InsetMathHull::validate(LaTeXFeatures & features) const } -void InsetMathHull::header_write(WriteStream & os) const +CtObject InsetMathHull::getCtObject(OutputParams const & runparams) const +{ + CtObject res = CtObject::Normal; + switch(type_) { + case hullNone: + case hullSimple: + case hullAlignAt: + case hullXAlignAt: + case hullXXAlignAt: + case hullRegexp: + case hullUnknown: + break; + + case hullEquation: + case hullEqnArray: + case hullAlign: + case hullFlAlign: + case hullGather: + case hullMultline: { + if (runparams.inulemcmd + && (!runparams.local_font || runparams.local_font->fontInfo().strikeout() != FONT_ON)) + res = CtObject::UDisplayObject; + else + res = CtObject::DisplayObject; + break; + } + } + return res; +} + + +void InsetMathHull::header_write(TeXMathStream & os) const { bool n = numberedType(); @@ -1179,7 +1156,7 @@ void InsetMathHull::header_write(WriteStream & os) const } -void InsetMathHull::footer_write(WriteStream & os) const +void InsetMathHull::footer_write(TeXMathStream & os) const { bool n = numberedType(); @@ -1362,8 +1339,11 @@ void InsetMathHull::delCol(col_type col) docstring InsetMathHull::nicelabel(row_type row) const { - if (!numbered(row)) - return docstring(); + if (!numbered(row)) { + if (!label_[row]) + return docstring(); + return '[' + label_[row]->screenLabel() + ']'; + } docstring const & val = numbers_[row]; if (!label_[row]) return '(' + val + ')'; @@ -1376,7 +1356,7 @@ void InsetMathHull::glueall(HullType type) MathData ar; for (idx_type i = 0; i < nargs(); ++i) ar.append(cell(i)); - InsetLabel * label = 0; + InsetLabel * label = nullptr; if (type == hullEquation) { // preserve first non-empty label for (row_type row = 0; row < nrows(); ++row) { @@ -1527,7 +1507,7 @@ void InsetMathHull::mutate(HullType newtype) numbered(0, false); } else { setType(hullEquation); - numbered(0, label_[0] ? true : false); + numbered(0, label_[0] != nullptr); mutate(newtype); } break; @@ -1666,30 +1646,33 @@ void InsetMathHull::mutate(HullType newtype) } -docstring InsetMathHull::eolString(row_type row, bool fragile, bool latex, - bool last_eoln) const +void InsetMathHull::eol(TeXMathStream & os, row_type row, bool fragile, bool latex, + bool last_eoln) const { - docstring res; if (numberedType()) { - if (label_[row] && numbered(row)) { + if (label_[row]) { docstring const name = latex ? escape(label_[row]->getParam("name")) : label_[row]->getParam("name"); - res += "\\label{" + name + '}'; + os << "\\label{" + name + '}'; } if (type_ != hullMultline) { if (numbered_[row] == NONUMBER) - res += "\\nonumber "; + os << "\\nonumber "; else if (numbered_[row] == NOTAG) - res += "\\notag "; + os<< "\\notag "; } + if (os.output() == TeXMathStream::wsPreview && !numbers_[row].empty()) { + os << "\\global\\def\\theequation{" << numbers_[row] << "}\n"; + } + } // Never add \\ on the last empty line of eqnarray and friends last_eoln = false; - return res + InsetMathGrid::eolString(row, fragile, latex, last_eoln); + InsetMathGrid::eol(os, row, fragile, latex, last_eoln); } -void InsetMathHull::write(WriteStream & os) const +void InsetMathHull::write(TeXMathStream & os) const { ModeSpecifier specifier(os, MATH_MODE); header_write(os); @@ -1842,11 +1825,22 @@ void InsetMathHull::doDispatch(Cursor & cur, FuncRequest & cmd) //lyxerr << "toggling all numbers" << endl; cur.recordUndoInset(); bool old = numberedType(); - if (type_ == hullMultline) - numbered(nrows() - 1, !old); - else - for (row_type row = 0; row < nrows(); ++row) + if (type_ == hullMultline) { + row_type row = nrows() - 1; + numbered(row, !old); + if (old && label_[row]) { + delete label_[row]; + label_[row] = 0; + } + } else { + for (row_type row = 0; row < nrows(); ++row) { numbered(row, !old); + if (old && label_[row]) { + delete label_[row]; + label_[row] = 0; + } + } + } cur.message(old ? _("No number") : _("Number")); cur.forceBufferUpdate(); @@ -1859,6 +1853,10 @@ void InsetMathHull::doDispatch(Cursor & cur, FuncRequest & cmd) bool old = numbered(r); cur.message(old ? _("No number") : _("Number")); numbered(r, !old); + if (old && label_[r]) { + delete label_[r]; + label_[r] = 0; + } cur.forceBufferUpdate(); break; } @@ -1893,7 +1891,7 @@ void InsetMathHull::doDispatch(Cursor & cur, FuncRequest & cmd) // if there is an argument, find the corresponding label, else // check whether there is at least one label. for (row = 0; row != nrows(); ++row) - if (numbered(row) && label_[row] + if (label_[row] && (cmd.argument().empty() || label(row) == cmd.argument())) break; } @@ -2062,15 +2060,15 @@ bool InsetMathHull::getStatus(Cursor & cur, FuncRequest const & cmd, return true; } case LFUN_MATH_DISPLAY: { - status.setEnabled(display() != Inline || allowDisplayMath(cur)); - status.setOnOff(display() != Inline); + status.setEnabled(display() || allowDisplayMath(cur)); + status.setOnOff(display()); return true; } case LFUN_MATH_NUMBER_TOGGLE: // FIXME: what is the right test, this or the one of // LABEL_INSERT? - status.setEnabled(display() != Inline); + status.setEnabled(display()); status.setOnOff(numberedType()); return true; @@ -2079,7 +2077,7 @@ bool InsetMathHull::getStatus(Cursor & cur, FuncRequest const & cmd, // LABEL_INSERT? bool const enable = (type_ == hullMultline) ? (nrows() - 1 == cur.row()) - : display() != Inline; + : display(); row_type const r = (type_ == hullMultline) ? nrows() - 1 : cur.row(); status.setEnabled(enable); status.setOnOff(enable && numbered(r)); @@ -2096,12 +2094,12 @@ bool InsetMathHull::getStatus(Cursor & cur, FuncRequest const & cmd, // if there is no argument and we're inside math, we retrieve // the row number from the cursor position. row_type row = (type_ == hullMultline) ? nrows() - 1 : cur.row(); - enabled = numberedType() && label_[row] && numbered(row); + enabled = numberedType() && label_[row]; } else { // if there is an argument, find the corresponding label, else // check whether there is at least one label. for (row_type row = 0; row != nrows(); ++row) { - if (numbered(row) && label_[row] && + if (label_[row] && (cmd.argument().empty() || label(row) == cmd.argument())) { enabled = true; break; @@ -2239,7 +2237,10 @@ void InsetMathHull::handleFont2(Cursor & cur, docstring const & arg) void InsetMathHull::edit(Cursor & cur, bool front, EntryDirection entry_from) { - InsetMathNest::edit(cur, front, entry_from); + cur.push(*this); + bool enter_front = (entry_from == Inset::ENTRY_DIRECTION_LEFT || + (entry_from == Inset::ENTRY_DIRECTION_IGNORE && front)); + enter_front ? idxFirst(cur) : idxLast(cur); // The inset formula dimension is not necessarily the same as the // one of the instant preview image, so we have to indicate to the // BufferView that a metrics update is needed. @@ -2295,7 +2296,7 @@ void InsetMathHull::write(ostream & os) const { odocstringstream oss; otexrowstream ots(oss); - WriteStream wi(ots, false, false, WriteStream::wsDefault); + TeXMathStream wi(ots, false, false, TeXMathStream::wsDefault); oss << "Formula "; write(wi); os << to_utf8(oss.str()); @@ -2339,12 +2340,19 @@ int InsetMathHull::plaintext(odocstringstream & os, odocstringstream oss; otexrowstream ots(oss); Encoding const * const enc = encodings.fromLyXName("utf8"); - WriteStream wi(ots, false, true, WriteStream::wsDefault, enc); + TeXMathStream::OutputType ot; + if (op.find_effective()) + ot = TeXMathStream::wsSearchAdv; + else + ot = TeXMathStream::wsDefault; // Fix Bug #6139 - if (type_ == hullRegexp) + if (type_ == hullRegexp) { + TeXMathStream wi(ots, false, true, ot, enc); write(wi); + } else { + TeXMathStream wi(ots, false, true, ot, enc); for (row_type r = 0; r < nrows(); ++r) { for (col_type c = 0; c < ncols(); ++c) wi << (c == 0 ? "" : "\t") << cell(index(r, c)); @@ -2362,60 +2370,105 @@ int InsetMathHull::plaintext(odocstringstream & os, } -int InsetMathHull::docbook(odocstream & os, OutputParams const & runparams) const -{ - MathStream ms(os); - int res = 0; +void InsetMathHull::docbook(XMLStream & xs, OutputParams const & runparams) const { + // Choose the tag around the MathML equation. docstring name; + bool doCR = false; if (getType() == hullSimple) name = from_ascii("inlineequation"); - else + else { + doCR = true; // This is a block equation, always have on its own line. name = from_ascii("informalequation"); + } - docstring bname = name; - if (!label(0).empty()) - bname += " id='" + sgml::cleanID(buffer(), runparams, label(0)) + "'"; + // DocBook also has , but it comes with a title. + // TODO: recognise \tag from amsmath? This would allow having with a proper title. - ++ms.tab(); ms.cr(); ms.os() << '<' << bname << '>'; + docstring attr; - odocstringstream ls; - otexstream ols(ls); - if (runparams.flavor == OutputParams::XML) { - ms << MTag("alt role='tex' "); - // Workaround for db2latex: db2latex always includes equations with - // \ensuremath{} or \begin{display}\end{display} - // so we strip LyX' math environment - WriteStream wi(ols, false, false, WriteStream::wsDefault, runparams.encoding); - InsetMathGrid::write(wi); - ms << from_utf8(subst(subst(to_utf8(ls.str()), "&", "&"), "<", "<")); - ms << ETag("alt"); - ms << MTag("math"); - ms << ETag("alt"); - ms << MTag("math"); - InsetMathGrid::mathmlize(ms); - ms << ETag("math"); - } else { - ms << MTag("alt role='tex'"); - latex(ols, runparams); - res = ols.texrow().rows(); - ms << from_utf8(subst(subst(to_utf8(ls.str()), "&", "&"), "<", "<")); - ms << ETag("alt"); + bool mathmlNamespaceInline = buffer().params().docbook_mathml_prefix == BufferParams::NoPrefix; + if (mathmlNamespaceInline) + attr += "xmlns=\"http://www.w3.org/1998/Math/MathML\""; + + for (row_type i = 0; i < nrows(); ++i) { + if (!label(i).empty()) { + if (!attr.empty()) + attr += " "; + + attr += "xml:id=\"" + xml::cleanID(label(i)) + "\""; + break; + } } - ms << from_ascii(""); - else - ms << from_ascii("\">"); + xs << xml::StartTag(name, attr); + xs << xml::CR(); + + // With DocBook 5, MathML must be within its own namespace (defined in Buffer.cpp::writeDocBookSource, except when + // it should be inlined). + // Output everything in a separate stream so that this does not interfere with the standard flow of DocBook tags. + std::string mathmlNamespacePrefix; + if (!mathmlNamespaceInline) { + if (buffer().params().docbook_mathml_prefix == BufferParams::MPrefix) + mathmlNamespacePrefix = "m"; + else if (buffer().params().docbook_mathml_prefix == BufferParams::MMLPrefix) + mathmlNamespacePrefix = "mml"; + } + + odocstringstream osmath; + MathMLStream ms(osmath, mathmlNamespacePrefix); - ms.cr(); --ms.tab(); ms.os() << "'; + // Output the MathML subtree. + // TeX transcription. Avoid MTag/ETag so that there are no extraneous spaces. + ms << "<" << from_ascii("alt") << " role='tex'" << ">"; + // Workaround for db2latex: db2latex always includes equations with + // \ensuremath{} or \begin{display}\end{display} + // so we strip LyX' math environment + odocstringstream ls; + otexstream ols(ls); + TeXMathStream wi(ols, false, false, TeXMathStream::wsDefault, runparams.encoding); + InsetMathGrid::write(wi); + ms << from_utf8(subst(subst(to_utf8(ls.str()), "&", "&"), "<", "<")); + ms << ""; + + // Actual transformation of the formula into MathML. This translation may fail (for example, due to custom macros). + // The new output stream is required to deal with the errors: first write completely the formula into this + // temporary stream; then, if it is possible without error, then copy it back to the "real" stream. Otherwise, + // some incomplete tags might be put into the real stream. + try { + // First, generate the MathML expression. If there is an error in the generation, this block is not fully + // executed, and the formula is not output to the DocBook stream. + odocstringstream ostmp; + MathMLStream mstmp(ostmp, ms.xmlns()); + mathmlize(mstmp); + + // Choose the display style for the formula, to be output as an attribute near the formula root. + std::string mathmlAttr; + if (getType() == hullSimple) + mathmlAttr = "display=\"inline\""; + else + mathmlAttr = "display=\"block\""; - return ms.line() + res; + // Then, output the formula. + ms << MTag("math", mathmlAttr); + ms.cr(); + osmath << ostmp.str(); // osmath is not a XMLStream, so no need for XMLStream::ESCAPE_NONE. + ms << ETag("math"); + } catch (MathExportException const &) { + ms.cr(); + osmath << "MathML export failed. Please report this as a bug to the LyX developers: " + "https://www.lyx.org/trac."; + } + + // Output the complete formula to the DocBook stream. + xs << XMLStream::ESCAPE_NONE << osmath.str(); + xs << xml::CR(); + xs << xml::EndTag(name); + if (doCR) + xs << xml::CR(); } @@ -2475,46 +2528,61 @@ void InsetMathHull::htmlize(HtmlStream & os) const // this duplicates code from InsetMathGrid, but // we need access here to number information, // and we simply do not have that in InsetMathGrid. -void InsetMathHull::mathmlize(MathStream & os) const +void InsetMathHull::mathmlize(MathMLStream & ms) const { - bool const havenumbers = haveNumbers(); - bool const havetable = havenumbers || nrows() > 1 || ncols() > 1; + bool const havetable = haveNumbers() || nrows() > 1 || ncols() > 1; + + // Simplest case: single row, single column, no numbering. + if (!havetable) { + ms << cell(index(0, 0)); + return; + } + + // More complex case: wrap elements in a table. + if (getType() == hullSimple) { + ms << MTag("mtable"); + } else if (getType() >= hullAlign && getType() <= hullXXAlignAt) { + // hullAlign, hullAlignAt, hullXAlignAt, hullXXAlignAt + string alignment; + for (col_type col = 0; col < ncols(); ++col) { + alignment += (col % 2) ? "left " : "right "; + } + ms << MTag("mtable", "displaystyle='true' columnalign='" + alignment + "'"); + } else { + ms << MTag("mtable", "displaystyle='true'"); + } - if (havetable) - os << MTag("mtable"); - char const * const celltag = havetable ? "mtd" : "mrow"; - // FIXME There does not seem to be wide support at the moment - // for mlabeledtr, so we have to use just mtr for now. - // char const * const rowtag = havenumbers ? "mlabeledtr" : "mtr"; - char const * const rowtag = "mtr"; for (row_type row = 0; row < nrows(); ++row) { - if (havetable) - os << MTag(rowtag); + // No Wed browser supports mlabeledtr in 2023, even though it has been introduced in + // MathML 2 (2003). Moreover, it is not supported in MathML 4 Core. + ms << MTag("mtr"); + for (col_type col = 0; col < ncols(); ++col) { - os << MTag(celltag) + ms << MTag("mtd") << cell(index(row, col)) - << ETag(celltag); + << ETag("mtd"); } + // fleqn? - if (havenumbers) { - os << MTag("mtd"); + if (haveNumbers()) { + ms << MTag("mtd"); docstring const & num = numbers_[row]; if (!num.empty()) - os << '(' << num << ')'; - os << ETag("mtd"); + ms << MTagInline("mtext") << '(' << num << ')' << ETagInline("mtext"); + ms << ETag("mtd"); } - if (havetable) - os << ETag(rowtag); + + ms << ETag("mtr"); } - if (havetable) - os << ETag("mtable"); + + ms << ETag("mtable"); } -void InsetMathHull::mathAsLatex(WriteStream & os) const +void InsetMathHull::mathAsLatex(TeXMathStream & os) const { MathEnsurer ensurer(os, false); - bool havenumbers = haveNumbers(); + bool const havenumbers = haveNumbers(); bool const havetable = havenumbers || nrows() > 1 || ncols() > 1; if (!havetable) { @@ -2535,7 +2603,7 @@ void InsetMathHull::mathAsLatex(WriteStream & os) const docstring const & num = numbers_[row]; if (!num.empty()) os << '(' << num << ')'; - os << ""; + os << ""; } os << ""; } @@ -2543,7 +2611,7 @@ void InsetMathHull::mathAsLatex(WriteStream & os) const } -docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const +docstring InsetMathHull::xhtml(XMLStream & xs, OutputParams const & op) const { BufferParams::MathOutput const mathtype = buffer().masterBuffer()->params().html_math_output; @@ -2562,22 +2630,29 @@ docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const // FIXME Eventually we would like to do this inset by inset. if (mathtype == BufferParams::MathML) { odocstringstream os; - MathStream ms(os); + MathMLStream ms(os); try { mathmlize(ms); success = true; } catch (MathExportException const &) {} if (success) { + string const name_space_declaration = "xmlns='http://www.w3.org/1998/Math/MathML'"; if (getType() == hullSimple) - xs << html::StartTag("math", - "xmlns=\"http://www.w3.org/1998/Math/MathML\"", true); - else - xs << html::StartTag("math", - "display=\"block\" xmlns=\"http://www.w3.org/1998/Math/MathML\"", true); - xs << XHTMLStream::ESCAPE_NONE - << os.str() - << html::EndTag("math"); + xs << xml::StartTag("math", name_space_declaration, true); + else { + if (!xs.isLastTagCR()) + xs << xml::CR(); + xs << xml::StartTag("math", name_space_declaration + " display='block'", true); + } + + xs << XMLStream::ESCAPE_NONE + << os.str(); + + if (!xs.isLastTagCR()) + xs << xml::CR(); + xs << xml::EndTag("math") << xml::CR(); } + // In case of failure, generate an image. } else if (mathtype == BufferParams::HTML) { odocstringstream os; HtmlStream ms(os); @@ -2587,11 +2662,12 @@ docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const } catch (MathExportException const &) {} if (success) { string const tag = (getType() == hullSimple) ? "span" : "div"; - xs << html::StartTag(tag, "class='formula'", true) - << XHTMLStream::ESCAPE_NONE + xs << xml::StartTag(tag, "class='formula'", true) + << XMLStream::ESCAPE_NONE << os.str() - << html::EndTag(tag); + << xml::EndTag(tag); } + // In case of failure, generate an image. } // what we actually want is this: @@ -2606,7 +2682,7 @@ docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const // // so this is for Images. if (!success && mathtype != BufferParams::LaTeX) { - graphics::PreviewImage const * pimage = 0; + graphics::PreviewImage const * pimage = nullptr; if (!op.dryrun) { loadPreview(docit_); pimage = preview_->getPreviewImage(buffer()); @@ -2630,11 +2706,11 @@ docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const } string const tag = (getType() == hullSimple) ? "span" : "div"; - xs << html::CR() - << html::StartTag(tag, "style = \"text-align: center;\"") - << html::CompTag("img", "src=\"" + filename + "\" alt=\"Mathematical Equation\"") - << html::EndTag(tag) - << html::CR(); + xs << xml::CR() + << xml::StartTag(tag, "style = \"text-align: center;\"") + << xml::CompTag("img", "src=\"" + filename + "\" alt=\"Mathematical Equation\"") + << xml::EndTag(tag) + << xml::CR(); success = true; } } @@ -2647,7 +2723,7 @@ docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const // $...$ or whatever. odocstringstream ls; otexrowstream ots(ls); - WriteStream wi(ots, false, true, WriteStream::wsPreview); + TeXMathStream wi(ots, false, true, TeXMathStream::wsPreview); ModeSpecifier specifier(wi, MATH_MODE); mathAsLatex(wi); docstring const latex = ls.str(); @@ -2657,10 +2733,10 @@ docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const // FIXME XHTML // probably should allow for some kind of customization here string const tag = (getType() == hullSimple) ? "span" : "div"; - xs << html::StartTag(tag, "class='math'") + xs << xml::StartTag(tag, "class='math'") << latex - << html::EndTag(tag) - << html::CR(); + << xml::EndTag(tag) + << xml::CR(); } return docstring(); } @@ -2669,7 +2745,7 @@ docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const void InsetMathHull::toString(odocstream & os) const { odocstringstream ods; - plaintext(ods, OutputParams(0)); + plaintext(ods, OutputParams(nullptr)); os << ods.str(); } @@ -2677,7 +2753,7 @@ void InsetMathHull::toString(odocstream & os) const void InsetMathHull::forOutliner(docstring & os, size_t const, bool const) const { odocstringstream ods; - OutputParams op(0); + OutputParams op(nullptr); op.for_toc = true; // FIXME: this results in spilling TeX into the LyXHTML output since the // outliner is used to generate the LyXHTML list of figures/etc. @@ -2701,7 +2777,7 @@ void InsetMathHull::recordLocation(DocIterator const & di) bool InsetMathHull::canPaintChange(BufferView const &) const { // We let RowPainter do it seamlessly for inline insets - return display() != Inline; + return display(); }