mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-07 12:18:39 +00:00
extract delimsizing functions into their own file
This commit is contained in:
committed by
Kevin Barabash
parent
8bdc5e3e6a
commit
a99c7c9e0f
103
src/buildHTML.js
103
src/buildHTML.js
@@ -52,7 +52,7 @@ const isBinRightCanceller = function(node, isRealGroup) {
|
||||
* the spliced-out array. Returns null if `children[i]` does not exist or is not
|
||||
* a space.
|
||||
*/
|
||||
const spliceSpaces = function(children, i) {
|
||||
export const spliceSpaces = function(children, i) {
|
||||
let j = i;
|
||||
while (j < children.length && isSpace(children[j])) {
|
||||
j++;
|
||||
@@ -246,7 +246,7 @@ const isCharacterBox = function(group) {
|
||||
baseElem.type === "punct";
|
||||
};
|
||||
|
||||
const makeNullDelimiter = function(options, classes) {
|
||||
export const makeNullDelimiter = function(options, classes) {
|
||||
const moreClasses = ["nulldelimiter"].concat(options.baseSizingClasses());
|
||||
return makeSpan(classes.concat(moreClasses));
|
||||
};
|
||||
@@ -1259,105 +1259,6 @@ groupTypes.font = function(group, options) {
|
||||
return buildGroup(group.value.body, options.withFont(font));
|
||||
};
|
||||
|
||||
groupTypes.delimsizing = function(group, options) {
|
||||
const delim = group.value.value;
|
||||
|
||||
if (delim === ".") {
|
||||
// Empty delimiters still count as elements, even though they don't
|
||||
// show anything.
|
||||
return makeSpan([group.value.mclass]);
|
||||
}
|
||||
|
||||
// Use delimiter.sizedDelim to generate the delimiter.
|
||||
return delimiter.sizedDelim(
|
||||
delim, group.value.size, options, group.mode,
|
||||
[group.value.mclass]);
|
||||
};
|
||||
|
||||
groupTypes.leftright = function(group, options) {
|
||||
// Build the inner expression
|
||||
const inner = buildExpression(group.value.body, options, true);
|
||||
|
||||
let innerHeight = 0;
|
||||
let innerDepth = 0;
|
||||
let hadMiddle = false;
|
||||
|
||||
// Calculate its height and depth
|
||||
for (let i = 0; i < inner.length; i++) {
|
||||
if (inner[i].isMiddle) {
|
||||
hadMiddle = true;
|
||||
} else {
|
||||
innerHeight = Math.max(inner[i].height, innerHeight);
|
||||
innerDepth = Math.max(inner[i].depth, innerDepth);
|
||||
}
|
||||
}
|
||||
|
||||
// The size of delimiters is the same, regardless of what style we are
|
||||
// in. Thus, to correctly calculate the size of delimiter we need around
|
||||
// a group, we scale down the inner size based on the size.
|
||||
innerHeight *= options.sizeMultiplier;
|
||||
innerDepth *= options.sizeMultiplier;
|
||||
|
||||
let leftDelim;
|
||||
if (group.value.left === ".") {
|
||||
// Empty delimiters in \left and \right make null delimiter spaces.
|
||||
leftDelim = makeNullDelimiter(options, ["mopen"]);
|
||||
} else {
|
||||
// Otherwise, use leftRightDelim to generate the correct sized
|
||||
// delimiter.
|
||||
leftDelim = delimiter.leftRightDelim(
|
||||
group.value.left, innerHeight, innerDepth, options,
|
||||
group.mode, ["mopen"]);
|
||||
}
|
||||
// Add it to the beginning of the expression
|
||||
inner.unshift(leftDelim);
|
||||
|
||||
// Handle middle delimiters
|
||||
if (hadMiddle) {
|
||||
for (let i = 1; i < inner.length; i++) {
|
||||
const middleDelim = inner[i];
|
||||
if (middleDelim.isMiddle) {
|
||||
// Apply the options that were active when \middle was called
|
||||
inner[i] = delimiter.leftRightDelim(
|
||||
middleDelim.isMiddle.value, innerHeight, innerDepth,
|
||||
middleDelim.isMiddle.options, group.mode, []);
|
||||
// Add back spaces shifted into the delimiter
|
||||
const spaces = spliceSpaces(middleDelim.children, 0);
|
||||
if (spaces) {
|
||||
buildCommon.prependChildren(inner[i], spaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rightDelim;
|
||||
// Same for the right delimiter
|
||||
if (group.value.right === ".") {
|
||||
rightDelim = makeNullDelimiter(options, ["mclose"]);
|
||||
} else {
|
||||
rightDelim = delimiter.leftRightDelim(
|
||||
group.value.right, innerHeight, innerDepth, options,
|
||||
group.mode, ["mclose"]);
|
||||
}
|
||||
// Add it to the end of the expression.
|
||||
inner.push(rightDelim);
|
||||
|
||||
return makeSpan(["minner"], inner, options);
|
||||
};
|
||||
|
||||
groupTypes.middle = function(group, options) {
|
||||
let middleDelim;
|
||||
if (group.value.value === ".") {
|
||||
middleDelim = makeNullDelimiter(options, []);
|
||||
} else {
|
||||
middleDelim = delimiter.sizedDelim(
|
||||
group.value.value, 1, options,
|
||||
group.mode, []);
|
||||
middleDelim.isMiddle = {value: group.value.value, options: options};
|
||||
}
|
||||
return middleDelim;
|
||||
};
|
||||
|
||||
groupTypes.rule = function(group, options) {
|
||||
// Make an empty span for the rule
|
||||
const rule = makeSpan(["mord", "rule"], [], options);
|
||||
|
@@ -17,7 +17,7 @@ import stretchy from "./stretchy";
|
||||
* Takes a symbol and converts it into a MathML text node after performing
|
||||
* optional replacement from symbols.js.
|
||||
*/
|
||||
const makeText = function(text, mode) {
|
||||
export const makeText = function(text, mode) {
|
||||
if (symbols[mode][text] && symbols[mode][text].replace) {
|
||||
text = symbols[mode][text].replace;
|
||||
}
|
||||
@@ -315,39 +315,6 @@ groupTypes.sqrt = function(group, options) {
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.leftright = function(group, options) {
|
||||
const inner = buildExpression(group.value.body, options);
|
||||
|
||||
if (group.value.left !== ".") {
|
||||
const leftNode = new mathMLTree.MathNode(
|
||||
"mo", [makeText(group.value.left, group.mode)]);
|
||||
|
||||
leftNode.setAttribute("fence", "true");
|
||||
|
||||
inner.unshift(leftNode);
|
||||
}
|
||||
|
||||
if (group.value.right !== ".") {
|
||||
const rightNode = new mathMLTree.MathNode(
|
||||
"mo", [makeText(group.value.right, group.mode)]);
|
||||
|
||||
rightNode.setAttribute("fence", "true");
|
||||
|
||||
inner.push(rightNode);
|
||||
}
|
||||
|
||||
const outerNode = new mathMLTree.MathNode("mrow", inner);
|
||||
|
||||
return outerNode;
|
||||
};
|
||||
|
||||
groupTypes.middle = function(group, options) {
|
||||
const middleNode = new mathMLTree.MathNode(
|
||||
"mo", [makeText(group.value.middle, group.mode)]);
|
||||
middleNode.setAttribute("fence", "true");
|
||||
return middleNode;
|
||||
};
|
||||
|
||||
groupTypes.accent = function(group, options) {
|
||||
let accentNode;
|
||||
if (group.value.isStretchy) {
|
||||
@@ -445,29 +412,6 @@ groupTypes.font = function(group, options) {
|
||||
return buildGroup(group.value.body, options.withFont(font));
|
||||
};
|
||||
|
||||
groupTypes.delimsizing = function(group) {
|
||||
const children = [];
|
||||
|
||||
if (group.value.value !== ".") {
|
||||
children.push(makeText(group.value.value, group.mode));
|
||||
}
|
||||
|
||||
const node = new mathMLTree.MathNode("mo", children);
|
||||
|
||||
if (group.value.mclass === "mopen" ||
|
||||
group.value.mclass === "mclose") {
|
||||
// Only some of the delimsizing functions act as fences, and they
|
||||
// return "mopen" or "mclose" mclass.
|
||||
node.setAttribute("fence", "true");
|
||||
} else {
|
||||
// Explicitly disable fencing if it's not a fence, to override the
|
||||
// defaults.
|
||||
node.setAttribute("fence", "false");
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
groupTypes.styling = function(group, options) {
|
||||
// Figure out what style we're changing to.
|
||||
// TODO(kevinb): dedupe this with buildHTML.js
|
||||
|
@@ -193,41 +193,6 @@ defineFunction(["\\pod", "\\pmod", "\\mod"], {
|
||||
};
|
||||
});
|
||||
|
||||
// Extra data needed for the delimiter handler down below
|
||||
const delimiterSizes = {
|
||||
"\\bigl" : {mclass: "mopen", size: 1},
|
||||
"\\Bigl" : {mclass: "mopen", size: 2},
|
||||
"\\biggl": {mclass: "mopen", size: 3},
|
||||
"\\Biggl": {mclass: "mopen", size: 4},
|
||||
"\\bigr" : {mclass: "mclose", size: 1},
|
||||
"\\Bigr" : {mclass: "mclose", size: 2},
|
||||
"\\biggr": {mclass: "mclose", size: 3},
|
||||
"\\Biggr": {mclass: "mclose", size: 4},
|
||||
"\\bigm" : {mclass: "mrel", size: 1},
|
||||
"\\Bigm" : {mclass: "mrel", size: 2},
|
||||
"\\biggm": {mclass: "mrel", size: 3},
|
||||
"\\Biggm": {mclass: "mrel", size: 4},
|
||||
"\\big" : {mclass: "mord", size: 1},
|
||||
"\\Big" : {mclass: "mord", size: 2},
|
||||
"\\bigg" : {mclass: "mord", size: 3},
|
||||
"\\Bigg" : {mclass: "mord", size: 4},
|
||||
};
|
||||
|
||||
const delimiters = [
|
||||
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
||||
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
||||
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
||||
"<", ">", "\\langle", "\\rangle", "\\lt", "\\gt",
|
||||
"\\lvert", "\\rvert", "\\lVert", "\\rVert",
|
||||
"\\lgroup", "\\rgroup", "\\lmoustache", "\\rmoustache",
|
||||
"/", "\\backslash",
|
||||
"|", "\\vert", "\\|", "\\Vert",
|
||||
"\\uparrow", "\\Uparrow",
|
||||
"\\downarrow", "\\Downarrow",
|
||||
"\\updownarrow", "\\Updownarrow",
|
||||
".",
|
||||
];
|
||||
|
||||
const fontAliases = {
|
||||
"\\Bbb": "\\mathbb",
|
||||
"\\bold": "\\mathbf",
|
||||
@@ -453,63 +418,7 @@ defineFunction("\\smash", {
|
||||
};
|
||||
});
|
||||
|
||||
// Delimiter functions
|
||||
const checkDelimiter = function(delim, context) {
|
||||
if (utils.contains(delimiters, delim.value)) {
|
||||
return delim;
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Invalid delimiter: '" + delim.value + "' after '" +
|
||||
context.funcName + "'", delim);
|
||||
}
|
||||
};
|
||||
|
||||
defineFunction([
|
||||
"\\bigl", "\\Bigl", "\\biggl", "\\Biggl",
|
||||
"\\bigr", "\\Bigr", "\\biggr", "\\Biggr",
|
||||
"\\bigm", "\\Bigm", "\\biggm", "\\Biggm",
|
||||
"\\big", "\\Big", "\\bigg", "\\Bigg",
|
||||
], {
|
||||
numArgs: 1,
|
||||
}, function(context, args) {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
|
||||
return {
|
||||
type: "delimsizing",
|
||||
size: delimiterSizes[context.funcName].size,
|
||||
mclass: delimiterSizes[context.funcName].mclass,
|
||||
value: delim.value,
|
||||
};
|
||||
});
|
||||
|
||||
defineFunction([
|
||||
"\\left", "\\right",
|
||||
], {
|
||||
numArgs: 1,
|
||||
}, function(context, args) {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
|
||||
// \left and \right are caught somewhere in Parser.js, which is
|
||||
// why this data doesn't match what is in buildHTML.
|
||||
return {
|
||||
type: "leftright",
|
||||
value: delim.value,
|
||||
};
|
||||
});
|
||||
|
||||
defineFunction("\\middle", {
|
||||
numArgs: 1,
|
||||
}, function(context, args) {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
if (!context.parser.leftrightDepth) {
|
||||
throw new ParseError("\\middle without preceding \\left", delim);
|
||||
}
|
||||
|
||||
return {
|
||||
type: "middle",
|
||||
value: delim.value,
|
||||
};
|
||||
});
|
||||
import "./functions/delimsizing";
|
||||
|
||||
// Sizing functions (handled in Parser.js explicitly, hence no handler)
|
||||
defineFunction([
|
||||
|
263
src/functions/delimsizing.js
Normal file
263
src/functions/delimsizing.js
Normal file
@@ -0,0 +1,263 @@
|
||||
import buildCommon, {makeSpan} from "../buildCommon";
|
||||
import defineFunction from "../defineFunction";
|
||||
import delimiter from "../delimiter";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import ParseError from "../ParseError";
|
||||
import utils from "../utils";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
|
||||
// Extra data needed for the delimiter handler down below
|
||||
const delimiterSizes = {
|
||||
"\\bigl" : {mclass: "mopen", size: 1},
|
||||
"\\Bigl" : {mclass: "mopen", size: 2},
|
||||
"\\biggl": {mclass: "mopen", size: 3},
|
||||
"\\Biggl": {mclass: "mopen", size: 4},
|
||||
"\\bigr" : {mclass: "mclose", size: 1},
|
||||
"\\Bigr" : {mclass: "mclose", size: 2},
|
||||
"\\biggr": {mclass: "mclose", size: 3},
|
||||
"\\Biggr": {mclass: "mclose", size: 4},
|
||||
"\\bigm" : {mclass: "mrel", size: 1},
|
||||
"\\Bigm" : {mclass: "mrel", size: 2},
|
||||
"\\biggm": {mclass: "mrel", size: 3},
|
||||
"\\Biggm": {mclass: "mrel", size: 4},
|
||||
"\\big" : {mclass: "mord", size: 1},
|
||||
"\\Big" : {mclass: "mord", size: 2},
|
||||
"\\bigg" : {mclass: "mord", size: 3},
|
||||
"\\Bigg" : {mclass: "mord", size: 4},
|
||||
};
|
||||
|
||||
const delimiters = [
|
||||
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
||||
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
||||
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
||||
"<", ">", "\\langle", "\\rangle", "\\lt", "\\gt",
|
||||
"\\lvert", "\\rvert", "\\lVert", "\\rVert",
|
||||
"\\lgroup", "\\rgroup", "\\lmoustache", "\\rmoustache",
|
||||
"/", "\\backslash",
|
||||
"|", "\\vert", "\\|", "\\Vert",
|
||||
"\\uparrow", "\\Uparrow",
|
||||
"\\downarrow", "\\Downarrow",
|
||||
"\\updownarrow", "\\Updownarrow",
|
||||
".",
|
||||
];
|
||||
|
||||
// Delimiter functions
|
||||
const checkDelimiter = function(delim, context) {
|
||||
if (utils.contains(delimiters, delim.value)) {
|
||||
return delim;
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Invalid delimiter: '" + delim.value + "' after '" +
|
||||
context.funcName + "'", delim);
|
||||
}
|
||||
};
|
||||
|
||||
defineFunction(
|
||||
[
|
||||
"\\bigl", "\\Bigl", "\\biggl", "\\Biggl",
|
||||
"\\bigr", "\\Bigr", "\\biggr", "\\Biggr",
|
||||
"\\bigm", "\\Bigm", "\\biggm", "\\Biggm",
|
||||
"\\big", "\\Big", "\\bigg", "\\Bigg",
|
||||
],
|
||||
{
|
||||
numArgs: 1,
|
||||
},
|
||||
function(context, args) {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
|
||||
return {
|
||||
type: "delimsizing",
|
||||
size: delimiterSizes[context.funcName].size,
|
||||
mclass: delimiterSizes[context.funcName].mclass,
|
||||
value: delim.value,
|
||||
};
|
||||
},
|
||||
"delimsizing",
|
||||
function(group, options) {
|
||||
const delim = group.value.value;
|
||||
|
||||
if (delim === ".") {
|
||||
// Empty delimiters still count as elements, even though they don't
|
||||
// show anything.
|
||||
return makeSpan([group.value.mclass]);
|
||||
}
|
||||
|
||||
// Use delimiter.sizedDelim to generate the delimiter.
|
||||
return delimiter.sizedDelim(
|
||||
delim, group.value.size, options, group.mode,
|
||||
[group.value.mclass]);
|
||||
},
|
||||
function(group) {
|
||||
const children = [];
|
||||
|
||||
if (group.value.value !== ".") {
|
||||
children.push(mml.makeText(group.value.value, group.mode));
|
||||
}
|
||||
|
||||
const node = new mathMLTree.MathNode("mo", children);
|
||||
|
||||
if (group.value.mclass === "mopen" ||
|
||||
group.value.mclass === "mclose") {
|
||||
// Only some of the delimsizing functions act as fences, and they
|
||||
// return "mopen" or "mclose" mclass.
|
||||
node.setAttribute("fence", "true");
|
||||
} else {
|
||||
// Explicitly disable fencing if it's not a fence, to override the
|
||||
// defaults.
|
||||
node.setAttribute("fence", "false");
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
);
|
||||
|
||||
defineFunction(
|
||||
[
|
||||
"\\left", "\\right",
|
||||
], {
|
||||
numArgs: 1,
|
||||
}, function(context, args) {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
|
||||
// \left and \right are caught somewhere in Parser.js, which is
|
||||
// why this data doesn't match what is in buildHTML.
|
||||
return {
|
||||
type: "leftright",
|
||||
value: delim.value,
|
||||
};
|
||||
},
|
||||
"leftright",
|
||||
function(group, options) {
|
||||
// Build the inner expression
|
||||
const inner = html.buildExpression(group.value.body, options, true);
|
||||
|
||||
let innerHeight = 0;
|
||||
let innerDepth = 0;
|
||||
let hadMiddle = false;
|
||||
|
||||
// Calculate its height and depth
|
||||
for (let i = 0; i < inner.length; i++) {
|
||||
if (inner[i].isMiddle) {
|
||||
hadMiddle = true;
|
||||
} else {
|
||||
innerHeight = Math.max(inner[i].height, innerHeight);
|
||||
innerDepth = Math.max(inner[i].depth, innerDepth);
|
||||
}
|
||||
}
|
||||
|
||||
// The size of delimiters is the same, regardless of what style we are
|
||||
// in. Thus, to correctly calculate the size of delimiter we need around
|
||||
// a group, we scale down the inner size based on the size.
|
||||
innerHeight *= options.sizeMultiplier;
|
||||
innerDepth *= options.sizeMultiplier;
|
||||
|
||||
let leftDelim;
|
||||
if (group.value.left === ".") {
|
||||
// Empty delimiters in \left and \right make null delimiter spaces.
|
||||
leftDelim = html.makeNullDelimiter(options, ["mopen"]);
|
||||
} else {
|
||||
// Otherwise, use leftRightDelim to generate the correct sized
|
||||
// delimiter.
|
||||
leftDelim = delimiter.leftRightDelim(
|
||||
group.value.left, innerHeight, innerDepth, options,
|
||||
group.mode, ["mopen"]);
|
||||
}
|
||||
// Add it to the beginning of the expression
|
||||
inner.unshift(leftDelim);
|
||||
|
||||
// Handle middle delimiters
|
||||
if (hadMiddle) {
|
||||
for (let i = 1; i < inner.length; i++) {
|
||||
const middleDelim = inner[i];
|
||||
if (middleDelim.isMiddle) {
|
||||
// Apply the options that were active when \middle was called
|
||||
inner[i] = delimiter.leftRightDelim(
|
||||
middleDelim.isMiddle.value, innerHeight, innerDepth,
|
||||
middleDelim.isMiddle.options, group.mode, []);
|
||||
// Add back spaces shifted into the delimiter
|
||||
const spaces = html.spliceSpaces(middleDelim.children, 0);
|
||||
if (spaces) {
|
||||
buildCommon.prependChildren(inner[i], spaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rightDelim;
|
||||
// Same for the right delimiter
|
||||
if (group.value.right === ".") {
|
||||
rightDelim = html.makeNullDelimiter(options, ["mclose"]);
|
||||
} else {
|
||||
rightDelim = delimiter.leftRightDelim(
|
||||
group.value.right, innerHeight, innerDepth, options,
|
||||
group.mode, ["mclose"]);
|
||||
}
|
||||
// Add it to the end of the expression.
|
||||
inner.push(rightDelim);
|
||||
|
||||
return makeSpan(["minner"], inner, options);
|
||||
},
|
||||
function(group, options) {
|
||||
const inner = mml.buildExpression(group.value.body, options);
|
||||
|
||||
if (group.value.left !== ".") {
|
||||
const leftNode = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value.left, group.mode)]);
|
||||
|
||||
leftNode.setAttribute("fence", "true");
|
||||
|
||||
inner.unshift(leftNode);
|
||||
}
|
||||
|
||||
if (group.value.right !== ".") {
|
||||
const rightNode = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value.right, group.mode)]);
|
||||
|
||||
rightNode.setAttribute("fence", "true");
|
||||
|
||||
inner.push(rightNode);
|
||||
}
|
||||
|
||||
const outerNode = new mathMLTree.MathNode("mrow", inner);
|
||||
|
||||
return outerNode;
|
||||
},
|
||||
);
|
||||
|
||||
defineFunction(
|
||||
"\\middle",
|
||||
{
|
||||
numArgs: 1,
|
||||
}, function(context, args) {
|
||||
const delim = checkDelimiter(args[0], context);
|
||||
if (!context.parser.leftrightDepth) {
|
||||
throw new ParseError("\\middle without preceding \\left", delim);
|
||||
}
|
||||
|
||||
return {
|
||||
type: "middle",
|
||||
value: delim.value,
|
||||
};
|
||||
},
|
||||
"middle",
|
||||
function(group, options) {
|
||||
let middleDelim;
|
||||
if (group.value.value === ".") {
|
||||
middleDelim = html.makeNullDelimiter(options, []);
|
||||
} else {
|
||||
middleDelim = delimiter.sizedDelim(
|
||||
group.value.value, 1, options,
|
||||
group.mode, []);
|
||||
middleDelim.isMiddle = {value: group.value.value, options: options};
|
||||
}
|
||||
return middleDelim;
|
||||
},
|
||||
function(group, options) {
|
||||
const middleNode = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value.middle, group.mode)]);
|
||||
middleNode.setAttribute("fence", "true");
|
||||
return middleNode;
|
||||
},
|
||||
);
|
Reference in New Issue
Block a user