UNPKG

4.36 kBJavaScriptView Raw
1// @flow
2import defineFunction from "../defineFunction";
3import buildCommon from "../buildCommon";
4import mathMLTree from "../mathMLTree";
5import delimiter from "../delimiter";
6import Style from "../Style";
7
8import * as html from "../buildHTML";
9import * as mml from "../buildMathML";
10
11defineFunction({
12 type: "sqrt",
13 names: ["\\sqrt"],
14 props: {
15 numArgs: 1,
16 numOptionalArgs: 1,
17 },
18 handler({parser}, args, optArgs) {
19 const index = optArgs[0];
20 const body = args[0];
21 return {
22 type: "sqrt",
23 mode: parser.mode,
24 body,
25 index,
26 };
27 },
28 htmlBuilder(group, options) {
29 // Square roots are handled in the TeXbook pg. 443, Rule 11.
30
31 // First, we do the same steps as in overline to build the inner group
32 // and line
33 let inner = html.buildGroup(group.body, options.havingCrampedStyle());
34 if (inner.height === 0) {
35 // Render a small surd.
36 inner.height = options.fontMetrics().xHeight;
37 }
38
39 // Some groups can return document fragments. Handle those by wrapping
40 // them in a span.
41 inner = buildCommon.wrapFragment(inner, options);
42
43 // Calculate the minimum size for the \surd delimiter
44 const metrics = options.fontMetrics();
45 const theta = metrics.defaultRuleThickness;
46
47 let phi = theta;
48 if (options.style.id < Style.TEXT.id) {
49 phi = options.fontMetrics().xHeight;
50 }
51
52 // Calculate the clearance between the body and line
53 let lineClearance = theta + phi / 4;
54
55 const minDelimiterHeight = (inner.height + inner.depth +
56 lineClearance + theta);
57
58 // Create a sqrt SVG of the required minimum size
59 const {span: img, ruleWidth, advanceWidth} =
60 delimiter.sqrtImage(minDelimiterHeight, options);
61
62 const delimDepth = img.height - ruleWidth;
63
64 // Adjust the clearance based on the delimiter size
65 if (delimDepth > inner.height + inner.depth + lineClearance) {
66 lineClearance =
67 (lineClearance + delimDepth - inner.height - inner.depth) / 2;
68 }
69
70 // Shift the sqrt image
71 const imgShift = img.height - inner.height - lineClearance - ruleWidth;
72
73 inner.style.paddingLeft = advanceWidth + "em";
74
75 // Overlay the image and the argument.
76 const body = buildCommon.makeVList({
77 positionType: "firstBaseline",
78 children: [
79 {type: "elem", elem: inner, wrapperClasses: ["svg-align"]},
80 {type: "kern", size: -(inner.height + imgShift)},
81 {type: "elem", elem: img},
82 {type: "kern", size: ruleWidth},
83 ],
84 }, options);
85
86 if (!group.index) {
87 return buildCommon.makeSpan(["mord", "sqrt"], [body], options);
88 } else {
89 // Handle the optional root index
90
91 // The index is always in scriptscript style
92 const newOptions = options.havingStyle(Style.SCRIPTSCRIPT);
93 const rootm = html.buildGroup(group.index, newOptions, options);
94
95 // The amount the index is shifted by. This is taken from the TeX
96 // source, in the definition of `\r@@t`.
97 const toShift = 0.6 * (body.height - body.depth);
98
99 // Build a VList with the superscript shifted up correctly
100 const rootVList = buildCommon.makeVList({
101 positionType: "shift",
102 positionData: -toShift,
103 children: [{type: "elem", elem: rootm}],
104 }, options);
105 // Add a class surrounding it so we can add on the appropriate
106 // kerning
107 const rootVListWrap = buildCommon.makeSpan(["root"], [rootVList]);
108
109 return buildCommon.makeSpan(["mord", "sqrt"],
110 [rootVListWrap, body], options);
111 }
112 },
113 mathmlBuilder(group, options) {
114 const {body, index} = group;
115 return index ?
116 new mathMLTree.MathNode(
117 "mroot", [
118 mml.buildGroup(body, options),
119 mml.buildGroup(index, options),
120 ]) :
121 new mathMLTree.MathNode(
122 "msqrt", [mml.buildGroup(body, options)]);
123 },
124});