UNPKG

7.44 kBJavaScriptView Raw
1// @flow
2import defineFunction from "../defineFunction";
3import buildCommon from "../buildCommon";
4import mathMLTree from "../mathMLTree";
5import utils from "../utils";
6import stretchy from "../stretchy";
7import {assertNodeType} from "../parseNode";
8
9import * as html from "../buildHTML";
10import * as mml from "../buildMathML";
11
12
13const htmlBuilder = (group, options) => {
14 // \cancel, \bcancel, \xcancel, \sout, \fbox, \colorbox, \fcolorbox
15 // Some groups can return document fragments. Handle those by wrapping
16 // them in a span.
17 const inner = buildCommon.wrapFragment(
18 html.buildGroup(group.body, options), options);
19
20 const label = group.label.substr(1);
21 const scale = options.sizeMultiplier;
22 let img;
23 let imgShift = 0;
24
25 // In the LaTeX cancel package, line geometry is slightly different
26 // depending on whether the subject is wider than it is tall, or vice versa.
27 // We don't know the width of a group, so as a proxy, we test if
28 // the subject is a single character. This captures most of the
29 // subjects that should get the "tall" treatment.
30 const isSingleChar = utils.isCharacterBox(group.body);
31
32 if (label === "sout") {
33 img = buildCommon.makeSpan(["stretchy", "sout"]);
34 img.height = options.fontMetrics().defaultRuleThickness / scale;
35 imgShift = -0.5 * options.fontMetrics().xHeight;
36
37 } else {
38 // Add horizontal padding
39 if (/cancel/.test(label)) {
40 if (!isSingleChar) {
41 inner.classes.push("cancel-pad");
42 }
43 } else {
44 inner.classes.push("boxpad");
45 }
46
47 // Add vertical padding
48 let vertPad = 0;
49 // ref: LaTeX source2e: \fboxsep = 3pt; \fboxrule = .4pt
50 // ref: cancel package: \advance\totalheight2\p@ % "+2"
51 if (/box/.test(label)) {
52 vertPad = label === "colorbox" ? 0.3 : 0.34;
53 } else {
54 vertPad = isSingleChar ? 0.2 : 0;
55 }
56
57 img = stretchy.encloseSpan(inner, label, vertPad, options);
58 imgShift = inner.depth + vertPad;
59
60 if (group.backgroundColor) {
61 img.style.backgroundColor = group.backgroundColor;
62 if (group.borderColor) {
63 img.style.borderColor = group.borderColor;
64 }
65 }
66 }
67
68 let vlist;
69 if (group.backgroundColor) {
70 vlist = buildCommon.makeVList({
71 positionType: "individualShift",
72 children: [
73 // Put the color background behind inner;
74 {type: "elem", elem: img, shift: imgShift},
75 {type: "elem", elem: inner, shift: 0},
76 ],
77 }, options);
78 } else {
79 vlist = buildCommon.makeVList({
80 positionType: "individualShift",
81 children: [
82 // Write the \cancel stroke on top of inner.
83 {
84 type: "elem",
85 elem: inner,
86 shift: 0,
87 },
88 {
89 type: "elem",
90 elem: img,
91 shift: imgShift,
92 wrapperClasses: /cancel/.test(label) ? ["svg-align"] : [],
93 },
94 ],
95 }, options);
96 }
97
98 if (/cancel/.test(label)) {
99 // The cancel package documentation says that cancel lines add their height
100 // to the expression, but tests show that isn't how it actually works.
101 vlist.height = inner.height;
102 vlist.depth = inner.depth;
103 }
104
105 if (/cancel/.test(label) && !isSingleChar) {
106 // cancel does not create horiz space for its line extension.
107 return buildCommon.makeSpan(["mord", "cancel-lap"], [vlist], options);
108 } else {
109 return buildCommon.makeSpan(["mord"], [vlist], options);
110 }
111};
112
113const mathmlBuilder = (group, options) => {
114 const node = new mathMLTree.MathNode(
115 (group.label.indexOf("colorbox") > -1) ? "mpadded" : "menclose",
116 [mml.buildGroup(group.body, options)]
117 );
118 switch (group.label) {
119 case "\\cancel":
120 node.setAttribute("notation", "updiagonalstrike");
121 break;
122 case "\\bcancel":
123 node.setAttribute("notation", "downdiagonalstrike");
124 break;
125 case "\\sout":
126 node.setAttribute("notation", "horizontalstrike");
127 break;
128 case "\\fbox":
129 node.setAttribute("notation", "box");
130 break;
131 case "\\fcolorbox":
132 case "\\colorbox":
133 // <menclose> doesn't have a good notation option. So use <mpadded>
134 // instead. Set some attributes that come included with <menclose>.
135 node.setAttribute("width", "+6pt");
136 node.setAttribute("height", "+6pt");
137 node.setAttribute("lspace", "3pt"); // LaTeX source2e: \fboxsep = 3pt
138 node.setAttribute("voffset", "3pt");
139 if (group.label === "\\fcolorbox") {
140 const thk = options.fontMetrics().defaultRuleThickness;
141 node.setAttribute("style", "border: " + thk + "em solid " +
142 String(group.borderColor));
143 }
144 break;
145 case "\\xcancel":
146 node.setAttribute("notation", "updiagonalstrike downdiagonalstrike");
147 break;
148 }
149 if (group.backgroundColor) {
150 node.setAttribute("mathbackground", group.backgroundColor);
151 }
152 return node;
153};
154
155defineFunction({
156 type: "enclose",
157 names: ["\\colorbox"],
158 props: {
159 numArgs: 2,
160 allowedInText: true,
161 greediness: 3,
162 argTypes: ["color", "text"],
163 },
164 handler({parser, funcName}, args, optArgs) {
165 const color = assertNodeType(args[0], "color-token").color;
166 const body = args[1];
167 return {
168 type: "enclose",
169 mode: parser.mode,
170 label: funcName,
171 backgroundColor: color,
172 body,
173 };
174 },
175 htmlBuilder,
176 mathmlBuilder,
177});
178
179defineFunction({
180 type: "enclose",
181 names: ["\\fcolorbox"],
182 props: {
183 numArgs: 3,
184 allowedInText: true,
185 greediness: 3,
186 argTypes: ["color", "color", "text"],
187 },
188 handler({parser, funcName}, args, optArgs) {
189 const borderColor = assertNodeType(args[0], "color-token").color;
190 const backgroundColor = assertNodeType(args[1], "color-token").color;
191 const body = args[2];
192 return {
193 type: "enclose",
194 mode: parser.mode,
195 label: funcName,
196 backgroundColor,
197 borderColor,
198 body,
199 };
200 },
201 htmlBuilder,
202 mathmlBuilder,
203});
204
205defineFunction({
206 type: "enclose",
207 names: ["\\fbox"],
208 props: {
209 numArgs: 1,
210 argTypes: ["text"],
211 allowedInText: true,
212 },
213 handler({parser}, args) {
214 return {
215 type: "enclose",
216 mode: parser.mode,
217 label: "\\fbox",
218 body: args[0],
219 };
220 },
221});
222
223defineFunction({
224 type: "enclose",
225 names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout"],
226 props: {
227 numArgs: 1,
228 },
229 handler({parser, funcName}, args, optArgs) {
230 const body = args[0];
231 return {
232 type: "enclose",
233 mode: parser.mode,
234 label: funcName,
235 body,
236 };
237 },
238 htmlBuilder,
239 mathmlBuilder,
240});