@andatoshiki/toshiki-cdn
Version:
Toshiki's own CDN packages
179 lines (170 loc) • 6.32 kB
JavaScript
var fontMetrics = require("./fontMetrics");
var parseData = require("./parseData");
var ParseError = require("./ParseError");
var ParseNode = parseData.ParseNode;
var ParseResult = parseData.ParseResult;
/**
* Parse the body of the environment, with rows delimited by \\ and
* columns delimited by &, and create a nested list in row-major order
* with one group per cell.
*/
function parseArray(parser, pos, mode, result) {
var row = [], body = [row], rowGaps = [];
while (true) {
var cell = parser.parseExpression(pos, mode, false, null);
row.push(new ParseNode("ordgroup", cell.result, mode));
pos = cell.position;
var next = cell.peek.text;
if (next === "&") {
pos = cell.peek.position;
} else if (next === "\\end") {
break;
} else if (next === "\\\\" || next === "\\cr") {
var cr = parser.parseFunction(pos, mode);
rowGaps.push(cr.result.value.size);
pos = cr.position;
row = [];
body.push(row);
} else {
throw new ParseError("Expected & or \\\\ or \\end",
parser.lexer, cell.peek.position);
}
}
result.body = body;
result.rowGaps = rowGaps;
return new ParseResult(new ParseNode(result.type, result, mode), pos);
}
/*
* An environment definition is very similar to a function definition.
* Each element of the following array may contain
* - names: The names associated with a function. This can be used to
* share one implementation between several similar environments.
* - numArgs: The number of arguments after the \begin{name} function.
* - argTypes: (optional) Just like for a function
* - allowedInText: (optional) Whether or not the environment is allowed inside
* text mode (default false) (not enforced yet)
* - numOptionalArgs: (optional) Just like for a function
* - handler: The function that is called to handle this environment.
* It will receive the following arguments:
* - pos: the current position of the parser.
* - mode: the current parsing mode.
* - envName: the name of the environment, one of the listed names.
* - [args]: the arguments passed to \begin.
* - positions: the positions associated with these arguments.
*/
var environmentDefinitions = [
// Arrays are part of LaTeX, defined in lttab.dtx so its documentation
// is part of the source2e.pdf file of LaTeX2e source documentation.
{
names: ["array"],
numArgs: 1,
handler: function(pos, mode, envName, colalign, positions) {
var parser = this;
colalign = colalign.value.map ? colalign.value : [colalign];
var cols = colalign.map(function(node) {
var ca = node.value;
if ("lcr".indexOf(ca) !== -1) {
return {
type: "align",
align: ca
};
} else if (ca === "|") {
return {
type: "separator",
separator: "|"
};
}
throw new ParseError(
"Unknown column alignment: " + node.value,
parser.lexer, positions[1]);
});
var res = {
type: "array",
cols: cols,
hskipBeforeAndAfter: true // \@preamble in lttab.dtx
};
res = parseArray(parser, pos, mode, res);
return res;
}
},
// The matrix environments of amsmath builds on the array environment
// of LaTeX, which is discussed above.
{
names: [
"matrix",
"pmatrix",
"bmatrix",
"Bmatrix",
"vmatrix",
"Vmatrix"
],
handler: function(pos, mode, envName) {
var delimiters = {
"matrix": null,
"pmatrix": ["(", ")"],
"bmatrix": ["[", "]"],
"Bmatrix": ["\\{", "\\}"],
"vmatrix": ["|", "|"],
"Vmatrix": ["\\Vert", "\\Vert"]
}[envName];
var res = {
type: "array",
hskipBeforeAndAfter: false // \hskip -\arraycolsep in amsmath
};
res = parseArray(this, pos, mode, res);
if (delimiters) {
res.result = new ParseNode("leftright", {
body: [res.result],
left: delimiters[0],
right: delimiters[1]
}, mode);
}
return res;
}
},
// A cases environment (in amsmath.sty) is almost equivalent to
// \def\arraystretch{1.2}%
// \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right.
{
names: ["cases"],
handler: function(pos, mode, envName) {
var res = {
type: "array",
arraystretch: 1.2,
cols: [{
type: "align",
align: "l",
pregap: 0,
postgap: fontMetrics.metrics.quad
}, {
type: "align",
align: "l",
pregap: 0,
postgap: 0
}]
};
res = parseArray(this, pos, mode, res);
res.result = new ParseNode("leftright", {
body: [res.result],
left: "\\{",
right: "."
}, mode);
return res;
}
}
];
module.exports = (function() {
// nested function so we don't leak i and j into the module scope
var exports = {};
for (var i = 0; i < environmentDefinitions.length; ++i) {
var def = environmentDefinitions[i];
def.greediness = 1;
def.allowedInText = !!def.allowedInText;
def.numArgs = def.numArgs || 0;
def.numOptionalArgs = def.numOptionalArgs || 0;
for (var j = 0; j < def.names.length; ++j) {
exports[def.names[j]] = def;
}
}
return exports;
})();