1 /** MathML.
2     Copyright: Per Nordlöw 2018-.
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 */
99 if (isFloatingPoint!T)
100 {
101     return toML(x, usePowPlus, useLeadZeros, MarkupLang.MathML);
102 }
103 
104 auto toHTML(T)(in T x,
105                bool usePowPlus = false,
106                bool useLeadZeros = false) @trusted /* pure nothrow */
107 if (isFloatingPoint!T)
108 {
109     return toML(x, usePowPlus, useLeadZeros, MarkupLang.HTML);
110 }
111 
112 /** Returns: MathML Representation of $(D x).
113  *
114  * See_Also: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mfrac
115  */
116 string toMathML(T)(in Rational!T x,
117                    bool bevelled = false,
118                    HAlign numAlign = HAlign.center,
119                    HAlign denomAlign = HAlign.center,
120                    in string href = null) @safe pure
121 {
122     import std.conv : to;
123     return (`<math><mfrac` ~
124             (bevelled ? ` bevelled="true"` : ``) ~
125             (numAlign != HAlign.center ? ` numAlign="` ~ to!string(numAlign) ~ `"` : ``) ~
126             (denomAlign != HAlign.center ? ` denomAlign="` ~ to!string(denomAlign) ~ `"` : ``) ~
127             `><mi>`
128             ~ to!string(x.numerator) ~ `</mi><mi>` ~
129             to!string(x.denominator) ~
130             `</mi></mfrac></math>`);
131 }
132 
133 unittest
134 {
135     alias Q = Rational;
136     auto x = Q!ulong(11, 22);
137     // import nxt.dbgio : dbg;
138     /** dbg(x.toMathML); */
139     /** dbg(x.toMathML(true)); */
140     /** dbg(x.toMathML(true, HAlign.left, HAlign.left)); */
141 }