1 |
|
2 | import defineFunction from "../defineFunction";
|
3 | import buildCommon from "../buildCommon";
|
4 | import mathMLTree from "../mathMLTree";
|
5 | import utils from "../utils";
|
6 | import stretchy from "../stretchy";
|
7 | import {assertNodeType, checkNodeType} from "../parseNode";
|
8 | import {assertSpan, assertSymbolDomNode} from "../domTree";
|
9 |
|
10 | import * as html from "../buildHTML";
|
11 | import * as mml from "../buildMathML";
|
12 |
|
13 | import type {ParseNode, AnyParseNode} from "../parseNode";
|
14 | import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction";
|
15 |
|
16 |
|
17 |
|
18 | export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
|
19 |
|
20 | let base: AnyParseNode;
|
21 | let group: ParseNode<"accent">;
|
22 |
|
23 | const supSub: ?ParseNode<"supsub"> = checkNodeType(grp, "supsub");
|
24 | let supSubGroup;
|
25 | if (supSub) {
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | group = assertNodeType(supSub.base, "accent");
|
36 |
|
37 | base = group.base;
|
38 |
|
39 | supSub.base = base;
|
40 |
|
41 |
|
42 |
|
43 | supSubGroup = assertSpan(html.buildGroup(supSub, options));
|
44 |
|
45 |
|
46 | supSub.base = group;
|
47 | } else {
|
48 | group = assertNodeType(grp, "accent");
|
49 | base = group.base;
|
50 | }
|
51 |
|
52 |
|
53 | const body = html.buildGroup(base, options.havingCrampedStyle());
|
54 |
|
55 |
|
56 | const mustShift = group.isShifty && utils.isCharacterBox(base);
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | let skew = 0;
|
64 | if (mustShift) {
|
65 |
|
66 |
|
67 | const baseChar = utils.getBaseElem(base);
|
68 |
|
69 | const baseGroup = html.buildGroup(baseChar, options.havingCrampedStyle());
|
70 |
|
71 | skew = assertSymbolDomNode(baseGroup).skew;
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | }
|
77 |
|
78 |
|
79 | let clearance = Math.min(
|
80 | body.height,
|
81 | options.fontMetrics().xHeight);
|
82 |
|
83 |
|
84 | let accentBody;
|
85 | if (!group.isStretchy) {
|
86 | let accent;
|
87 | let width: number;
|
88 | if (group.label === "\\vec") {
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | accent = buildCommon.staticSvg("vec", options);
|
95 | width = buildCommon.svgData.vec[1];
|
96 | } else {
|
97 | accent = buildCommon.makeSymbol(
|
98 | group.label, "Main-Regular", group.mode, options);
|
99 |
|
100 |
|
101 | accent.italic = 0;
|
102 | width = accent.width;
|
103 | }
|
104 |
|
105 | accentBody = buildCommon.makeSpan(["accent-body"], [accent]);
|
106 |
|
107 |
|
108 |
|
109 |
|
110 | const accentFull = (group.label === "\\textcircled");
|
111 | if (accentFull) {
|
112 | accentBody.classes.push('accent-full');
|
113 | clearance = body.height;
|
114 | }
|
115 |
|
116 |
|
117 | let left = skew;
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | if (!accentFull) {
|
124 | left -= width / 2;
|
125 | }
|
126 |
|
127 | accentBody.style.left = left + "em";
|
128 |
|
129 |
|
130 |
|
131 | if (group.label === "\\textcircled") {
|
132 | accentBody.style.top = ".2em";
|
133 | }
|
134 |
|
135 | accentBody = buildCommon.makeVList({
|
136 | positionType: "firstBaseline",
|
137 | children: [
|
138 | {type: "elem", elem: body},
|
139 | {type: "kern", size: -clearance},
|
140 | {type: "elem", elem: accentBody},
|
141 | ],
|
142 | }, options);
|
143 |
|
144 | } else {
|
145 | accentBody = stretchy.svgSpan(group, options);
|
146 |
|
147 | accentBody = buildCommon.makeVList({
|
148 | positionType: "firstBaseline",
|
149 | children: [
|
150 | {type: "elem", elem: body},
|
151 | {
|
152 | type: "elem",
|
153 | elem: accentBody,
|
154 | wrapperClasses: ["svg-align"],
|
155 | wrapperStyle: skew > 0
|
156 | ? {
|
157 | width: `calc(100% - ${2 * skew}em)`,
|
158 | marginLeft: `${(2 * skew)}em`,
|
159 | }
|
160 | : undefined,
|
161 | },
|
162 | ],
|
163 | }, options);
|
164 | }
|
165 |
|
166 | const accentWrap =
|
167 | buildCommon.makeSpan(["mord", "accent"], [accentBody], options);
|
168 |
|
169 | if (supSubGroup) {
|
170 |
|
171 |
|
172 | supSubGroup.children[0] = accentWrap;
|
173 |
|
174 |
|
175 |
|
176 | supSubGroup.height = Math.max(accentWrap.height, supSubGroup.height);
|
177 |
|
178 |
|
179 | supSubGroup.classes[0] = "mord";
|
180 |
|
181 | return supSubGroup;
|
182 | } else {
|
183 | return accentWrap;
|
184 | }
|
185 | };
|
186 |
|
187 | const mathmlBuilder: MathMLBuilder<"accent"> = (group, options) => {
|
188 | const accentNode =
|
189 | group.isStretchy ?
|
190 | stretchy.mathMLnode(group.label) :
|
191 | new mathMLTree.MathNode("mo", [mml.makeText(group.label, group.mode)]);
|
192 |
|
193 | const node = new mathMLTree.MathNode(
|
194 | "mover",
|
195 | [mml.buildGroup(group.base, options), accentNode]);
|
196 |
|
197 | node.setAttribute("accent", "true");
|
198 |
|
199 | return node;
|
200 | };
|
201 |
|
202 | const NON_STRETCHY_ACCENT_REGEX = new RegExp([
|
203 | "\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve",
|
204 | "\\check", "\\hat", "\\vec", "\\dot", "\\mathring",
|
205 | ].map(accent => `\\${accent}`).join("|"));
|
206 |
|
207 |
|
208 | defineFunction({
|
209 | type: "accent",
|
210 | names: [
|
211 | "\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve",
|
212 | "\\check", "\\hat", "\\vec", "\\dot", "\\mathring", "\\widecheck",
|
213 | "\\widehat", "\\widetilde", "\\overrightarrow", "\\overleftarrow",
|
214 | "\\Overrightarrow", "\\overleftrightarrow", "\\overgroup",
|
215 | "\\overlinesegment", "\\overleftharpoon", "\\overrightharpoon",
|
216 | ],
|
217 | props: {
|
218 | numArgs: 1,
|
219 | },
|
220 | handler: (context, args) => {
|
221 | const base = args[0];
|
222 |
|
223 | const isStretchy = !NON_STRETCHY_ACCENT_REGEX.test(context.funcName);
|
224 | const isShifty = !isStretchy ||
|
225 | context.funcName === "\\widehat" ||
|
226 | context.funcName === "\\widetilde" ||
|
227 | context.funcName === "\\widecheck";
|
228 |
|
229 | return {
|
230 | type: "accent",
|
231 | mode: context.parser.mode,
|
232 | label: context.funcName,
|
233 | isStretchy: isStretchy,
|
234 | isShifty: isShifty,
|
235 | base: base,
|
236 | };
|
237 | },
|
238 | htmlBuilder,
|
239 | mathmlBuilder,
|
240 | });
|
241 |
|
242 |
|
243 | defineFunction({
|
244 | type: "accent",
|
245 | names: [
|
246 | "\\'", "\\`", "\\^", "\\~", "\\=", "\\u", "\\.", '\\"',
|
247 | "\\r", "\\H", "\\v", "\\textcircled",
|
248 | ],
|
249 | props: {
|
250 | numArgs: 1,
|
251 | allowedInText: true,
|
252 | allowedInMath: false,
|
253 | },
|
254 | handler: (context, args) => {
|
255 | const base = args[0];
|
256 |
|
257 | return {
|
258 | type: "accent",
|
259 | mode: context.parser.mode,
|
260 | label: context.funcName,
|
261 | isStretchy: false,
|
262 | isShifty: true,
|
263 | base: base,
|
264 | };
|
265 | },
|
266 | htmlBuilder,
|
267 | mathmlBuilder,
|
268 | });
|