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 
12 /** Markup language. */
13 enum MarkupLanguage {
14 	unknown, nullValue = unknown, // `HybridHashMap` null support
15 	HTML,
16 	MathML
17 }
18 
19 /** Horizontal Alignment. */
20 enum HAlign {
21 	unknown, nullValue = unknown, // `HybridHashMap` null support
22 	left,					   ///< Left aligned.
23 	center,					 ///< Center aligned.
24 	right					   ///< Right aligned.
25 }
26 
27 /** Vertical Alignment. */
28 enum VAlign {
29 	unknown,
30 	top,						///< Top aligned.
31 	middle,					 ///< Middle aligned.
32 	bottom,					 ///< Bottom aligned.
33 }
34 
35 /** Generic case. */
36 string toMathML(T)(in T x) @trusted /* pure nothrow */
37 if (isScalarType!T &&
38 	!isFloatingPoint!T)
39 {
40 	import std.conv : to;
41 	return to!string(x);
42 }
43 
44 /** Returns: x in $(D MarkupLanguage) format.
45  *
46  * See_Also: http://forum.dlang.org/thread/awkynfizwqjnbilgddbh@forum.dlang.org#post-awkynfizwqjnbilgddbh:40forum.dlang.org
47  * See_Also: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mn
48  * See_Also: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/msup
49  */
50 string toML(T)(in T x,
51 			   bool usePowPlus = false,
52 			   bool useLeadZeros = false,
53 			   MarkupLanguage mlang = MarkupLanguage.HTML) @trusted /* pure nothrow */
54 if (isFloatingPoint!T)
55 {
56 	import std.conv : to;
57 	import nxt.algorithm.searching : findSplitAmong;
58 	const parts = to!string(x).findSplitAmong!('e'); /+ TODO: Use std.bitmanip.FloatRep instead +/
59 	if (parts[2].length >= 1)
60 	{
61 		// mantissa
62 		const mant = parts[0];
63 
64 		/+ TODO: These format fixes for the exponent are not needed if we use +/
65 		// std.bitmanip.FloatRep instead
66 
67 		// exponent
68 		auto exp = ((!usePowPlus &&
69 					  parts[2][0] == '+') ? // if leading plus
70 					 parts[2][1..$] : // skip plus
71 					 parts[2]); // otherwise whole
72 		import nxt.algorithm_ex : dropWhile;
73 		auto zexp = useLeadZeros ? exp : exp.dropWhile('0');
74 
75 		final switch (mlang)
76 		{
77 			case MarkupLanguage.unknown:
78 			case MarkupLanguage.HTML:
79 				return (mant ~ `&middot;` ~ `10` ~ `<msup>` ~ zexp ~ `</msup>`);
80 			case MarkupLanguage.MathML:
81 				return (`<math>` ~ mant ~ `&middot;` ~
82 						`<msup>` ~
83 						`<mn>10</mn>` ~
84 						`<mn mathsize="80%">` ~ zexp ~ `</mn>` ~
85 						`</msup>` ~
86 						`</math>`);
87 		}
88 		/* NOTE: This doesn't work in Firefox. Why? */
89 		/* return (`<math>` ~ parts[0] ~ `&middot;` ~ */
90 		/*		 `<apply><power/>` ~ */
91 		/*		 `<ci>10</ci>` ~ */
92 		/*		 `<cn>` ~ parts[2] ~ `</cn>` */
93 		/*		 `</apply>` ~ */
94 		/*		 `</math>`); */
95 	}
96 	else
97 	{
98 		return parts[0];
99 	}
100 }
101 
102 auto toMathML(T)(in T x,
103 				 bool usePowPlus = false,
104 				 bool useLeadZeros = false) @trusted /* pure nothrow */ if (isFloatingPoint!T)
105 	=> toML(x, usePowPlus, useLeadZeros, MarkupLanguage.MathML);
106 
107 auto toHTML(T)(in T x,
108 			   bool usePowPlus = false,
109 			   bool useLeadZeros = false) @trusted /* pure nothrow */ if (isFloatingPoint!T)
110 	=> toML(x, usePowPlus, useLeadZeros, MarkupLanguage.HTML);
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 	alias Q = Rational;
135 	auto x = Q!ulong(11, 22);
136 	// import nxt.debugio : dbg;
137 	/** dbg(x.toMathML); */
138 	/** dbg(x.toMathML(true)); */
139 	/** dbg(x.toMathML(true, HAlign.left, HAlign.left)); */
140 }