mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-11 22:18:41 +00:00
extract sqrt, enclose, and verb into their own files (#1040)
* extract sqrt, enclose, and verb into their own files * add returns types in utils.js and update the comment * rebase, fix location of comment
This commit is contained in:
188
src/functions/enclose.js
Normal file
188
src/functions/enclose.js
Normal file
@@ -0,0 +1,188 @@
|
||||
// @flow
|
||||
import defineFunction from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import utils from "../utils";
|
||||
import stretchy from "../stretchy";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
|
||||
|
||||
const htmlBuilder = (group, options) => {
|
||||
// \cancel, \bcancel, \xcancel, \sout, \fbox, \colorbox, \fcolorbox
|
||||
const inner = html.buildGroup(group.value.body, options);
|
||||
|
||||
const label = group.value.label.substr(1);
|
||||
const scale = options.sizeMultiplier;
|
||||
let img;
|
||||
let imgShift = 0;
|
||||
const isColorbox = /color/.test(label);
|
||||
|
||||
if (label === "sout") {
|
||||
img = buildCommon.makeSpan(["stretchy", "sout"]);
|
||||
img.height = options.fontMetrics().defaultRuleThickness / scale;
|
||||
imgShift = -0.5 * options.fontMetrics().xHeight;
|
||||
|
||||
} else {
|
||||
// Add horizontal padding
|
||||
inner.classes.push(/cancel/.test(label) ? "cancel-pad" : "boxpad");
|
||||
|
||||
// Add vertical padding
|
||||
let vertPad = 0;
|
||||
// ref: LaTeX source2e: \fboxsep = 3pt; \fboxrule = .4pt
|
||||
// ref: cancel package: \advance\totalheight2\p@ % "+2"
|
||||
if (/box/.test(label)) {
|
||||
vertPad = label === "colorbox" ? 0.3 : 0.34;
|
||||
} else {
|
||||
vertPad = utils.isCharacterBox(group.value.body) ? 0.2 : 0;
|
||||
}
|
||||
|
||||
img = stretchy.encloseSpan(inner, label, vertPad, options);
|
||||
imgShift = inner.depth + vertPad;
|
||||
|
||||
if (isColorbox) {
|
||||
img.style.backgroundColor = group.value.backgroundColor.value;
|
||||
if (label === "fcolorbox") {
|
||||
img.style.borderColor = group.value.borderColor.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let vlist;
|
||||
if (isColorbox) {
|
||||
vlist = buildCommon.makeVList({
|
||||
positionType: "individualShift",
|
||||
children: [
|
||||
// Put the color background behind inner;
|
||||
{type: "elem", elem: img, shift: imgShift},
|
||||
{type: "elem", elem: inner, shift: 0},
|
||||
],
|
||||
}, options);
|
||||
} else {
|
||||
vlist = buildCommon.makeVList({
|
||||
positionType: "individualShift",
|
||||
children: [
|
||||
// Write the \cancel stroke on top of inner.
|
||||
{
|
||||
type: "elem",
|
||||
elem: inner,
|
||||
shift: 0,
|
||||
},
|
||||
{
|
||||
type: "elem",
|
||||
elem: img,
|
||||
shift: imgShift,
|
||||
wrapperClasses: /cancel/.test(label) ? ["svg-align"] : [],
|
||||
},
|
||||
],
|
||||
}, options);
|
||||
}
|
||||
|
||||
if (/cancel/.test(label)) {
|
||||
// cancel does not create horiz space for its line extension.
|
||||
// That is, not when adjacent to a mord.
|
||||
return buildCommon.makeSpan(["mord", "cancel-lap"], [vlist], options);
|
||||
} else {
|
||||
return buildCommon.makeSpan(["mord"], [vlist], options);
|
||||
}
|
||||
};
|
||||
|
||||
const mathmlBuilder = (group, options) => {
|
||||
const node = new mathMLTree.MathNode(
|
||||
"menclose", [mml.buildGroup(group.value.body, options)]);
|
||||
switch (group.value.label) {
|
||||
case "\\cancel":
|
||||
node.setAttribute("notation", "updiagonalstrike");
|
||||
break;
|
||||
case "\\bcancel":
|
||||
node.setAttribute("notation", "downdiagonalstrike");
|
||||
break;
|
||||
case "\\sout":
|
||||
node.setAttribute("notation", "horizontalstrike");
|
||||
break;
|
||||
case "\\fbox":
|
||||
node.setAttribute("notation", "box");
|
||||
break;
|
||||
case "\\colorbox":
|
||||
node.setAttribute("mathbackground",
|
||||
group.value.backgroundColor.value);
|
||||
break;
|
||||
case "\\fcolorbox":
|
||||
node.setAttribute("mathbackground",
|
||||
group.value.backgroundColor.value);
|
||||
// TODO(ron): I don't know any way to set the border color.
|
||||
node.setAttribute("notation", "box");
|
||||
break;
|
||||
default:
|
||||
// xcancel
|
||||
node.setAttribute("notation", "updiagonalstrike downdiagonalstrike");
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
defineFunction({
|
||||
type: "enclose",
|
||||
names: ["\\colorbox"],
|
||||
props: {
|
||||
numArgs: 2,
|
||||
allowedInText: true,
|
||||
greediness: 3,
|
||||
argTypes: ["color", "text"],
|
||||
},
|
||||
handler(context, args, optArgs) {
|
||||
const color = args[0];
|
||||
const body = args[1];
|
||||
return {
|
||||
type: "enclose",
|
||||
label: context.funcName,
|
||||
backgroundColor: color,
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
htmlBuilder,
|
||||
mathmlBuilder,
|
||||
});
|
||||
|
||||
defineFunction({
|
||||
type: "enclose",
|
||||
names: ["\\fcolorbox"],
|
||||
props: {
|
||||
numArgs: 3,
|
||||
allowedInText: true,
|
||||
greediness: 3,
|
||||
argTypes: ["color", "color", "text"],
|
||||
},
|
||||
handler(context, args, optArgs) {
|
||||
const borderColor = args[0];
|
||||
const backgroundColor = args[1];
|
||||
const body = args[2];
|
||||
return {
|
||||
type: "enclose",
|
||||
label: context.funcName,
|
||||
backgroundColor: backgroundColor,
|
||||
borderColor: borderColor,
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
htmlBuilder,
|
||||
mathmlBuilder,
|
||||
});
|
||||
|
||||
defineFunction({
|
||||
type: "enclose",
|
||||
names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\fbox"],
|
||||
props: {
|
||||
numArgs: 1,
|
||||
},
|
||||
handler(context, args, optArgs) {
|
||||
const body = args[0];
|
||||
return {
|
||||
type: "enclose",
|
||||
label: context.funcName,
|
||||
body: body,
|
||||
};
|
||||
},
|
||||
htmlBuilder,
|
||||
mathmlBuilder,
|
||||
});
|
130
src/functions/sqrt.js
Normal file
130
src/functions/sqrt.js
Normal file
@@ -0,0 +1,130 @@
|
||||
// @flow
|
||||
import defineFunction from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import domTree from "../domTree";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import delimiter from "../delimiter";
|
||||
import Style from "../Style";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
|
||||
defineFunction({
|
||||
type: "sqrt",
|
||||
names: ["\\sqrt"],
|
||||
props: {
|
||||
numArgs: 1,
|
||||
numOptionalArgs: 1,
|
||||
},
|
||||
handler(context, args, optArgs) {
|
||||
const index = optArgs[0];
|
||||
const body = args[0];
|
||||
return {
|
||||
type: "sqrt",
|
||||
body: body,
|
||||
index: index,
|
||||
};
|
||||
},
|
||||
htmlBuilder(group, options) {
|
||||
// Square roots are handled in the TeXbook pg. 443, Rule 11.
|
||||
|
||||
// First, we do the same steps as in overline to build the inner group
|
||||
// and line
|
||||
let inner = html.buildGroup(group.value.body, options.havingCrampedStyle());
|
||||
if (inner.height === 0) {
|
||||
// Render a small surd.
|
||||
inner.height = options.fontMetrics().xHeight;
|
||||
}
|
||||
|
||||
// Some groups can return document fragments. Handle those by wrapping
|
||||
// them in a span.
|
||||
if (inner instanceof domTree.documentFragment) {
|
||||
inner = buildCommon.makeSpan([], [inner], options);
|
||||
}
|
||||
|
||||
// Calculate the minimum size for the \surd delimiter
|
||||
const metrics = options.fontMetrics();
|
||||
const theta = metrics.defaultRuleThickness;
|
||||
|
||||
let phi = theta;
|
||||
if (options.style.id < Style.TEXT.id) {
|
||||
phi = options.fontMetrics().xHeight;
|
||||
}
|
||||
|
||||
// Calculate the clearance between the body and line
|
||||
let lineClearance = theta + phi / 4;
|
||||
|
||||
const minDelimiterHeight = (inner.height + inner.depth +
|
||||
lineClearance + theta) * options.sizeMultiplier;
|
||||
|
||||
// Create a sqrt SVG of the required minimum size
|
||||
const {span: img, ruleWidth} =
|
||||
delimiter.sqrtImage(minDelimiterHeight, options);
|
||||
|
||||
const delimDepth = img.height - ruleWidth;
|
||||
|
||||
// Adjust the clearance based on the delimiter size
|
||||
if (delimDepth > inner.height + inner.depth + lineClearance) {
|
||||
lineClearance =
|
||||
(lineClearance + delimDepth - inner.height - inner.depth) / 2;
|
||||
}
|
||||
|
||||
// Shift the sqrt image
|
||||
const imgShift = img.height - inner.height - lineClearance - ruleWidth;
|
||||
|
||||
inner.style.paddingLeft = img.advanceWidth + "em";
|
||||
|
||||
// Overlay the image and the argument.
|
||||
const body = buildCommon.makeVList({
|
||||
positionType: "firstBaseline",
|
||||
children: [
|
||||
{type: "elem", elem: inner, wrapperClasses: ["svg-align"]},
|
||||
{type: "kern", size: -(inner.height + imgShift)},
|
||||
{type: "elem", elem: img},
|
||||
{type: "kern", size: ruleWidth},
|
||||
],
|
||||
}, options);
|
||||
|
||||
if (!group.value.index) {
|
||||
return buildCommon.makeSpan(["mord", "sqrt"], [body], options);
|
||||
} else {
|
||||
// Handle the optional root index
|
||||
|
||||
// The index is always in scriptscript style
|
||||
const newOptions = options.havingStyle(Style.SCRIPTSCRIPT);
|
||||
const rootm = html.buildGroup(group.value.index, newOptions, options);
|
||||
|
||||
// The amount the index is shifted by. This is taken from the TeX
|
||||
// source, in the definition of `\r@@t`.
|
||||
const toShift = 0.6 * (body.height - body.depth);
|
||||
|
||||
// Build a VList with the superscript shifted up correctly
|
||||
const rootVList = buildCommon.makeVList({
|
||||
positionType: "shift",
|
||||
positionData: -toShift,
|
||||
children: [{type: "elem", elem: rootm}],
|
||||
}, options);
|
||||
// Add a class surrounding it so we can add on the appropriate
|
||||
// kerning
|
||||
const rootVListWrap = buildCommon.makeSpan(["root"], [rootVList]);
|
||||
|
||||
return buildCommon.makeSpan(["mord", "sqrt"],
|
||||
[rootVListWrap, body], options);
|
||||
}
|
||||
},
|
||||
mathmlBuilder(group, options) {
|
||||
let node;
|
||||
if (group.value.index) {
|
||||
node = new mathMLTree.MathNode(
|
||||
"mroot", [
|
||||
mml.buildGroup(group.value.body, options),
|
||||
mml.buildGroup(group.value.index, options),
|
||||
]);
|
||||
} else {
|
||||
node = new mathMLTree.MathNode(
|
||||
"msqrt", [mml.buildGroup(group.value.body, options)]);
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
});
|
55
src/functions/verb.js
Normal file
55
src/functions/verb.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// @flow
|
||||
import defineFunction from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import ParseError from "../ParseError";
|
||||
|
||||
defineFunction({
|
||||
type: "verb",
|
||||
names: ["\\verb"],
|
||||
props: {
|
||||
numArgs: 0,
|
||||
allowedInText: true,
|
||||
},
|
||||
handler(context, args, optArgs) {
|
||||
// \verb and \verb* are dealt with directly in Parser.js.
|
||||
// If we end up here, it's because of a failure to match the two delimiters
|
||||
// in the regex in Lexer.js. LaTeX raises the following error when \verb is
|
||||
// terminated by end of line (or file).
|
||||
throw new ParseError(
|
||||
"\\verb ended by end of line instead of matching delimiter");
|
||||
},
|
||||
htmlBuilder(group, options) {
|
||||
const text = buildCommon.makeVerb(group, options);
|
||||
const body = [];
|
||||
// \verb enters text mode and therefore is sized like \textstyle
|
||||
const newOptions = options.havingStyle(options.style.text());
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (text[i] === '\xA0') { // spaces appear as nonbreaking space
|
||||
// The space character isn't in the Typewriter-Regular font,
|
||||
// so we implement it as a kern of the same size as a character.
|
||||
// 0.525 is the width of a texttt character in LaTeX.
|
||||
// It automatically gets scaled by the font size.
|
||||
const rule = buildCommon.makeSpan(["mord", "rule"], [], newOptions);
|
||||
rule.style.marginLeft = "0.525em";
|
||||
body.push(rule);
|
||||
} else {
|
||||
body.push(buildCommon.makeSymbol(text[i], "Typewriter-Regular",
|
||||
group.mode, newOptions, ["mathtt"]));
|
||||
}
|
||||
}
|
||||
buildCommon.tryCombineChars(body);
|
||||
return buildCommon.makeSpan(
|
||||
["mord", "text"].concat(newOptions.sizingClasses(options)),
|
||||
// tryCombinChars expects CombinableDomNode[] while makeSpan expects
|
||||
// DomChildNode[].
|
||||
// $FlowFixMe: CombinableDomNode[] is not compatible with DomChildNode[]
|
||||
body, newOptions);
|
||||
},
|
||||
mathmlBuilder(group, options) {
|
||||
const text = new mathMLTree.TextNode(buildCommon.makeVerb(group, options));
|
||||
const node = new mathMLTree.MathNode("mtext", [text]);
|
||||
node.setAttribute("mathvariant", buildCommon.fontMap["mathtt"].variant);
|
||||
return node;
|
||||
},
|
||||
});
|
Reference in New Issue
Block a user