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 ~ `&middot;` ~ `10` ~ `<msup>` ~ zexp ~ `</msup>`);
74             case MarkupLang.MathML:
75                 return (`<math>` ~ mant ~ `&middot;` ~
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] ~ `&middot;` ~ */
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 }