1 |
|
2 | import {defineFunctionBuilders} from "../defineFunction";
|
3 | import buildCommon from "../buildCommon";
|
4 | import {SymbolNode} from "../domTree";
|
5 | import mathMLTree from "../mathMLTree";
|
6 | import utils from "../utils";
|
7 | import Style from "../Style";
|
8 | import {checkNodeType} from "../parseNode";
|
9 |
|
10 | import * as html from "../buildHTML";
|
11 | import * as mml from "../buildMathML";
|
12 | import * as accent from "./accent";
|
13 | import * as horizBrace from "./horizBrace";
|
14 | import * as op from "./op";
|
15 |
|
16 | import type Options from "../Options";
|
17 | import type {ParseNode} from "../parseNode";
|
18 | import type {HtmlBuilder} from "../defineFunction";
|
19 | import type {MathNodeType} from "../mathMLTree";
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | const htmlBuilderDelegate = function(
|
29 | group: ParseNode<"supsub">,
|
30 | options: Options,
|
31 | ): ?HtmlBuilder<*> {
|
32 | const base = group.base;
|
33 | if (!base) {
|
34 | return null;
|
35 | } else if (base.type === "op") {
|
36 |
|
37 |
|
38 | const delegate = base.limits &&
|
39 | (options.style.size === Style.DISPLAY.size ||
|
40 | base.alwaysHandleSupSub);
|
41 | return delegate ? op.htmlBuilder : null;
|
42 | } else if (base.type === "accent") {
|
43 | return utils.isCharacterBox(base.base) ? accent.htmlBuilder : null;
|
44 | } else if (base.type === "horizBrace") {
|
45 | const isSup = !group.sub;
|
46 | return isSup === base.isOver ? horizBrace.htmlBuilder : null;
|
47 | } else {
|
48 | return null;
|
49 | }
|
50 | };
|
51 |
|
52 |
|
53 |
|
54 | defineFunctionBuilders({
|
55 | type: "supsub",
|
56 | htmlBuilder(group, options) {
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | const builderDelegate = htmlBuilderDelegate(group, options);
|
63 | if (builderDelegate) {
|
64 | return builderDelegate(group, options);
|
65 | }
|
66 |
|
67 | const {base: valueBase, sup: valueSup, sub: valueSub} = group;
|
68 | const base = html.buildGroup(valueBase, options);
|
69 | let supm;
|
70 | let subm;
|
71 |
|
72 | const metrics = options.fontMetrics();
|
73 |
|
74 |
|
75 | let supShift = 0;
|
76 | let subShift = 0;
|
77 |
|
78 | const isCharacterBox = valueBase && utils.isCharacterBox(valueBase);
|
79 | if (valueSup) {
|
80 | const newOptions = options.havingStyle(options.style.sup());
|
81 | supm = html.buildGroup(valueSup, newOptions, options);
|
82 | if (!isCharacterBox) {
|
83 | supShift = base.height - newOptions.fontMetrics().supDrop
|
84 | * newOptions.sizeMultiplier / options.sizeMultiplier;
|
85 | }
|
86 | }
|
87 |
|
88 | if (valueSub) {
|
89 | const newOptions = options.havingStyle(options.style.sub());
|
90 | subm = html.buildGroup(valueSub, newOptions, options);
|
91 | if (!isCharacterBox) {
|
92 | subShift = base.depth + newOptions.fontMetrics().subDrop
|
93 | * newOptions.sizeMultiplier / options.sizeMultiplier;
|
94 | }
|
95 | }
|
96 |
|
97 |
|
98 | let minSupShift;
|
99 | if (options.style === Style.DISPLAY) {
|
100 | minSupShift = metrics.sup1;
|
101 | } else if (options.style.cramped) {
|
102 | minSupShift = metrics.sup3;
|
103 | } else {
|
104 | minSupShift = metrics.sup2;
|
105 | }
|
106 |
|
107 |
|
108 |
|
109 | const multiplier = options.sizeMultiplier;
|
110 | const marginRight = (0.5 / metrics.ptPerEm) / multiplier + "em";
|
111 |
|
112 | let marginLeft = null;
|
113 | if (subm) {
|
114 |
|
115 |
|
116 |
|
117 | const isOiint =
|
118 | group.base && group.base.type === "op" && group.base.name &&
|
119 | (group.base.name === "\\oiint" || group.base.name === "\\oiiint");
|
120 | if (base instanceof SymbolNode || isOiint) {
|
121 |
|
122 | marginLeft = -base.italic + "em";
|
123 | }
|
124 | }
|
125 |
|
126 | let supsub;
|
127 | if (supm && subm) {
|
128 | supShift = Math.max(
|
129 | supShift, minSupShift, supm.depth + 0.25 * metrics.xHeight);
|
130 | subShift = Math.max(subShift, metrics.sub2);
|
131 |
|
132 | const ruleWidth = metrics.defaultRuleThickness;
|
133 |
|
134 |
|
135 | const maxWidth = 4 * ruleWidth;
|
136 | if ((supShift - supm.depth) - (subm.height - subShift) < maxWidth) {
|
137 | subShift = maxWidth - (supShift - supm.depth) + subm.height;
|
138 | const psi = 0.8 * metrics.xHeight - (supShift - supm.depth);
|
139 | if (psi > 0) {
|
140 | supShift += psi;
|
141 | subShift -= psi;
|
142 | }
|
143 | }
|
144 |
|
145 | const vlistElem = [
|
146 | {type: "elem", elem: subm, shift: subShift, marginRight,
|
147 | marginLeft},
|
148 | {type: "elem", elem: supm, shift: -supShift, marginRight},
|
149 | ];
|
150 |
|
151 | supsub = buildCommon.makeVList({
|
152 | positionType: "individualShift",
|
153 | children: vlistElem,
|
154 | }, options);
|
155 | } else if (subm) {
|
156 |
|
157 | subShift = Math.max(
|
158 | subShift, metrics.sub1,
|
159 | subm.height - 0.8 * metrics.xHeight);
|
160 |
|
161 | const vlistElem =
|
162 | [{type: "elem", elem: subm, marginLeft, marginRight}];
|
163 |
|
164 | supsub = buildCommon.makeVList({
|
165 | positionType: "shift",
|
166 | positionData: subShift,
|
167 | children: vlistElem,
|
168 | }, options);
|
169 | } else if (supm) {
|
170 |
|
171 | supShift = Math.max(supShift, minSupShift,
|
172 | supm.depth + 0.25 * metrics.xHeight);
|
173 |
|
174 | supsub = buildCommon.makeVList({
|
175 | positionType: "shift",
|
176 | positionData: -supShift,
|
177 | children: [{type: "elem", elem: supm, marginRight}],
|
178 | }, options);
|
179 | } else {
|
180 | throw new Error("supsub must have either sup or sub.");
|
181 | }
|
182 |
|
183 |
|
184 | const mclass = html.getTypeOfDomTree(base, "right") || "mord";
|
185 | return buildCommon.makeSpan([mclass],
|
186 | [base, buildCommon.makeSpan(["msupsub"], [supsub])],
|
187 | options);
|
188 | },
|
189 | mathmlBuilder(group, options) {
|
190 |
|
191 | let isBrace = false;
|
192 | let isOver;
|
193 | let isSup;
|
194 |
|
195 | const horizBrace = checkNodeType(group.base, "horizBrace");
|
196 | if (horizBrace) {
|
197 | isSup = !!group.sup;
|
198 | if (isSup === horizBrace.isOver) {
|
199 | isBrace = true;
|
200 | isOver = horizBrace.isOver;
|
201 | }
|
202 | }
|
203 |
|
204 | if (group.base && group.base.type === "op") {
|
205 | group.base.parentIsSupSub = true;
|
206 | }
|
207 |
|
208 | const children = [mml.buildGroup(group.base, options)];
|
209 |
|
210 | if (group.sub) {
|
211 | children.push(mml.buildGroup(group.sub, options));
|
212 | }
|
213 |
|
214 | if (group.sup) {
|
215 | children.push(mml.buildGroup(group.sup, options));
|
216 | }
|
217 |
|
218 | let nodeType: MathNodeType;
|
219 | if (isBrace) {
|
220 | nodeType = (isOver ? "mover" : "munder");
|
221 | } else if (!group.sub) {
|
222 | const base = group.base;
|
223 | if (base && base.type === "op" && base.limits &&
|
224 | (options.style === Style.DISPLAY || base.alwaysHandleSupSub)) {
|
225 | nodeType = "mover";
|
226 | } else {
|
227 | nodeType = "msup";
|
228 | }
|
229 | } else if (!group.sup) {
|
230 | const base = group.base;
|
231 | if (base && base.type === "op" && base.limits &&
|
232 | (options.style === Style.DISPLAY || base.alwaysHandleSupSub)) {
|
233 | nodeType = "munder";
|
234 | } else {
|
235 | nodeType = "msub";
|
236 | }
|
237 | } else {
|
238 | const base = group.base;
|
239 | if (base && base.type === "op" && base.limits &&
|
240 | options.style === Style.DISPLAY) {
|
241 | nodeType = "munderover";
|
242 | } else {
|
243 | nodeType = "msubsup";
|
244 | }
|
245 | }
|
246 |
|
247 | const node = new mathMLTree.MathNode(nodeType, children);
|
248 |
|
249 | return node;
|
250 | },
|
251 | });
|
252 |
|