1 /** MathML. 2 Copyright: Per Nordlöw 2022-. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: $(WEB Per Nordlöw) 5 */ 6 module nxt.mathml; 7 8 import std.traits : isScalarType, isFloatingPoint; 9 10 import nxt.rational : Rational; // TODO: Can we turn this dep into a duck type dep? 11 import nxt.languages : MarkupLang; 12 13 /** Horizontal Alignment. */ 14 enum HAlign 15 { 16 left, ///< Left aligned. 17 center, ///< Center aligned. 18 right ///< Right aligned. 19 } 20 21 /** Vertical Alignment. */ 22 enum VAlign 23 { 24 top, ///< Top aligned. 25 middle, ///< Middle aligned. 26 bottom, ///< Bottom aligned. 27 } 28 29 /** Generic case. */ 30 string toMathML(T)(in T x) @trusted /* pure nothrow */ 31 if (isScalarType!T && 32 !isFloatingPoint!T) 33 { 34 import std.conv : to; 35 return to!string(x); 36 } 37 38 /** Returns: x in $(D MarkupLang) format. 39 * 40 * See_Also: http://forum.dlang.org/thread/awkynfizwqjnbilgddbh@forum.dlang.org#post-awkynfizwqjnbilgddbh:40forum.dlang.org 41 * See_Also: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mn 42 * See_Also: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msup 43 */ 44 string toML(T)(in T x, 45 bool usePowPlus = false, 46 bool useLeadZeros = false, 47 MarkupLang mlang = MarkupLang.HTML) @trusted /* pure nothrow */ 48 if (isFloatingPoint!T) 49 { 50 import std.conv : to; 51 import nxt.find_split_ex : findSplitAmong; 52 const parts = to!string(x).findSplitAmong!('e'); // TODO: Use std.bitmanip.FloatRep instead 53 if (parts[2].length >= 1) 54 { 55 // mantissa 56 const mant = parts[0]; 57 58 // TODO: These format fixes for the exponent are not needed if we use 59 // std.bitmanip.FloatRep instead 60 61 // exponent 62 auto exp = ((!usePowPlus && 63 parts[2][0] == '+') ? // if leading plus 64 parts[2][1..$] : // skip plus 65 parts[2]); // otherwise whole 66 import nxt.algorithm_ex : dropWhile; 67 auto zexp = useLeadZeros ? exp : exp.dropWhile('0'); 68 69 final switch (mlang) 70 { 71 case MarkupLang.unknown: 72 case MarkupLang.HTML: 73 return (mant ~ `·` ~ `10` ~ `<msup>` ~ zexp ~ `</msup>`); 74 case MarkupLang.MathML: 75 return (`<math>` ~ mant ~ `·` ~ 76 `<msup>` ~ 77 `<mn>10</mn>` ~ 78 `<mn mathsize="80%">` ~ zexp ~ `</mn>` ~ 79 `</msup>` ~ 80 `</math>`); 81 } 82 /* NOTE: This doesn't work in Firefox. Why? */ 83 /* return (`<math>` ~ parts[0] ~ `·` ~ */ 84 /* `<apply><power/>` ~ */ 85 /* `<ci>10</ci>` ~ */ 86 /* `<cn>` ~ parts[2] ~ `</cn>` */ 87 /* `</apply>` ~ */ 88 /* `</math>`); */ 89 } 90 else 91 { 92 return parts[0]; 93 } 94 } 95 96 auto toMathML(T)(in T x, 97 bool usePowPlus = false, 98 bool useLeadZeros = false) @trusted /* pure nothrow */ if (isFloatingPoint!T) 99 => toML(x, usePowPlus, useLeadZeros, MarkupLang.MathML); 100 101 auto toHTML(T)(in T x, 102 bool usePowPlus = false, 103 bool useLeadZeros = false) @trusted /* pure nothrow */ if (isFloatingPoint!T) 104 => toML(x, usePowPlus, useLeadZeros, MarkupLang.HTML); 105 106 /** Returns: MathML Representation of $(D x). 107 * 108 * See_Also: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mfrac 109 */ 110 string toMathML(T)(in Rational!T x, 111 bool bevelled = false, 112 HAlign numAlign = HAlign.center, 113 HAlign denomAlign = HAlign.center, 114 in string href = null) @safe pure 115 { 116 import std.conv : to; 117 return (`<math><mfrac` ~ 118 (bevelled ? ` bevelled="true"` : ``) ~ 119 (numAlign != HAlign.center ? ` numAlign="` ~ to!string(numAlign) ~ `"` : ``) ~ 120 (denomAlign != HAlign.center ? ` denomAlign="` ~ to!string(denomAlign) ~ `"` : ``) ~ 121 `><mi>` 122 ~ to!string(x.numerator) ~ `</mi><mi>` ~ 123 to!string(x.denominator) ~ 124 `</mi></mfrac></math>`); 125 } 126 127 unittest 128 { 129 alias Q = Rational; 130 auto x = Q!ulong(11, 22); 131 // import nxt.dbgio : dbg; 132 /** dbg(x.toMathML); */ 133 /** dbg(x.toMathML(true)); */ 134 /** dbg(x.toMathML(true, HAlign.left, HAlign.left)); */ 135 }