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:
Ashish Myles
2018-05-17 10:38:48 -04:00
committed by GitHub
parent 7eed150c48
commit 431434258d
17 changed files with 210 additions and 161 deletions

View File

@@ -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;
}

View File

@@ -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);
}
/**

View File

@@ -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;

View File

@@ -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];
};

View File

@@ -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,

View File

@@ -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");

View File

@@ -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;

View File

@@ -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",

View File

@@ -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"),
};
},

View File

@@ -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;
},

View File

@@ -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",

View File

@@ -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,

View File

@@ -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.

View File

@@ -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,

View File

@@ -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;

View File

@@ -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.

View File

@@ -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