commit 1254e249faac7e08f0318daabb0f6d2adcf787cf Author: Thibaut Cuvelier <tcuvel...@lyx.org> Date: Sun Nov 3 05:30:37 2024 +0100
Add math font into HtmlStream/MathMLStream. At the same time, implement font nesting for maths. MathFontInfo also moves to its final resting place, along with the math streams. --- src/mathed/InsetMathFont.cpp | 200 ++++--------------------------------------- src/mathed/MathStream.cpp | 158 ++++++++++++++++++++++++++++++++++ src/mathed/MathStream.h | 74 ++++++++++++++++ 3 files changed, 248 insertions(+), 184 deletions(-) diff --git a/src/mathed/InsetMathFont.cpp b/src/mathed/InsetMathFont.cpp index 120a8cf6da..91c5ed517e 100644 --- a/src/mathed/InsetMathFont.cpp +++ b/src/mathed/InsetMathFont.cpp @@ -29,178 +29,6 @@ using namespace lyx::support; namespace lyx { -namespace { -// Similar to FontInfo and its related enums, but specifically for the math -// mode. -// -// All types have enumerations, like FontEnums.h, even though there are -// sometimes only two cases: this design ensures some future-proofness and -// ensures that you cannot inadvertently swap two values. -class MathFontInfo { -public: - enum MathFontFamily { - MATH_NORMAL_FAMILY = 0, // Default value in MathML. - MATH_FRAKTUR_FAMILY, - MATH_SANS_FAMILY, - MATH_MONOSPACE_FAMILY, - MATH_DOUBLE_STRUCK_FAMILY, - MATH_SCRIPT_FAMILY, - MATH_SMALL_CAPS // Not natively supported in any version of MathML. - }; - - enum MathFontSeries { - MATH_MEDIUM_SERIES = 0, // Default value in MathML. // Default value in MathML. - MATH_BOLD_SERIES - }; - - enum MathFontShape { - MATH_UP_SHAPE = 0, - MATH_ITALIC_SHAPE // Default value in MathML mi, not outside. - }; - - MathFontInfo() : - family_(MATH_NORMAL_FAMILY), series_(MATH_MEDIUM_SERIES), shape_(MATH_UP_SHAPE) {} - MathFontInfo(const MathFontFamily family, const MathFontSeries series, const MathFontShape shape) : - family_(family), series_(series), shape_(shape) {} - - static MathFontInfo fromMacro(const docstring& tag) - { - MathFontInfo font; - if (tag == "mathnormal" || tag == "mathrm" - || tag == "text" || tag == "textnormal" - || tag == "textrm" || tag == "textup" - || tag == "textmd") - font.shape_ = MATH_UP_SHAPE; - else if (tag == "frak" || tag == "mathfrak") - font.family_ = MATH_FRAKTUR_FAMILY; - else if (tag == "mathbf" || tag == "textbf") - font.series_ = MATH_BOLD_SERIES; - else if (tag == "mathbb" || tag == "mathbbm" - || tag == "mathds") - font.family_ = MATH_DOUBLE_STRUCK_FAMILY; - else if (tag == "mathcal") - font.family_ = MATH_SCRIPT_FAMILY; - else if (tag == "mathit" || tag == "textsl" - || tag == "emph" || tag == "textit") - font.shape_ = MATH_ITALIC_SHAPE; - else if (tag == "mathsf" || tag == "textsf") - font.family_ = MATH_SANS_FAMILY; - else if (tag == "mathtt" || tag == "texttt") - font.family_ = MATH_MONOSPACE_FAMILY; - else if (tag == "textipa" || tag == "textsc" || tag == "noun") - font.family_ = MATH_SMALL_CAPS; - // Otherwise, the tag is not recognised, use the default font. - - return font; - } - - MathFontFamily family() const { return family_; } - MathFontSeries series() const { return series_; } - MathFontShape shape() const { return shape_; } - - std::string toMathMLMathVariant(MathMLStream::MathMLVersion mathml_version) const - { - return mathml_version == MathMLStream::MathMLVersion::mathml3 ? - toMathVariantForMathML3() : toMathVariantForMathMLCore(); - } - - std::string toHTMLSpanClass() const - { - std::string span_class; - switch (family_) { - case MATH_NORMAL_FAMILY: - break; - case MATH_FRAKTUR_FAMILY: - span_class = "fraktur"; - break; - case MATH_SANS_FAMILY: - span_class = "sans"; - break; - case MATH_MONOSPACE_FAMILY: - span_class = "monospace"; - break; - case MATH_DOUBLE_STRUCK_FAMILY: - // This style does not exist in HTML and cannot be implemented in CSS. - break; - case MATH_SCRIPT_FAMILY: - span_class = "script"; - break; - case MATH_SMALL_CAPS: - span_class = "noun"; - break; - } - // Explicitly match the cases with an empty output. This ensures that we catch at runtime - // invalid values for the enum while keeping compile-time warnings. - if (span_class.empty() && (family_ == MATH_NORMAL_FAMILY || family_ == MATH_DOUBLE_STRUCK_FAMILY)) { - LYXERR(Debug::MATHED, - "Unexpected case in MathFontInfo::toHTMLSpanClass: family_ = " << family_ - << ", series = " << series_ << ", shape = " << shape_); - } - - if (series_ == MATH_BOLD_SERIES) { - if (!span_class.empty()) span_class += "-"; - span_class += "bold"; - } - - if (shape_ == MATH_ITALIC_SHAPE) { - if (!span_class.empty()) span_class += "-"; - span_class += "italic"; - } - - return span_class; - } - -private: - MathFontFamily family_; - MathFontSeries series_; - MathFontShape shape_; - - std::string toMathVariantForMathML3() const - { - // mathvariant is the way MathML 3 encodes fonts. - // Not all combinations are supported. Official list: - // https://www.w3.org/TR/MathML3/chapter3.html#presm.commatt - // "initial", "tailed", "looped", and "stretched" are not implemented, - // as they are only useful for Arabic characters (for which LyX has no - // support right now). - switch (family_) { - case MATH_MONOSPACE_FAMILY: - return "monospace"; - case MATH_DOUBLE_STRUCK_FAMILY: - return "double-struck"; - case MATH_FRAKTUR_FAMILY: - return series_ == MATH_BOLD_SERIES ? "bold-fraktur" : "fraktur"; - case MATH_SCRIPT_FAMILY: - return series_ == MATH_BOLD_SERIES ? "bold-script" : "script"; - case MATH_SANS_FAMILY: - if (series_ == MATH_MEDIUM_SERIES) { - return shape_ == MATH_UP_SHAPE ? "sans-serif" : "sans-serif-italic"; - } - return shape_ == MATH_UP_SHAPE ? "bold-sans-serif" : "sans-serif-bold-italic"; - case MATH_NORMAL_FAMILY: - if (series_ == MATH_MEDIUM_SERIES) { - return shape_ == MATH_UP_SHAPE ? "normal" : "italic"; - } - return shape_ == MATH_UP_SHAPE ? "bold" : "bold-italic"; - case MATH_SMALL_CAPS: - // No valid value... - return ""; - } - - // Better safe than sorry. - LYXERR(Debug::MATHED, - "Unexpected case in MathFontInfo::toMathVariantForMathML3: family_ = " << family_ - << ", series = " << series_ << ", shape = " << shape_); - return ""; - } - - std::string toMathVariantForMathMLCore() const - { - return shape_ == MATH_UP_SHAPE ? "normal" : ""; - } -}; -} - InsetMathFont::InsetMathFont(Buffer * buf, latexkeys const * key) : InsetMathNest(buf, 1), key_(key) {} @@ -340,31 +168,33 @@ void InsetMathFont::validate(LaTeXFeatures & features) const // The fonts we want to support are listed in lib/symbols void InsetMathFont::htmlize(HtmlStream & os) const { - // FIXME These are not quite right, because they do not nest - // correctly. A proper fix would presumably involve tracking - // the fonts already in effect. - const MathFontInfo font = MathFontInfo::fromMacro(key_->name); - const std::string span_class = font.toHTMLSpanClass(); + MathFontInfo old_font = os.fontInfo().mergeWith(MathFontInfo::fromMacro(key_->name)); + const std::string span_class = os.fontInfo().toHTMLSpanClass(); + // TODO: with this implementation, variants are output several times. See mathmlize. if (!span_class.empty()) { os << MTag("span", "class='" + span_class + "'") << cell(0) << ETag("span"); } else os << cell(0); + + os.fontInfo().replaceBy(old_font); } // The fonts we want to support are listed in lib/symbols void InsetMathFont::mathmlize(MathMLStream & ms) const { - // FIXME These are not quite right, because they do not nest - // correctly. A proper fix would presumably involve tracking - // the fonts already in effect. - const MathFontInfo font = MathFontInfo::fromMacro(key_->name); - const std::string variant = font.toMathMLMathVariant(ms.version()); - - if (font.shape() == MathFontInfo::MATH_UP_SHAPE) { + MathFontInfo old_font = ms.fontInfo().mergeWith(MathFontInfo::fromMacro(key_->name)); + const std::string variant = ms.fontInfo().toMathMLMathVariant(ms.version()); + + // TODO: with this implementation, variants are output several times (e.g., + // for \textit{a\textbf{b}}, italics will be present twice in the output, + // <mstyle mathvariant="italic">a<mstyle mathvariant="italic bold">b</mstyle></mstyle> + // To do better, we'd need more logic for (un)toggling, like in the code for text + // (for instance, computeDocBookFontSwitch). + if (ms.fontInfo().shape() == MathFontInfo::MATH_UP_SHAPE) { SetMode textmode(ms, true); ms << cell(0); } else if (!variant.empty()) { @@ -374,6 +204,8 @@ void InsetMathFont::mathmlize(MathMLStream & ms) const } else { ms << cell(0); } + + ms.fontInfo().replaceBy(old_font); } diff --git a/src/mathed/MathStream.cpp b/src/mathed/MathStream.cpp index 085c8310d4..c6f78f72c1 100644 --- a/src/mathed/MathStream.cpp +++ b/src/mathed/MathStream.cpp @@ -35,6 +35,164 @@ namespace lyx { ////////////////////////////////////////////////////////////////////// +MathFontInfo MathFontInfo::mergeWith(const MathFontInfo& other) +{ + MathFontInfo old = *this; + + if (other.family_ != family_ && other.family_ != MATH_INHERIT_FAMILY) { + family_ = other.family_; + } + if (other.series_ != series_ && other.series_ != MATH_INHERIT_SERIES) { + series_ = other.series_; + } + if (other.shape_ != shape_ && other.shape_ != MATH_INHERIT_SHAPE) { + shape_ = other.shape_; + } + + return old; +} + + +void MathFontInfo::replaceBy(const MathFontInfo& other) +{ + *this = other; +} + + +MathFontInfo MathFontInfo::fromMacro(const docstring& tag) +{ + MathFontInfo font; + if (tag == "mathnormal" || tag == "mathrm" + || tag == "text" || tag == "textnormal" + || tag == "textrm" || tag == "textup" + || tag == "textmd") + font.shape_ = MATH_UP_SHAPE; + else if (tag == "frak" || tag == "mathfrak") + font.family_ = MATH_FRAKTUR_FAMILY; + else if (tag == "mathbf" || tag == "textbf") + font.series_ = MATH_BOLD_SERIES; + else if (tag == "mathbb" || tag == "mathbbm" + || tag == "mathds") + font.family_ = MATH_DOUBLE_STRUCK_FAMILY; + else if (tag == "mathcal") + font.family_ = MATH_SCRIPT_FAMILY; + else if (tag == "mathit" || tag == "textsl" + || tag == "emph" || tag == "textit") + font.shape_ = MATH_ITALIC_SHAPE; + else if (tag == "mathsf" || tag == "textsf") + font.family_ = MATH_SANS_FAMILY; + else if (tag == "mathtt" || tag == "texttt") + font.family_ = MATH_MONOSPACE_FAMILY; + else if (tag == "textipa" || tag == "textsc" || tag == "noun") + font.family_ = MATH_SMALL_CAPS; + // Otherwise, the tag is not recognised, use the default font. + + return font; +} + + +std::string MathFontInfo::toMathMLMathVariant(MathMLVersion mathml_version) const +{ + return mathml_version == MathMLVersion::mathml3 ? + toMathVariantForMathML3() : toMathVariantForMathMLCore(); +} + + +std::string MathFontInfo::toMathVariantForMathML3() const +{ + // mathvariant is the way MathML 3 encodes fonts. + // Not all combinations are supported. Official list: + // https://www.w3.org/TR/MathML3/chapter3.html#presm.commatt + // "initial", "tailed", "looped", and "stretched" are not implemented, + // as they are only useful for Arabic characters (for which LyX has no + // support right now). + switch (family_) { + case MATH_MONOSPACE_FAMILY: + return "monospace"; + case MATH_DOUBLE_STRUCK_FAMILY: + return "double-struck"; + case MATH_FRAKTUR_FAMILY: + return series_ == MATH_BOLD_SERIES ? "bold-fraktur" : "fraktur"; + case MATH_SCRIPT_FAMILY: + return series_ == MATH_BOLD_SERIES ? "bold-script" : "script"; + case MATH_SANS_FAMILY: + if (series_ == MATH_BOLD_SERIES) { + return shape_ == MATH_UP_SHAPE ? "bold-sans-serif" : "sans-serif-bold-italic"; + } + return shape_ == MATH_UP_SHAPE ? "sans-serif" : "sans-serif-italic"; + case MATH_NORMAL_FAMILY: + case MATH_INHERIT_FAMILY: // Only consider the other two attributes. + if (series_ == MATH_BOLD_SERIES) { + return shape_ == MATH_UP_SHAPE ? "bold" : "bold-italic"; + } + return shape_ == MATH_UP_SHAPE ? "normal" : "italic"; + case MATH_SMALL_CAPS: + // No valid value to return. + return ""; + } + + // Better safe than sorry. + LYXERR(Debug::MATHED, + "Unexpected case in MathFontInfo::toMathVariantForMathML3: family_ = " << family_ + << ", series = " << series_ << ", shape = " << shape_); + return ""; +} + + +std::string MathFontInfo::toMathVariantForMathMLCore() const +{ + return shape_ == MATH_UP_SHAPE ? "normal" : ""; +} + + +std::string MathFontInfo::toHTMLSpanClass() const +{ + std::string span_class; + switch (family_) { + case MATH_INHERIT_FAMILY: + case MATH_NORMAL_FAMILY: + break; + case MATH_FRAKTUR_FAMILY: + span_class = "fraktur"; + break; + case MATH_SANS_FAMILY: + span_class = "sans"; + break; + case MATH_MONOSPACE_FAMILY: + span_class = "monospace"; + break; + case MATH_DOUBLE_STRUCK_FAMILY: + // This style does not exist in HTML and cannot be implemented in CSS. + break; + case MATH_SCRIPT_FAMILY: + span_class = "script"; + break; + case MATH_SMALL_CAPS: + span_class = "noun"; + break; + } + // Explicitly match the cases with an empty output. This ensures that we catch at runtime + // invalid values for the enum while keeping compile-time warnings. + if (span_class.empty() && (family_ == MATH_INHERIT_FAMILY || family_ == MATH_NORMAL_FAMILY || family_ == MATH_DOUBLE_STRUCK_FAMILY)) { + LYXERR(Debug::MATHED, + "Unexpected case in MathFontInfo::toHTMLSpanClass: family_ = " << family_ + << ", series = " << series_ << ", shape = " << shape_); + } + + if (series_ == MATH_BOLD_SERIES) { + if (!span_class.empty()) span_class += "-"; + span_class += "bold"; + } + + if (shape_ == MATH_ITALIC_SHAPE) { + if (!span_class.empty()) span_class += "-"; + span_class += "italic"; + } + + return span_class; +} + + NormalStream & operator<<(NormalStream & ns, MathAtom const & at) { at->normalize(ns); diff --git a/src/mathed/MathStream.h b/src/mathed/MathStream.h index 9b76ff5344..d611ec8c7c 100644 --- a/src/mathed/MathStream.h +++ b/src/mathed/MathStream.h @@ -33,6 +33,72 @@ enum class MathMLVersion : int { mathmlCore }; +// Similar to FontInfo and its related enums, but specifically for the math +// mode. +// +// All types have enumerations, like FontEnums.h, even though there are +// sometimes only two cases: this design ensures some future-proofness and +// ensures that you cannot inadvertently swap two values. +class MathFontInfo { +public: + enum MathFontFamily { + MATH_INHERIT_FAMILY = 0, + MATH_NORMAL_FAMILY, // Default value in MathML. + MATH_FRAKTUR_FAMILY, + MATH_SANS_FAMILY, + MATH_MONOSPACE_FAMILY, + MATH_DOUBLE_STRUCK_FAMILY, + MATH_SCRIPT_FAMILY, + MATH_SMALL_CAPS // Not natively supported in any version of MathML. + }; + + enum MathFontSeries { + MATH_INHERIT_SERIES = 0, + MATH_MEDIUM_SERIES, // Default value in MathML. // Default value in MathML. + MATH_BOLD_SERIES + }; + + enum MathFontShape { + MATH_INHERIT_SHAPE = 0, + MATH_UP_SHAPE, + MATH_ITALIC_SHAPE // Default value in MathML mi, not outside. + }; + + MathFontInfo() : + family_(MATH_INHERIT_FAMILY), series_(MATH_INHERIT_SERIES), shape_(MATH_INHERIT_SHAPE) {} + MathFontInfo(const MathFontFamily family, const MathFontSeries series, const MathFontShape shape) : + family_(family), series_(series), shape_(shape) {} + + /// Merges this font with another one, replacing all fields in this font + /// by the ones in the argument if they are not "inherit". + MathFontInfo mergeWith(const MathFontInfo& other); + /// Replaces this font by the given one. + void replaceBy(const MathFontInfo& other); + /// Parses a LaTeX font macro into a MathFontInfo object (builder method). + static MathFontInfo fromMacro(const docstring& tag); + + MathFontFamily family() const { return family_; } + MathFontSeries series() const { return series_; } + MathFontShape shape() const { return shape_; } + + /// Transforms this font into the mathvariant attribute for MathML. + /// For MathML 3, all fonts are output as mathvariants; for MathML Core, + /// almost all fonts are supposed to be output as characters. + std::string toMathMLMathVariant(MathMLVersion mathml_version) const; + /// Transforms this font into a class attribute for the HTML span tag. + std::string toHTMLSpanClass() const; + +private: + MathFontFamily family_; + MathFontSeries series_; + MathFontShape shape_; + + /// Transforms this font into the mathvariant attribute for MathML 3. + std::string toMathVariantForMathML3() const; + /// Transforms this font into the mathvariant attribute for MathML Core. + std::string toMathVariantForMathMLCore() const; +}; + // // LaTeX/LyX // @@ -409,6 +475,8 @@ public: const MathStyle & getFontMathStyle() const { return font_math_style_; } /// Sets the current math style in the stream. void setFontMathStyle(const MathStyle style) { font_math_style_ = style; } + /// Gets a mutable reference to the stream's current font. + MathFontInfo& fontInfo() { return current_font_; } private: /// Check whether it makes sense to start a <mtext> void beforeText(); @@ -433,6 +501,8 @@ private: MathMLVersion version_; /// The only important part of a FontInfo object. MathStyle font_math_style_; + /// Current font (which might be nested). + MathFontInfo current_font_; /// friend class SetMode; friend MathMLStream & operator<<(MathMLStream &, MathAtom const &); @@ -506,6 +576,8 @@ public: docstring deferred() const; /// bool inText() const { return in_text_; } + /// Gets a mutable reference to the stream's current font. + MathFontInfo& fontInfo() { return current_font_; } private: /// void setTextMode(bool t) { in_text_ = t; } @@ -519,6 +591,8 @@ private: bool in_text_; /// odocstringstream deferred_; + /// Current font (which might be nested). + MathFontInfo current_font_; /// friend class SetHTMLMode; }; -- lyx-cvs mailing list lyx-cvs@lists.lyx.org https://lists.lyx.org/mailman/listinfo/lyx-cvs