mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-11 22:18:41 +00:00
Make htmlBuilder and mathmlBuilder params type-safe. (#1312)
* Make htmlBuilder and mathmlBuilder params type-safe. Also correct and refine some of the ParseNode types.
This commit is contained in:
@@ -39,22 +39,29 @@ export type NodeType = $Keys<ParseNodeTypes>;
|
||||
export type NodeValue<TYPE: NodeType> = $ElementType<ParseNodeTypes, TYPE>;
|
||||
|
||||
export type AccentStructType = {|
|
||||
type: "accent",
|
||||
type: "accent" | "accentUnder",
|
||||
label: string,
|
||||
isStretchy: boolean,
|
||||
isShifty: boolean,
|
||||
isStretchy?: boolean,
|
||||
isShifty?: boolean,
|
||||
base: ParseNode<*>,
|
||||
|};
|
||||
|
||||
export type LeftRightDelimType = {|
|
||||
type?: "leftright",
|
||||
body: ParseNode<*>[],
|
||||
left: string,
|
||||
right: string,
|
||||
|};
|
||||
|
||||
// Map from `type` field value to corresponding `value` type.
|
||||
type ParseNodeTypes = {
|
||||
"array": ArrayEnvNodeData,
|
||||
"accent": AccentStructType,
|
||||
"color": {|
|
||||
type: "color",
|
||||
color: string,
|
||||
value: ParseNode<*>[],
|
||||
|},
|
||||
"color-token": string,
|
||||
"leftright": {|
|
||||
body: [{|
|
||||
type: "array",
|
||||
@@ -63,20 +70,29 @@ type ParseNodeTypes = {
|
||||
left: string,
|
||||
right: string,
|
||||
|},
|
||||
// To avoid requiring run-time type assertions, this more carefully captures
|
||||
// the requirements on the fields per the op.js htmlBuilder logic:
|
||||
// - `body` and `value` are NEVER set simultanouesly.
|
||||
// - When `symbol` is true, `body` is set.
|
||||
"op": {|
|
||||
type: "op",
|
||||
limits: boolean,
|
||||
symbol: boolean,
|
||||
alwaysHandleSupSub?: boolean,
|
||||
suppressBaseShift?: boolean,
|
||||
body?: string,
|
||||
value?: ParseNode<*>[],
|
||||
symbol: boolean,
|
||||
body: string,
|
||||
value?: void,
|
||||
|} | {|
|
||||
type: "op",
|
||||
limits: boolean,
|
||||
alwaysHandleSupSub?: boolean,
|
||||
suppressBaseShift?: boolean,
|
||||
symbol: false, // If 'symbol' is true, `body` *must* be set.
|
||||
body?: void,
|
||||
value: ParseNode<*>[],
|
||||
|},
|
||||
"ordgroup": ParseNode<*>[],
|
||||
"size": {|
|
||||
number: number,
|
||||
unit: string,
|
||||
|},
|
||||
"size": Measurement,
|
||||
"styling": {|
|
||||
type: "styling",
|
||||
style: StyleStr,
|
||||
@@ -92,7 +108,6 @@ type ParseNodeTypes = {
|
||||
body: ParseNode<*>[],
|
||||
font?: string,
|
||||
|},
|
||||
"textord": string,
|
||||
"url": string,
|
||||
"verb": {|
|
||||
body: string,
|
||||
@@ -112,18 +127,15 @@ type ParseNodeTypes = {
|
||||
"rel": string,
|
||||
"spacing": string,
|
||||
"textord": string,
|
||||
// From functions.js and functions/*.js. See also "accent", "color", "op",
|
||||
// "styling", and "text" above.
|
||||
"accentUnder": {|
|
||||
type: "accentUnder",
|
||||
label: string,
|
||||
base: ParseNode<*>,
|
||||
|},
|
||||
// From functions.js and functions/*.js. See also "color", "op", "styling",
|
||||
// and "text" above.
|
||||
"accent": AccentStructType,
|
||||
"accentUnder": AccentStructType,
|
||||
"cr": {|
|
||||
type: "cr",
|
||||
newRow: boolean,
|
||||
newLine: boolean,
|
||||
size: ?ParseNode<*>,
|
||||
size: ?ParseNode<"size">,
|
||||
|},
|
||||
"delimsizing": {|
|
||||
type: "delimsizing",
|
||||
@@ -134,8 +146,8 @@ type ParseNodeTypes = {
|
||||
"enclose": {|
|
||||
type: "enclose",
|
||||
label: string,
|
||||
backgroundColor?: ParseNode<*>,
|
||||
borderColor?: ParseNode<*>,
|
||||
backgroundColor?: ParseNode<"color-token">,
|
||||
borderColor?: ParseNode<"color-token">,
|
||||
body: ParseNode<*>,
|
||||
|},
|
||||
"environment": {|
|
||||
@@ -182,12 +194,7 @@ type ParseNodeTypes = {
|
||||
alignment: string,
|
||||
body: ParseNode<*>,
|
||||
|},
|
||||
"leftright": {|
|
||||
type?: "leftright",
|
||||
body?: ParseNode<*>[],
|
||||
left: string,
|
||||
right: string,
|
||||
|} | {|
|
||||
"leftright": LeftRightDelimType | {|
|
||||
type: "leftright",
|
||||
value: string,
|
||||
|},
|
||||
@@ -243,8 +250,8 @@ type ParseNodeTypes = {
|
||||
"rule": {|
|
||||
type: "rule",
|
||||
shift: ?Measurement,
|
||||
width: ParseNode<*>,
|
||||
height: ParseNode<*>,
|
||||
width: Measurement,
|
||||
height: Measurement,
|
||||
|},
|
||||
"sizing": {|
|
||||
type: "sizing",
|
||||
@@ -273,3 +280,32 @@ type ParseNodeTypes = {
|
||||
below: ?ParseNode<*>,
|
||||
|},
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that the node is of the given type and returns it with stricter
|
||||
* typing. Throws if the node's type does not match.
|
||||
*/
|
||||
export function assertNodeType<NODETYPE: NodeType>(
|
||||
node: ParseNode<*>,
|
||||
type: NODETYPE,
|
||||
): ParseNode<NODETYPE> {
|
||||
const typedNode = checkNodeType(node, type);
|
||||
if (!typedNode) {
|
||||
throw new Error(
|
||||
`Expected node of type ${type}, but got node of type ${node.type}`);
|
||||
}
|
||||
return typedNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node more strictly typed iff it is of the given type. Otherwise,
|
||||
* returns null.
|
||||
*/
|
||||
export function checkNodeType<NODETYPE: NodeType>(
|
||||
node: ParseNode<*>,
|
||||
type: NODETYPE,
|
||||
): ?ParseNode<NODETYPE> {
|
||||
return node.type === type ?
|
||||
(node: ParseNode<NODETYPE>) :
|
||||
null;
|
||||
}
|
||||
|
@@ -556,7 +556,7 @@ export default class Parser {
|
||||
*/
|
||||
parseArguments(
|
||||
func: string, // Should look like "\name" or "\begin{name}".
|
||||
funcData: FunctionSpec<*> | EnvSpec,
|
||||
funcData: FunctionSpec<*> | EnvSpec<*>,
|
||||
): {
|
||||
args: ParseNode<*>[],
|
||||
optArgs: (?ParseNode<*>)[],
|
||||
@@ -777,7 +777,7 @@ export default class Parser {
|
||||
if (!match) {
|
||||
throw new ParseError("Invalid color: '" + res.text + "'", res);
|
||||
}
|
||||
return newArgument(new ParseNode("color", match[0], this.mode), res);
|
||||
return newArgument(new ParseNode("color-token", match[0], this.mode), res);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -7,6 +7,7 @@ import ParseNode from "./ParseNode";
|
||||
|
||||
import type Parser from "./Parser";
|
||||
import type {ArgType, Mode} from "./types";
|
||||
import type {NodeType} from "./ParseNode";
|
||||
|
||||
/**
|
||||
* The context contains the following properties:
|
||||
@@ -25,11 +26,11 @@ type EnvContext = {|
|
||||
* - args: an array of arguments passed to \begin{name}
|
||||
* - optArgs: an array of optional arguments passed to \begin{name}
|
||||
*/
|
||||
type EnvHandler = (
|
||||
type EnvHandler<NODETYPE: NodeType> = (
|
||||
context: EnvContext,
|
||||
args: ParseNode<*>[],
|
||||
optArgs: (?ParseNode<*>)[],
|
||||
) => ParseNode<*>;
|
||||
) => ParseNode<NODETYPE>;
|
||||
|
||||
/**
|
||||
* - numArgs: (default 0) The number of arguments after the \begin{name} function.
|
||||
@@ -49,13 +50,16 @@ type EnvProps = {
|
||||
* 2. requires all arguments except argType
|
||||
* It is generated by `defineEnvironment()` below.
|
||||
*/
|
||||
export type EnvSpec = {|
|
||||
export type EnvSpec<NODETYPE: NodeType> = {|
|
||||
type: NODETYPE, // Need to use the type to avoid error. See NOTES below.
|
||||
numArgs: number,
|
||||
argTypes?: ArgType[],
|
||||
greediness: number,
|
||||
allowedInText: boolean,
|
||||
numOptionalArgs: number,
|
||||
handler: EnvHandler,
|
||||
// FLOW TYPE NOTES: Same issue as the notes on the handler of FunctionSpec
|
||||
// in defineFunction.
|
||||
handler: EnvHandler<*>,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -63,11 +67,11 @@ export type EnvSpec = {|
|
||||
* `environments.js` exports this same dictionary again and makes it public.
|
||||
* `Parser.js` requires this dictionary via `environments.js`.
|
||||
*/
|
||||
export const _environments: {[string]: EnvSpec} = {};
|
||||
export const _environments: {[string]: EnvSpec<*>} = {};
|
||||
|
||||
type EnvDefSpec = {|
|
||||
type EnvDefSpec<NODETYPE: NodeType> = {|
|
||||
// Unique string to differentiate parse nodes.
|
||||
type: string,
|
||||
type: NODETYPE,
|
||||
|
||||
// List of functions which use the give handler, htmlBuilder,
|
||||
// and mathmlBuilder.
|
||||
@@ -76,34 +80,35 @@ type EnvDefSpec = {|
|
||||
// Properties that control how the environments are parsed.
|
||||
props: EnvProps,
|
||||
|
||||
handler: EnvHandler,
|
||||
handler: EnvHandler<NODETYPE>,
|
||||
|
||||
// This function returns an object representing the DOM structure to be
|
||||
// created when rendering the defined LaTeX function.
|
||||
// TODO: Port buildHTML to flow and make the group and return types explicit.
|
||||
htmlBuilder: (group: *, options: Options) => *,
|
||||
// TODO: Port buildHTML to flow and make the return type explicit.
|
||||
htmlBuilder: (group: ParseNode<NODETYPE>, options: Options) => *,
|
||||
|
||||
// This function returns an object representing the MathML structure to be
|
||||
// created when rendering the defined LaTeX function.
|
||||
// TODO: Port buildMathML to flow and make the group and return types explicit.
|
||||
mathmlBuilder: (group: *, options: Options) => *,
|
||||
// TODO: Port buildMathML to flow and make the return type explicit.
|
||||
mathmlBuilder: (group: ParseNode<NODETYPE>, options: Options) => *,
|
||||
|};
|
||||
|
||||
export default function defineEnvironment({
|
||||
export default function defineEnvironment<NODETYPE: NodeType>({
|
||||
type,
|
||||
names,
|
||||
props,
|
||||
handler,
|
||||
htmlBuilder,
|
||||
mathmlBuilder,
|
||||
}: EnvDefSpec) {
|
||||
// Set default values of environments
|
||||
}: EnvDefSpec<NODETYPE>) {
|
||||
// Set default values of environments.
|
||||
const data = {
|
||||
type,
|
||||
numArgs: props.numArgs || 0,
|
||||
greediness: 1,
|
||||
allowedInText: false,
|
||||
numOptionalArgs: 0,
|
||||
handler: handler,
|
||||
handler,
|
||||
};
|
||||
for (let i = 0; i < names.length; ++i) {
|
||||
_environments[names[i]] = data;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
import {groupTypes as htmlGroupTypes} from "./buildHTML";
|
||||
import {groupTypes as mathmlGroupTypes} from "./buildMathML";
|
||||
import {checkNodeType} from "./ParseNode";
|
||||
|
||||
import type Parser from "./Parser";
|
||||
import type ParseNode from "./ParseNode";
|
||||
@@ -103,13 +104,13 @@ type FunctionDefSpec<NODETYPE: NodeType> = {|
|
||||
|
||||
// This function returns an object representing the DOM structure to be
|
||||
// created when rendering the defined LaTeX function.
|
||||
// TODO: Change `group` to ParseNode<NODETYPE> and make return type explicit.
|
||||
htmlBuilder?: (group: *, options: Options) => *,
|
||||
// TODO: Make return type explicit.
|
||||
htmlBuilder?: (group: ParseNode<NODETYPE>, options: Options) => *,
|
||||
|
||||
// This function returns an object representing the MathML structure to be
|
||||
// created when rendering the defined LaTeX function.
|
||||
// TODO: Change `group` to ParseNode<NODETYPE> and make return type explicit.
|
||||
mathmlBuilder?: (group: *, options: Options) => *,
|
||||
// TODO: Make return type explicit.
|
||||
mathmlBuilder?: (group: ParseNode<NODETYPE>, options: Options) => *,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -195,9 +196,6 @@ export default function defineFunction<NODETYPE: NodeType>({
|
||||
// Since the corresponding buildHTML/buildMathML function expects a
|
||||
// list of elements, we normalize for different kinds of arguments
|
||||
export const ordargument = function(arg: ParseNode<*>): ParseNode<*>[] {
|
||||
if (arg.type === "ordgroup") {
|
||||
return arg.value;
|
||||
} else {
|
||||
return [arg];
|
||||
}
|
||||
const node = checkNodeType(arg, "ordgroup");
|
||||
return node ? node.value : [arg];
|
||||
};
|
||||
|
@@ -4,6 +4,7 @@ import defineEnvironment from "../defineEnvironment";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import ParseError from "../ParseError";
|
||||
import ParseNode from "../ParseNode";
|
||||
import {assertNodeType} from "../ParseNode";
|
||||
import {calculateSize} from "../units";
|
||||
import utils from "../utils";
|
||||
|
||||
@@ -20,18 +21,29 @@ type AlignSpec = { type: "separator", separator: string } | {
|
||||
pregap?: number,
|
||||
postgap?: number,
|
||||
};
|
||||
export type ArrayEnvNodeData = {
|
||||
|
||||
export type ArrayEnvNodeData = {|
|
||||
type: "array",
|
||||
hskipBeforeAndAfter?: boolean,
|
||||
arraystretch?: number,
|
||||
addJot?: boolean,
|
||||
cols?: AlignSpec[],
|
||||
// These fields are always set, but not on struct construction
|
||||
// initialization.
|
||||
body?: ParseNode<*>[][], // List of rows in the (2D) array.
|
||||
rowGaps?: number[],
|
||||
body: ParseNode<*>[][], // List of rows in the (2D) array.
|
||||
rowGaps: (?ParseNode<*>)[],
|
||||
numHLinesBeforeRow: number[],
|
||||
|};
|
||||
// Same as above but with some fields not yet filled.
|
||||
type ArrayEnvNodeDataIncomplete = {|
|
||||
type: "array",
|
||||
hskipBeforeAndAfter?: boolean,
|
||||
arraystretch?: number,
|
||||
addJot?: boolean,
|
||||
cols?: AlignSpec[],
|
||||
// Before these fields are filled.
|
||||
body?: ParseNode<*>[][],
|
||||
rowGaps?: (?ParseNode<*>)[],
|
||||
numHLinesBeforeRow?: number[],
|
||||
};
|
||||
|};
|
||||
|
||||
function getNumHLines(parser: Parser): number {
|
||||
let n = 0;
|
||||
@@ -52,9 +64,9 @@ function getNumHLines(parser: Parser): number {
|
||||
*/
|
||||
function parseArray(
|
||||
parser: Parser,
|
||||
result: ArrayEnvNodeData,
|
||||
result: ArrayEnvNodeDataIncomplete,
|
||||
style: StyleStr,
|
||||
): ParseNode<*> {
|
||||
): ParseNode<"array"> {
|
||||
// Parse body of array with \\ temporarily mapped to \cr
|
||||
const oldNewline = parser.gullet.macros["\\\\"];
|
||||
parser.gullet.macros["\\\\"] = "\\cr";
|
||||
@@ -96,7 +108,7 @@ function parseArray(
|
||||
if (!cr) {
|
||||
throw new ParseError(`Failed to parse function after ${next}`);
|
||||
}
|
||||
rowGaps.push(cr.value.size);
|
||||
rowGaps.push(assertNodeType(cr, "cr").value.size);
|
||||
|
||||
// check for \hline(s) following the row separator
|
||||
numHLinesBeforeRow.push(getNumHLines(parser));
|
||||
@@ -111,8 +123,10 @@ function parseArray(
|
||||
result.body = body;
|
||||
result.rowGaps = rowGaps;
|
||||
result.numHLinesBeforeRow = numHLinesBeforeRow;
|
||||
// $FlowFixMe: The required fields were added immediately above.
|
||||
const res: ArrayEnvNodeData = result;
|
||||
parser.gullet.macros["\\\\"] = oldNewline;
|
||||
return new ParseNode("array", result, parser.mode);
|
||||
return new ParseNode("array", res, parser.mode);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +148,7 @@ type Outrow = {
|
||||
};
|
||||
|
||||
const htmlBuilder = function(group, options) {
|
||||
const groupValue = assertNodeType(group, "array").value;
|
||||
let r;
|
||||
let c;
|
||||
const nr = group.value.body.length;
|
||||
@@ -153,7 +168,7 @@ const htmlBuilder = function(group, options) {
|
||||
const jot = 3 * pt;
|
||||
// Default \arraystretch from lttab.dtx
|
||||
// TODO(gagern): may get redefined once we have user-defined macros
|
||||
const arraystretch = utils.deflt(group.value.arraystretch, 1);
|
||||
const arraystretch = utils.deflt(groupValue.arraystretch, 1);
|
||||
const arrayskip = arraystretch * baselineskip;
|
||||
const arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and
|
||||
const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx
|
||||
@@ -168,8 +183,8 @@ const htmlBuilder = function(group, options) {
|
||||
hlinePos.push(totalHeight);
|
||||
}
|
||||
|
||||
for (r = 0; r < group.value.body.length; ++r) {
|
||||
const inrow = group.value.body[r];
|
||||
for (r = 0; r < groupValue.body.length; ++r) {
|
||||
const inrow = groupValue.body[r];
|
||||
let height = arstrutHeight; // \@array adds an \@arstrut
|
||||
let depth = arstrutDepth; // to each tow (via the template)
|
||||
|
||||
@@ -189,9 +204,10 @@ const htmlBuilder = function(group, options) {
|
||||
outrow[c] = elt;
|
||||
}
|
||||
|
||||
const rowGap = groupValue.rowGaps[r];
|
||||
let gap = 0;
|
||||
if (group.value.rowGaps[r]) {
|
||||
gap = calculateSize(group.value.rowGaps[r].value, options);
|
||||
if (rowGap) {
|
||||
gap = calculateSize(rowGap.value, options);
|
||||
if (gap > 0) { // \@argarraycr
|
||||
gap += arstrutDepth;
|
||||
if (depth < gap) {
|
||||
@@ -203,7 +219,7 @@ const htmlBuilder = function(group, options) {
|
||||
// In AMS multiline environments such as aligned and gathered, rows
|
||||
// correspond to lines that have additional \jot added to the
|
||||
// \baselineskip via \openup.
|
||||
if (group.value.addJot) {
|
||||
if (groupValue.addJot) {
|
||||
depth += jot;
|
||||
}
|
||||
|
||||
@@ -224,7 +240,7 @@ const htmlBuilder = function(group, options) {
|
||||
}
|
||||
|
||||
const offset = totalHeight / 2 + options.fontMetrics().axisHeight;
|
||||
const colDescriptions = group.value.cols || [];
|
||||
const colDescriptions = groupValue.cols || [];
|
||||
const cols = [];
|
||||
let colSep;
|
||||
let colDescrNum;
|
||||
@@ -271,7 +287,7 @@ const htmlBuilder = function(group, options) {
|
||||
}
|
||||
|
||||
let sepwidth;
|
||||
if (c > 0 || group.value.hskipBeforeAndAfter) {
|
||||
if (c > 0 || groupValue.hskipBeforeAndAfter) {
|
||||
sepwidth = utils.deflt(colDescr.pregap, arraycolsep);
|
||||
if (sepwidth !== 0) {
|
||||
colSep = buildCommon.makeSpan(["arraycolsep"], []);
|
||||
@@ -302,7 +318,7 @@ const htmlBuilder = function(group, options) {
|
||||
[col]);
|
||||
cols.push(col);
|
||||
|
||||
if (c < nc - 1 || group.value.hskipBeforeAndAfter) {
|
||||
if (c < nc - 1 || groupValue.hskipBeforeAndAfter) {
|
||||
sepwidth = utils.deflt(colDescr.postgap, arraycolsep);
|
||||
if (sepwidth !== 0) {
|
||||
colSep = buildCommon.makeSpan(["arraycolsep"], []);
|
||||
@@ -331,8 +347,9 @@ const htmlBuilder = function(group, options) {
|
||||
};
|
||||
|
||||
const mathmlBuilder = function(group, options) {
|
||||
const groupValue = assertNodeType(group, "array").value;
|
||||
return new mathMLTree.MathNode(
|
||||
"mtable", group.value.body.map(function(row) {
|
||||
"mtable", groupValue.body.map(function(row) {
|
||||
return new mathMLTree.MathNode(
|
||||
"mtr", row.map(function(cell) {
|
||||
return new mathMLTree.MathNode(
|
||||
@@ -343,9 +360,10 @@ const mathmlBuilder = function(group, options) {
|
||||
|
||||
// Convinient function for aligned and alignedat environments.
|
||||
const alignedHandler = function(context, args) {
|
||||
const cols = [];
|
||||
let res = {
|
||||
type: "array",
|
||||
cols: [],
|
||||
cols,
|
||||
addJot: true,
|
||||
};
|
||||
res = parseArray(context.parser, res, "display");
|
||||
@@ -383,7 +401,7 @@ const alignedHandler = function(context, args) {
|
||||
throw new ParseError(
|
||||
"Too many math in a row: " +
|
||||
`expected ${numMaths}, but got ${curMaths}`,
|
||||
row);
|
||||
row[0]);
|
||||
}
|
||||
} else if (numCols < row.length) { // Case 2
|
||||
numCols = row.length;
|
||||
@@ -401,7 +419,7 @@ const alignedHandler = function(context, args) {
|
||||
} else if (i > 0 && isAligned) { // "aligned" mode.
|
||||
pregap = 1; // add one \quad
|
||||
}
|
||||
res.value.cols[i] = {
|
||||
cols[i] = {
|
||||
type: "align",
|
||||
align: align,
|
||||
pregap: pregap,
|
||||
|
@@ -172,17 +172,18 @@ const htmlBuilder = (group, options) => {
|
||||
};
|
||||
|
||||
const mathmlBuilder = (group, options) => {
|
||||
const groupValue = group.value;
|
||||
let accentNode;
|
||||
if (group.value.isStretchy) {
|
||||
accentNode = stretchy.mathMLnode(group.value.label);
|
||||
if (groupValue.isStretchy) {
|
||||
accentNode = stretchy.mathMLnode(groupValue.label);
|
||||
} else {
|
||||
accentNode = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value.label, group.mode)]);
|
||||
"mo", [mml.makeText(groupValue.label, group.mode)]);
|
||||
}
|
||||
|
||||
const node = new mathMLTree.MathNode(
|
||||
"mover",
|
||||
[mml.buildGroup(group.value.base, options), accentNode]);
|
||||
[mml.buildGroup(groupValue.base, options), accentNode]);
|
||||
|
||||
node.setAttribute("accent", "true");
|
||||
|
||||
|
@@ -8,6 +8,8 @@ import stretchy from "../stretchy";
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
|
||||
import type ParseNode from "../ParseNode";
|
||||
|
||||
defineFunction({
|
||||
type: "accentUnder",
|
||||
names: [
|
||||
@@ -25,7 +27,7 @@ defineFunction({
|
||||
base: base,
|
||||
};
|
||||
},
|
||||
htmlBuilder: (group, options) => {
|
||||
htmlBuilder: (group: ParseNode<"accentUnder">, options) => {
|
||||
// Treat under accents much like underlines.
|
||||
const innerGroup = html.buildGroup(group.value.base, options);
|
||||
|
||||
@@ -49,7 +51,7 @@ defineFunction({
|
||||
const accentNode = stretchy.mathMLnode(group.value.label);
|
||||
const node = new mathMLTree.MathNode(
|
||||
"munder",
|
||||
[mml.buildGroup(group.value.body, options), accentNode]
|
||||
[mml.buildGroup(group.value.base, options), accentNode]
|
||||
);
|
||||
node.setAttribute("accentunder", "true");
|
||||
return node;
|
||||
|
@@ -3,6 +3,7 @@ import defineFunction, {ordargument} from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import ParseError from "../ParseError";
|
||||
import {assertNodeType} from "../ParseNode";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
@@ -41,7 +42,7 @@ defineFunction({
|
||||
argTypes: ["color", "original"],
|
||||
},
|
||||
handler(context, args) {
|
||||
const color = args[0];
|
||||
const color = assertNodeType(args[0], "color-token");
|
||||
const body = args[1];
|
||||
return {
|
||||
type: "color",
|
||||
|
@@ -6,6 +6,7 @@ import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import { calculateSize } from "../units";
|
||||
import ParseError from "../ParseError";
|
||||
import {assertNodeType} from "../ParseNode";
|
||||
|
||||
// \\ is a macro mapping to either \cr or \newline. Because they have the
|
||||
// same signature, we implement them as one megafunction, with newRow
|
||||
@@ -23,6 +24,7 @@ defineFunction({
|
||||
},
|
||||
|
||||
handler: (context, args, optArgs) => {
|
||||
const size = optArgs[0];
|
||||
const newRow = (context.funcName === "\\cr");
|
||||
let newLine = false;
|
||||
if (!newRow) {
|
||||
@@ -39,7 +41,7 @@ defineFunction({
|
||||
type: "cr",
|
||||
newLine,
|
||||
newRow,
|
||||
size: optArgs[0],
|
||||
size: size && assertNodeType(size, "size"),
|
||||
};
|
||||
},
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
|
||||
import type ParseNode from "../ParseNode";
|
||||
import type {LeftRightDelimType} from "../ParseNode";
|
||||
import type {FunctionContext} from "../defineFunction";
|
||||
|
||||
// Extra data needed for the delimiter handler down below
|
||||
@@ -122,6 +123,15 @@ defineFunction({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
function leftRightGroupValue(group: ParseNode<"leftright">): LeftRightDelimType {
|
||||
if (!group.value.body) {
|
||||
throw new Error("Bug: The leftright ParseNode wasn't fully parsed.");
|
||||
}
|
||||
return group.value;
|
||||
}
|
||||
|
||||
|
||||
defineFunction({
|
||||
type: "leftright",
|
||||
names: [
|
||||
@@ -163,8 +173,9 @@ defineFunction({
|
||||
}
|
||||
},
|
||||
htmlBuilder: (group, options) => {
|
||||
const groupValue = leftRightGroupValue(group);
|
||||
// Build the inner expression
|
||||
const inner = html.buildExpression(group.value.body, options, true,
|
||||
const inner = html.buildExpression(groupValue.body, options, true,
|
||||
[null, "mclose"]);
|
||||
|
||||
let innerHeight = 0;
|
||||
@@ -188,14 +199,14 @@ defineFunction({
|
||||
innerDepth *= options.sizeMultiplier;
|
||||
|
||||
let leftDelim;
|
||||
if (group.value.left === ".") {
|
||||
if (groupValue.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,
|
||||
groupValue.left, innerHeight, innerDepth, options,
|
||||
group.mode, ["mopen"]);
|
||||
}
|
||||
// Add it to the beginning of the expression
|
||||
@@ -216,11 +227,11 @@ defineFunction({
|
||||
|
||||
let rightDelim;
|
||||
// Same for the right delimiter
|
||||
if (group.value.right === ".") {
|
||||
if (groupValue.right === ".") {
|
||||
rightDelim = html.makeNullDelimiter(options, ["mclose"]);
|
||||
} else {
|
||||
rightDelim = delimiter.leftRightDelim(
|
||||
group.value.right, innerHeight, innerDepth, options,
|
||||
groupValue.right, innerHeight, innerDepth, options,
|
||||
group.mode, ["mclose"]);
|
||||
}
|
||||
// Add it to the end of the expression.
|
||||
@@ -229,20 +240,21 @@ defineFunction({
|
||||
return buildCommon.makeSpan(["minner"], inner, options);
|
||||
},
|
||||
mathmlBuilder: (group, options) => {
|
||||
const inner = mml.buildExpression(group.value.body, options);
|
||||
const groupValue = leftRightGroupValue(group);
|
||||
const inner = mml.buildExpression(groupValue.body, options);
|
||||
|
||||
if (group.value.left !== ".") {
|
||||
if (groupValue.left !== ".") {
|
||||
const leftNode = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value.left, group.mode)]);
|
||||
"mo", [mml.makeText(groupValue.left, group.mode)]);
|
||||
|
||||
leftNode.setAttribute("fence", "true");
|
||||
|
||||
inner.unshift(leftNode);
|
||||
}
|
||||
|
||||
if (group.value.right !== ".") {
|
||||
if (groupValue.right !== ".") {
|
||||
const rightNode = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value.right, group.mode)]);
|
||||
"mo", [mml.makeText(groupValue.right, group.mode)]);
|
||||
|
||||
rightNode.setAttribute("fence", "true");
|
||||
|
||||
@@ -293,7 +305,7 @@ defineFunction({
|
||||
},
|
||||
mathmlBuilder: (group, options) => {
|
||||
const middleNode = new mathMLTree.MathNode(
|
||||
"mo", [mml.makeText(group.value.middle, group.mode)]);
|
||||
"mo", [mml.makeText(group.value.value, group.mode)]);
|
||||
middleNode.setAttribute("fence", "true");
|
||||
return middleNode;
|
||||
},
|
||||
|
@@ -4,6 +4,7 @@ import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import utils from "../utils";
|
||||
import stretchy from "../stretchy";
|
||||
import {assertNodeType} from "../ParseNode";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
@@ -17,7 +18,6 @@ const htmlBuilder = (group, options) => {
|
||||
const scale = options.sizeMultiplier;
|
||||
let img;
|
||||
let imgShift = 0;
|
||||
const isColorbox = /color/.test(label);
|
||||
|
||||
if (label === "sout") {
|
||||
img = buildCommon.makeSpan(["stretchy", "sout"]);
|
||||
@@ -41,16 +41,16 @@ const htmlBuilder = (group, options) => {
|
||||
img = stretchy.encloseSpan(inner, label, vertPad, options);
|
||||
imgShift = inner.depth + vertPad;
|
||||
|
||||
if (isColorbox) {
|
||||
if (group.value.backgroundColor) {
|
||||
img.style.backgroundColor = group.value.backgroundColor.value;
|
||||
if (label === "fcolorbox") {
|
||||
if (group.value.borderColor) {
|
||||
img.style.borderColor = group.value.borderColor.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let vlist;
|
||||
if (isColorbox) {
|
||||
if (group.value.backgroundColor) {
|
||||
vlist = buildCommon.makeVList({
|
||||
positionType: "individualShift",
|
||||
children: [
|
||||
@@ -104,19 +104,16 @@ const mathmlBuilder = (group, options) => {
|
||||
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
|
||||
case "\\xcancel":
|
||||
node.setAttribute("notation", "updiagonalstrike downdiagonalstrike");
|
||||
break;
|
||||
}
|
||||
if (group.value.backgroundColor) {
|
||||
node.setAttribute("mathbackground", group.value.backgroundColor.value);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
@@ -131,7 +128,7 @@ defineFunction({
|
||||
argTypes: ["color", "text"],
|
||||
},
|
||||
handler(context, args, optArgs) {
|
||||
const color = args[0];
|
||||
const color = assertNodeType(args[0], "color-token");
|
||||
const body = args[1];
|
||||
return {
|
||||
type: "enclose",
|
||||
@@ -154,8 +151,8 @@ defineFunction({
|
||||
argTypes: ["color", "color", "text"],
|
||||
},
|
||||
handler(context, args, optArgs) {
|
||||
const borderColor = args[0];
|
||||
const backgroundColor = args[1];
|
||||
const borderColor = assertNodeType(args[0], "color-token");
|
||||
const backgroundColor = assertNodeType(args[1], "color-token");
|
||||
const body = args[2];
|
||||
return {
|
||||
type: "enclose",
|
||||
|
@@ -2,6 +2,7 @@
|
||||
import defineFunction, {ordargument} from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import {assertNodeType} from "../ParseNode";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
@@ -15,7 +16,7 @@ defineFunction({
|
||||
},
|
||||
handler: (context, args) => {
|
||||
const body = args[1];
|
||||
const href = args[0].value;
|
||||
const href = assertNodeType(args[0], "url").value;
|
||||
return {
|
||||
type: "href",
|
||||
href: href,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
import ParseNode from "../ParseNode";
|
||||
import defineFunction, {ordargument} from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
@@ -29,17 +30,21 @@ defineFunction({
|
||||
let letter = "";
|
||||
let mode = "";
|
||||
|
||||
for (const child of group.value.value) {
|
||||
const groupValue = group.value.value.map(child => {
|
||||
const childValue = child.value;
|
||||
// In the amsopn package, \newmcodes@ changes four
|
||||
// characters, *-/:’, from math operators back into text.
|
||||
if ("*-/:".indexOf(child.value) !== -1) {
|
||||
child.type = "textord";
|
||||
if (typeof childValue === "string" &&
|
||||
"*-/:".indexOf(childValue) !== -1) {
|
||||
return new ParseNode("textord", childValue, child.mode);
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Consolidate Greek letter function names into symbol characters.
|
||||
const temp = html.buildExpression(
|
||||
group.value.value, options.withFont("mathrm"), true);
|
||||
groupValue, options.withFont("mathrm"), true);
|
||||
|
||||
// All we want from temp are the letters. With them, we'll
|
||||
// create a text operator similar to \tan or \cos.
|
||||
|
@@ -2,6 +2,7 @@
|
||||
import buildCommon from "../buildCommon";
|
||||
import defineFunction from "../defineFunction";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import {assertNodeType} from "../ParseNode";
|
||||
import {calculateSize} from "../units";
|
||||
|
||||
defineFunction({
|
||||
@@ -14,8 +15,8 @@ defineFunction({
|
||||
},
|
||||
handler(context, args, optArgs) {
|
||||
const shift = optArgs[0];
|
||||
const width = args[0];
|
||||
const height = args[1];
|
||||
const width = assertNodeType(args[0], "size");
|
||||
const height = assertNodeType(args[1], "size");
|
||||
return {
|
||||
type: "rule",
|
||||
shift: shift && shift.value,
|
||||
|
@@ -49,7 +49,9 @@ defineFunction({
|
||||
const font = group.value.font;
|
||||
// Checks if the argument is a font family or a font style.
|
||||
let newOptions;
|
||||
if (textFontFamilies[font]) {
|
||||
if (!font) {
|
||||
newOptions = options;
|
||||
} else if (textFontFamilies[font]) {
|
||||
newOptions = options.withTextFontFamily(textFontFamilies[font]);
|
||||
} else if (textFontWeights[font]) {
|
||||
newOptions = options.withTextFontWeight(textFontWeights[font]);
|
||||
@@ -58,8 +60,7 @@ defineFunction({
|
||||
}
|
||||
const inner = html.buildExpression(group.value.body, newOptions, true);
|
||||
buildCommon.tryCombineChars(inner);
|
||||
return buildCommon.makeSpan(["mord", "text"],
|
||||
inner, newOptions);
|
||||
return buildCommon.makeSpan(["mord", "text"], inner, newOptions);
|
||||
},
|
||||
mathmlBuilder(group, options) {
|
||||
const body = group.value.body;
|
||||
|
@@ -168,7 +168,7 @@ const groupLength = function(arg: ParseNode<*>): number {
|
||||
};
|
||||
|
||||
const svgSpan = function(
|
||||
group: ParseNode<"accent">,
|
||||
group: ParseNode<"accent"> | ParseNode<"accentUnder">,
|
||||
options: Options,
|
||||
): DomSpan | SvgSpan {
|
||||
// Create a span with inline SVG for the element.
|
||||
|
39
src/utils.js
39
src/utils.js
@@ -4,6 +4,8 @@
|
||||
* files.
|
||||
*/
|
||||
|
||||
import type ParseNode from "./ParseNode";
|
||||
|
||||
/**
|
||||
* Provide an `indexOf` function which works in IE8, but defers to native if
|
||||
* possible.
|
||||
@@ -89,45 +91,12 @@ function clearNode(node: Node) {
|
||||
setTextContent(node, "");
|
||||
}
|
||||
|
||||
type BaseElem = {|
|
||||
type: "mathord",
|
||||
|} | {|
|
||||
type: "textord",
|
||||
|} | {|
|
||||
type: "bin",
|
||||
|} | {|
|
||||
type: "rel",
|
||||
|} | {|
|
||||
type: "inner",
|
||||
|} | {|
|
||||
type: "open",
|
||||
|} | {|
|
||||
type: "close",
|
||||
|} | {|
|
||||
type: "punct",
|
||||
|};
|
||||
|
||||
type Group = {|
|
||||
type: "ordgroup",
|
||||
value: Group[],
|
||||
|} | {|
|
||||
type: "color",
|
||||
value: {|
|
||||
value: Group[],
|
||||
|},
|
||||
|} | {|
|
||||
type: "font",
|
||||
value: {|
|
||||
body: Group,
|
||||
|},
|
||||
|} | BaseElem;
|
||||
|
||||
/**
|
||||
* Sometimes we want to pull out the innermost element of a group. In most
|
||||
* cases, this will just be the group itself, but when ordgroups and colors have
|
||||
* a single element, we want to pull that out.
|
||||
*/
|
||||
const getBaseElem = function(group: Group): Group | boolean {
|
||||
const getBaseElem = function(group: ParseNode<*>): ParseNode<*> | false {
|
||||
if (!group) {
|
||||
return false;
|
||||
} else if (group.type === "ordgroup") {
|
||||
@@ -154,7 +123,7 @@ const getBaseElem = function(group: Group): Group | boolean {
|
||||
* with a single character in them. To decide if something is a character box,
|
||||
* we find its innermost group, and see if it is a single character.
|
||||
*/
|
||||
const isCharacterBox = function(group: Group): boolean {
|
||||
const isCharacterBox = function(group: ParseNode<*>): boolean {
|
||||
const baseElem = getBaseElem(group);
|
||||
|
||||
// These are all they types of groups which hold single characters
|
||||
|
Reference in New Issue
Block a user