group all \phantom related code together

This commit is contained in:
Kevin Barabash
2017-07-17 23:12:27 -07:00
committed by Kevin Barabash
parent 092aa0c767
commit 8bdc5e3e6a
5 changed files with 245 additions and 193 deletions

View File

@@ -71,7 +71,7 @@ const spliceSpaces = function(children, i) {
* is a real group (no atoms will be added on either side), as opposed to
* a partial group (e.g. one created by \color).
*/
const buildExpression = function(expression, options, isRealGroup) {
export const buildExpression = function(expression, options, isRealGroup) {
// Parse expressions into `groups`.
const groups = [];
for (let i = 0; i < expression.length; i++) {
@@ -255,7 +255,7 @@ const makeNullDelimiter = function(options, classes) {
* This is a map of group types to the function used to handle that type.
* Simpler types come at the beginning, while complicated types come afterwards.
*/
const groupTypes = {};
export const groupTypes = {};
groupTypes.mathord = function(group, options) {
return buildCommon.makeOrd(group, options, "mathord");
@@ -1723,46 +1723,6 @@ groupTypes.xArrow = function(group, options) {
return makeSpan(["mrel", "x-arrow"], [vlist], options);
};
groupTypes.phantom = function(group, options) {
const elements = buildExpression(
group.value.value,
options.withPhantom(),
false
);
// \phantom isn't supposed to affect the elements it contains.
// See "color" for more details.
return new buildCommon.makeFragment(elements);
};
groupTypes.hphantom = function(group, options) {
let node = makeSpan(
[], [buildGroup(group.value.body, options.withPhantom())]);
node.height = 0;
node.depth = 0;
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
node.children[i].height = 0;
node.children[i].depth = 0;
}
}
// See smash for comment re: use of makeVList
node = buildCommon.makeVList([
{type: "elem", elem: node},
], "firstBaseline", null, options);
return node;
};
groupTypes.vphantom = function(group, options) {
const inner = makeSpan(
["inner"], [buildGroup(group.value.body, options.withPhantom())]);
const fix = makeSpan(["fix"], []);
return makeSpan(
["mord", "rlap"], [inner, fix], options);
};
groupTypes.mclass = function(group, options) {
const elements = buildExpression(group.value.value, options, true);
@@ -1774,7 +1734,7 @@ groupTypes.mclass = function(group, options) {
* function for it. It also handles the interaction of size and style changes
* between parents and children.
*/
const buildGroup = function(group, options, baseOptions) {
export const buildGroup = function(group, options, baseOptions) {
if (!group) {
return makeSpan();
}
@@ -1807,7 +1767,7 @@ const buildGroup = function(group, options, baseOptions) {
* Take an entire parse tree, and build it into an appropriate set of HTML
* nodes.
*/
const buildHTML = function(tree, options) {
export default function buildHTML(tree, options) {
// buildExpression is destructive, so we need to make a clone
// of the incoming tree so that it isn't accidentally changed
tree = JSON.parse(JSON.stringify(tree));
@@ -1835,6 +1795,4 @@ const buildHTML = function(tree, options) {
htmlNode.setAttribute("aria-hidden", "true");
return htmlNode;
};
module.exports = buildHTML;
}

View File

@@ -60,7 +60,7 @@ const getVariant = function(group, options) {
* Functions for handling the different types of groups found in the parse
* tree. Each function should take a parse group and return a MathML node.
*/
const groupTypes = {};
export const groupTypes = {};
const defaultVariant = {
"mi": "italic",
@@ -650,25 +650,6 @@ groupTypes.smash = function(group, options) {
return node;
};
groupTypes.phantom = function(group, options) {
const inner = buildExpression(group.value.value, options);
return new mathMLTree.MathNode("mphantom", inner);
};
groupTypes.hphantom = function(group, options) {
const inner = buildExpression(group.value.value, options);
const node = new mathMLTree.MathNode("mphantom", inner);
node.setAttribute("height", "0px");
return node;
};
groupTypes.vphantom = function(group, options) {
const inner = buildExpression(group.value.value, options);
const node = new mathMLTree.MathNode("mphantom", inner);
node.setAttribute("width", "0px");
return node;
};
groupTypes.mclass = function(group, options) {
const inner = buildExpression(group.value.value, options);
return new mathMLTree.MathNode("mstyle", inner);
@@ -679,7 +660,7 @@ groupTypes.mclass = function(group, options) {
* MathML nodes. A little simpler than the HTML version because we don't do any
* previous-node handling.
*/
const buildExpression = function(expression, options) {
export const buildExpression = function(expression, options) {
const groups = [];
for (let i = 0; i < expression.length; i++) {
const group = expression[i];
@@ -695,8 +676,9 @@ const buildExpression = function(expression, options) {
* Takes a group from the parser and calls the appropriate groupTypes function
* on it to produce a MathML node.
*/
// TODO(kevinb): determine if removeUnnecessaryRow should always be true
const buildGroup = function(group, options, removeUnnecessaryRow = false) {
export const buildGroup = function(
group, options, removeUnnecessaryRow = false,
) {
if (!group) {
return new mathMLTree.MathNode("mrow");
}
@@ -724,7 +706,7 @@ const buildGroup = function(group, options, removeUnnecessaryRow = false) {
* Note that we actually return a domTree element with a `<math>` inside it so
* we can do appropriate styling.
*/
const buildMathML = function(tree, texExpression, options) {
export default function buildMathML(tree, texExpression, options) {
const expression = buildExpression(tree, options);
// Wrap up the expression in an mrow so it is presented in the semantics
@@ -744,6 +726,4 @@ const buildMathML = function(tree, texExpression, options) {
// You can't style <math> nodes, so we wrap the node in a span.
return makeSpan(["katex-mathml"], [math]);
};
module.exports = buildMathML;
}

123
src/defineFunction.js Normal file
View File

@@ -0,0 +1,123 @@
import functions from "./functions";
import {groupTypes as htmlGroupTypes} from "./buildHTML";
import {groupTypes as mathmlGroupTypes} from "./buildMathML";
/* This file contains a list of functions that we parse, identified by
* the calls to defineFunction.
*
* The first argument to defineFunction is a single name or a list of names.
* All functions named in such a list will share a single implementation.
*
* Each declared function can have associated properties, which
* include the following:
*
* - numArgs: The number of arguments the function takes.
* If this is the only property, it can be passed as a number
* instead of an element of a properties object.
* - argTypes: (optional) An array corresponding to each argument of the
* function, giving the type of argument that should be parsed. Its
* length should be equal to `numArgs + numOptionalArgs`. Valid
* types:
* - "size": A size-like thing, such as "1em" or "5ex"
* - "color": An html color, like "#abc" or "blue"
* - "original": The same type as the environment that the
* function being parsed is in (e.g. used for the
* bodies of functions like \textcolor where the
* first argument is special and the second
* argument is parsed normally)
* Other possible types (probably shouldn't be used)
* - "text": Text-like (e.g. \text)
* - "math": Normal math
* If undefined, this will be treated as an appropriate length
* array of "original" strings
* - greediness: (optional) The greediness of the function to use ungrouped
* arguments.
*
* E.g. if you have an expression
* \sqrt \frac 1 2
* since \frac has greediness=2 vs \sqrt's greediness=1, \frac
* will use the two arguments '1' and '2' as its two arguments,
* then that whole function will be used as the argument to
* \sqrt. On the other hand, the expressions
* \frac \frac 1 2 3
* and
* \frac \sqrt 1 2
* will fail because \frac and \frac have equal greediness
* and \sqrt has a lower greediness than \frac respectively. To
* make these parse, we would have to change them to:
* \frac {\frac 1 2} 3
* and
* \frac {\sqrt 1} 2
*
* The default value is `1`
* - allowedInText: (optional) Whether or not the function is allowed inside
* text mode (default false)
* - numOptionalArgs: (optional) The number of optional arguments the function
* should parse. If the optional arguments aren't found,
* `null` will be passed to the handler in their place.
* (default 0)
* - infix: (optional) Must be true if the function is an infix operator.
*
* The last argument is that implementation, the handler for the function(s).
* It is called to handle these functions and their arguments.
* It receives two arguments:
* - context contains information and references provided by the parser
* - args is an array of arguments obtained from TeX input
* The context contains the following properties:
* - funcName: the text (i.e. name) of the function, including \
* - parser: the parser object
* - lexer: the lexer object
* - positions: the positions in the overall string of the function
* and the arguments.
* The latter three should only be used to produce error messages.
*
* The function should return an object with the following keys:
* - type: The type of element that this is. This is then used in
* buildHTML/buildMathML to determine which function
* should be called to build this node into a DOM node
* Any other data can be added to the object, which will be passed
* in to the function in buildHTML/buildMathML as `group.value`.
*/
export default function defineFunction(
names, props, handler, type, htmlBuilder, mathmlBuilder
) {
if (typeof names === "string") {
names = [names];
}
if (typeof props === "number") {
props = { numArgs: props };
}
// Set default values of functions
const data = {
numArgs: props.numArgs,
argTypes: props.argTypes,
greediness: (props.greediness === undefined) ? 1 : props.greediness,
allowedInText: !!props.allowedInText,
allowedInMath: props.allowedInMath,
numOptionalArgs: props.numOptionalArgs || 0,
infix: !!props.infix,
handler: handler,
};
for (let i = 0; i < names.length; ++i) {
functions[names[i]] = data;
}
if (type) {
if (htmlBuilder) {
htmlGroupTypes[type] = htmlBuilder;
}
if (mathmlBuilder) {
mathmlGroupTypes[type] = mathmlBuilder;
}
}
}
// Since the corresponding buildHTML/buildMathML function expects a
// list of elements, we normalize for different kinds of arguments
export const ordargument = function(arg) {
if (arg.type === "ordgroup") {
return arg.value;
} else {
return [arg];
}
};

View File

@@ -2,115 +2,7 @@ import utils from "./utils";
import ParseError from "./ParseError";
import ParseNode from "./ParseNode";
/* This file contains a list of functions that we parse, identified by
* the calls to defineFunction.
*
* The first argument to defineFunction is a single name or a list of names.
* All functions named in such a list will share a single implementation.
*
* Each declared function can have associated properties, which
* include the following:
*
* - numArgs: The number of arguments the function takes.
* If this is the only property, it can be passed as a number
* instead of an element of a properties object.
* - argTypes: (optional) An array corresponding to each argument of the
* function, giving the type of argument that should be parsed. Its
* length should be equal to `numArgs + numOptionalArgs`. Valid
* types:
* - "size": A size-like thing, such as "1em" or "5ex"
* - "color": An html color, like "#abc" or "blue"
* - "original": The same type as the environment that the
* function being parsed is in (e.g. used for the
* bodies of functions like \textcolor where the
* first argument is special and the second
* argument is parsed normally)
* Other possible types (probably shouldn't be used)
* - "text": Text-like (e.g. \text)
* - "math": Normal math
* If undefined, this will be treated as an appropriate length
* array of "original" strings
* - greediness: (optional) The greediness of the function to use ungrouped
* arguments.
*
* E.g. if you have an expression
* \sqrt \frac 1 2
* since \frac has greediness=2 vs \sqrt's greediness=1, \frac
* will use the two arguments '1' and '2' as its two arguments,
* then that whole function will be used as the argument to
* \sqrt. On the other hand, the expressions
* \frac \frac 1 2 3
* and
* \frac \sqrt 1 2
* will fail because \frac and \frac have equal greediness
* and \sqrt has a lower greediness than \frac respectively. To
* make these parse, we would have to change them to:
* \frac {\frac 1 2} 3
* and
* \frac {\sqrt 1} 2
*
* The default value is `1`
* - allowedInText: (optional) Whether or not the function is allowed inside
* text mode (default false)
* - numOptionalArgs: (optional) The number of optional arguments the function
* should parse. If the optional arguments aren't found,
* `null` will be passed to the handler in their place.
* (default 0)
* - infix: (optional) Must be true if the function is an infix operator.
*
* The last argument is that implementation, the handler for the function(s).
* It is called to handle these functions and their arguments.
* It receives two arguments:
* - context contains information and references provided by the parser
* - args is an array of arguments obtained from TeX input
* The context contains the following properties:
* - funcName: the text (i.e. name) of the function, including \
* - parser: the parser object
* - lexer: the lexer object
* - positions: the positions in the overall string of the function
* and the arguments.
* The latter three should only be used to produce error messages.
*
* The function should return an object with the following keys:
* - type: The type of element that this is. This is then used in
* buildHTML/buildMathML to determine which function
* should be called to build this node into a DOM node
* Any other data can be added to the object, which will be passed
* in to the function in buildHTML/buildMathML as `group.value`.
*/
function defineFunction(names, props, handler) {
if (typeof names === "string") {
names = [names];
}
if (typeof props === "number") {
props = { numArgs: props };
}
// Set default values of functions
const data = {
numArgs: props.numArgs,
argTypes: props.argTypes,
greediness: (props.greediness === undefined) ? 1 : props.greediness,
allowedInText: !!props.allowedInText,
allowedInMath: props.allowedInMath,
numOptionalArgs: props.numOptionalArgs || 0,
infix: !!props.infix,
handler: handler,
};
for (let i = 0; i < names.length; ++i) {
module.exports[names[i]] = data;
}
}
// Since the corresponding buildHTML/buildMathML function expects a
// list of elements, we normalize for different kinds of arguments
const ordargument = function(arg) {
if (arg.type === "ordgroup") {
return arg.value;
} else {
return [arg];
}
};
import defineFunction, {ordargument} from "./defineFunction";
// A normal square root
defineFunction("\\sqrt", {
@@ -234,16 +126,7 @@ defineFunction("\\KaTeX", {
};
});
defineFunction(["\\phantom", "\\hphantom", "\\vphantom"], {
numArgs: 1,
}, function(context, args) {
const body = args[0];
return {
type: context.funcName.slice(1),
value: ordargument(body),
body: body,
};
});
import "./functions/phantom";
// Math class commands except \mathop
defineFunction([

108
src/functions/phantom.js Normal file
View File

@@ -0,0 +1,108 @@
import defineFunction, {ordargument} from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
defineFunction(
"\\phantom",
{
numArgs: 1,
},
(context, args) => {
const body = args[0];
return {
type: "phantom",
value: ordargument(body),
};
},
"phantom",
(group, options) => {
const elements = html.buildExpression(
group.value.value,
options.withPhantom(),
false
);
// \phantom isn't supposed to affect the elements it contains.
// See "color" for more details.
return new buildCommon.makeFragment(elements);
},
(group, options) => {
const inner = mml.buildExpression(group.value.value, options);
return new mathMLTree.MathNode("mphantom", inner);
},
);
defineFunction(
"\\hphantom",
{
numArgs: 1,
},
(context, args) => {
const body = args[0];
return {
type: "hphantom",
value: ordargument(body),
body: body,
};
},
"hphantom",
(group, options) => {
let node = buildCommon.makeSpan(
[], [html.buildGroup(group.value.body, options.withPhantom())]);
node.height = 0;
node.depth = 0;
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
node.children[i].height = 0;
node.children[i].depth = 0;
}
}
// See smash for comment re: use of makeVList
node = buildCommon.makeVList([
{type: "elem", elem: node},
], "firstBaseline", null, options);
return node;
},
(group, options) => {
const inner = mml.buildExpression(group.value.value, options);
const node = new mathMLTree.MathNode("mphantom", inner);
node.setAttribute("height", "0px");
return node;
},
);
defineFunction(
"\\vphantom",
{
numArgs: 1,
},
(context, args) => {
const body = args[0];
return {
type: "vphantom",
value: ordargument(body),
body: body,
};
},
"vphantom",
(group, options) => {
const inner = buildCommon.makeSpan(
["inner"],
[html.buildGroup(group.value.body, options.withPhantom())]);
const fix = buildCommon.makeSpan(["fix"], []);
return buildCommon.makeSpan(
["mord", "rlap"], [inner, fix], options);
},
(group, options) => {
const inner = mml.buildExpression(group.value.value, options);
const node = new mathMLTree.MathNode("mphantom", inner);
node.setAttribute("width", "0px");
return node;
},
);