|
|
|
import buildCommon from "../buildCommon"; |
|
import Style from "../Style"; |
|
import defineEnvironment from "../defineEnvironment"; |
|
import {parseCD} from "./cd"; |
|
import defineFunction from "../defineFunction"; |
|
import defineMacro from "../defineMacro"; |
|
import mathMLTree from "../mathMLTree"; |
|
import ParseError from "../ParseError"; |
|
import {assertNodeType, assertSymbolNodeType} from "../parseNode"; |
|
import {checkSymbolNodeType} from "../parseNode"; |
|
import {Token} from "../Token"; |
|
import {calculateSize, makeEm} from "../units"; |
|
import utils from "../utils"; |
|
|
|
import * as html from "../buildHTML"; |
|
import * as mml from "../buildMathML"; |
|
|
|
import type Parser from "../Parser"; |
|
import type {ParseNode, AnyParseNode} from "../parseNode"; |
|
import type {StyleStr} from "../types"; |
|
import type {HtmlBuilder, MathMLBuilder} from "../defineFunction"; |
|
|
|
|
|
export type AlignSpec = { type: "separator", separator: string } | { |
|
type: "align", |
|
align: string, |
|
pregap?: number, |
|
postgap?: number, |
|
}; |
|
|
|
|
|
export type ColSeparationType = "align" | "alignat" | "gather" | "small" | "CD"; |
|
|
|
|
|
function getHLines(parser: Parser): boolean[] { |
|
|
|
|
|
const hlineInfo = []; |
|
parser.consumeSpaces(); |
|
let nxt = parser.fetch().text; |
|
if (nxt === "\\relax") { |
|
parser.consume(); |
|
parser.consumeSpaces(); |
|
nxt = parser.fetch().text; |
|
} |
|
while (nxt === "\\hline" || nxt === "\\hdashline") { |
|
parser.consume(); |
|
hlineInfo.push(nxt === "\\hdashline"); |
|
parser.consumeSpaces(); |
|
nxt = parser.fetch().text; |
|
} |
|
return hlineInfo; |
|
} |
|
|
|
const validateAmsEnvironmentContext = context => { |
|
const settings = context.parser.settings; |
|
if (!settings.displayMode) { |
|
throw new ParseError(`{${context.envName}} can be used only in` + |
|
` display mode.`); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
function getAutoTag(name): ?boolean { |
|
if (name.indexOf("ed") === -1) { |
|
return name.indexOf("*") === -1; |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseArray( |
|
parser: Parser, |
|
{ |
|
hskipBeforeAndAfter, |
|
addJot, |
|
cols, |
|
arraystretch, |
|
colSeparationType, |
|
autoTag, |
|
singleRow, |
|
emptySingleRow, |
|
maxNumCols, |
|
leqno, |
|
}: {| |
|
hskipBeforeAndAfter?: boolean, |
|
addJot?: boolean, |
|
cols?: AlignSpec[], |
|
arraystretch?: number, |
|
colSeparationType?: ColSeparationType, |
|
autoTag?: ?boolean, |
|
singleRow?: boolean, |
|
emptySingleRow?: boolean, |
|
maxNumCols?: number, |
|
leqno?: boolean, |
|
|}, |
|
style: StyleStr, |
|
): ParseNode<"array"> { |
|
parser.gullet.beginGroup(); |
|
if (!singleRow) { |
|
|
|
|
|
parser.gullet.macros.set("\\cr", "\\\\\\relax"); |
|
} |
|
|
|
|
|
if (!arraystretch) { |
|
const stretch = parser.gullet.expandMacroAsText("\\arraystretch"); |
|
if (stretch == null) { |
|
|
|
arraystretch = 1; |
|
} else { |
|
arraystretch = parseFloat(stretch); |
|
if (!arraystretch || arraystretch < 0) { |
|
throw new ParseError(`Invalid \\arraystretch: ${stretch}`); |
|
} |
|
} |
|
} |
|
|
|
|
|
parser.gullet.beginGroup(); |
|
|
|
let row = []; |
|
const body = [row]; |
|
const rowGaps = []; |
|
const hLinesBeforeRow = []; |
|
|
|
const tags = (autoTag != null ? [] : undefined); |
|
|
|
|
|
|
|
|
|
function beginRow() { |
|
if (autoTag) { |
|
parser.gullet.macros.set("\\@eqnsw", "1", true); |
|
} |
|
} |
|
function endRow() { |
|
if (tags) { |
|
if (parser.gullet.macros.get("\\df@tag")) { |
|
tags.push(parser.subparse([new Token("\\df@tag")])); |
|
parser.gullet.macros.set("\\df@tag", undefined, true); |
|
} else { |
|
tags.push(Boolean(autoTag) && |
|
parser.gullet.macros.get("\\@eqnsw") === "1"); |
|
} |
|
} |
|
} |
|
beginRow(); |
|
|
|
|
|
hLinesBeforeRow.push(getHLines(parser)); |
|
|
|
while (true) { |
|
|
|
let cell = parser.parseExpression(false, singleRow ? "\\end" : "\\\\"); |
|
parser.gullet.endGroup(); |
|
parser.gullet.beginGroup(); |
|
|
|
cell = { |
|
type: "ordgroup", |
|
mode: parser.mode, |
|
body: cell, |
|
}; |
|
if (style) { |
|
cell = { |
|
type: "styling", |
|
mode: parser.mode, |
|
style, |
|
body: [cell], |
|
}; |
|
} |
|
row.push(cell); |
|
const next = parser.fetch().text; |
|
if (next === "&") { |
|
if (maxNumCols && row.length === maxNumCols) { |
|
if (singleRow || colSeparationType) { |
|
|
|
throw new ParseError("Too many tab characters: &", |
|
parser.nextToken); |
|
} else { |
|
|
|
parser.settings.reportNonstrict("textEnv", "Too few columns " + |
|
"specified in the {array} column argument."); |
|
} |
|
} |
|
parser.consume(); |
|
} else if (next === "\\end") { |
|
endRow(); |
|
|
|
|
|
|
|
|
|
if (row.length === 1 && cell.type === "styling" && |
|
cell.body[0].body.length === 0 && |
|
(body.length > 1 || !emptySingleRow)) { |
|
body.pop(); |
|
} |
|
if (hLinesBeforeRow.length < body.length + 1) { |
|
hLinesBeforeRow.push([]); |
|
} |
|
break; |
|
} else if (next === "\\\\") { |
|
parser.consume(); |
|
let size; |
|
|
|
|
|
|
|
|
|
|
|
if (parser.gullet.future().text !== " ") { |
|
size = parser.parseSizeGroup(true); |
|
} |
|
rowGaps.push(size ? size.value : null); |
|
endRow(); |
|
|
|
|
|
hLinesBeforeRow.push(getHLines(parser)); |
|
|
|
row = []; |
|
body.push(row); |
|
beginRow(); |
|
} else { |
|
throw new ParseError("Expected & or \\\\ or \\cr or \\end", |
|
parser.nextToken); |
|
} |
|
} |
|
|
|
|
|
parser.gullet.endGroup(); |
|
|
|
parser.gullet.endGroup(); |
|
|
|
return { |
|
type: "array", |
|
mode: parser.mode, |
|
addJot, |
|
arraystretch, |
|
body, |
|
cols, |
|
rowGaps, |
|
hskipBeforeAndAfter, |
|
hLinesBeforeRow, |
|
colSeparationType, |
|
tags, |
|
leqno, |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
function dCellStyle(envName): StyleStr { |
|
if (envName.slice(0, 1) === "d") { |
|
return "display"; |
|
} else { |
|
return "text"; |
|
} |
|
} |
|
|
|
type Outrow = { |
|
[idx: number]: *, |
|
height: number, |
|
depth: number, |
|
pos: number, |
|
}; |
|
|
|
const htmlBuilder: HtmlBuilder<"array"> = function(group, options) { |
|
let r; |
|
let c; |
|
const nr = group.body.length; |
|
const hLinesBeforeRow = group.hLinesBeforeRow; |
|
let nc = 0; |
|
let body = new Array(nr); |
|
const hlines = []; |
|
|
|
const ruleThickness = Math.max( |
|
|
|
(options.fontMetrics().arrayRuleWidth), |
|
options.minRuleThickness, |
|
); |
|
|
|
|
|
const pt = 1 / options.fontMetrics().ptPerEm; |
|
let arraycolsep = 5 * pt; |
|
if (group.colSeparationType && group.colSeparationType === "small") { |
|
|
|
|
|
|
|
|
|
|
|
const localMultiplier = options.havingStyle(Style.SCRIPT).sizeMultiplier; |
|
arraycolsep = 0.2778 * (localMultiplier / options.sizeMultiplier); |
|
} |
|
|
|
|
|
const baselineskip = group.colSeparationType === "CD" |
|
? calculateSize({number: 3, unit: "ex"}, options) |
|
: 12 * pt; |
|
|
|
|
|
const jot = 3 * pt; |
|
const arrayskip = group.arraystretch * baselineskip; |
|
const arstrutHeight = 0.7 * arrayskip; |
|
const arstrutDepth = 0.3 * arrayskip; |
|
|
|
let totalHeight = 0; |
|
|
|
|
|
function setHLinePos(hlinesInGap: boolean[]) { |
|
for (let i = 0; i < hlinesInGap.length; ++i) { |
|
if (i > 0) { |
|
totalHeight += 0.25; |
|
} |
|
hlines.push({pos: totalHeight, isDashed: hlinesInGap[i]}); |
|
} |
|
} |
|
setHLinePos(hLinesBeforeRow[0]); |
|
|
|
for (r = 0; r < group.body.length; ++r) { |
|
const inrow = group.body[r]; |
|
let height = arstrutHeight; |
|
let depth = arstrutDepth; |
|
|
|
if (nc < inrow.length) { |
|
nc = inrow.length; |
|
} |
|
|
|
const outrow: Outrow = (new Array(inrow.length): any); |
|
for (c = 0; c < inrow.length; ++c) { |
|
const elt = html.buildGroup(inrow[c], options); |
|
if (depth < elt.depth) { |
|
depth = elt.depth; |
|
} |
|
if (height < elt.height) { |
|
height = elt.height; |
|
} |
|
outrow[c] = elt; |
|
} |
|
|
|
const rowGap = group.rowGaps[r]; |
|
let gap = 0; |
|
if (rowGap) { |
|
gap = calculateSize(rowGap, options); |
|
if (gap > 0) { |
|
gap += arstrutDepth; |
|
if (depth < gap) { |
|
depth = gap; |
|
} |
|
gap = 0; |
|
} |
|
} |
|
|
|
|
|
|
|
if (group.addJot) { |
|
depth += jot; |
|
} |
|
|
|
outrow.height = height; |
|
outrow.depth = depth; |
|
totalHeight += height; |
|
outrow.pos = totalHeight; |
|
totalHeight += depth + gap; |
|
body[r] = outrow; |
|
|
|
|
|
setHLinePos(hLinesBeforeRow[r + 1]); |
|
} |
|
|
|
const offset = totalHeight / 2 + options.fontMetrics().axisHeight; |
|
const colDescriptions = group.cols || []; |
|
const cols = []; |
|
let colSep; |
|
let colDescrNum; |
|
|
|
const tagSpans = []; |
|
if (group.tags && group.tags.some((tag) => tag)) { |
|
|
|
|
|
for (r = 0; r < nr; ++r) { |
|
const rw = body[r]; |
|
const shift = rw.pos - offset; |
|
const tag = group.tags[r]; |
|
let tagSpan; |
|
if (tag === true) { |
|
tagSpan = buildCommon.makeSpan(["eqn-num"], [], options); |
|
} else if (tag === false) { |
|
|
|
tagSpan = buildCommon.makeSpan([], [], options); |
|
} else { |
|
tagSpan = buildCommon.makeSpan([], |
|
html.buildExpression(tag, options, true), options); |
|
} |
|
tagSpan.depth = rw.depth; |
|
tagSpan.height = rw.height; |
|
tagSpans.push({type: "elem", elem: tagSpan, shift}); |
|
} |
|
} |
|
|
|
for (c = 0, colDescrNum = 0; |
|
|
|
|
|
c < nc || colDescrNum < colDescriptions.length; |
|
++c, ++colDescrNum) { |
|
|
|
let colDescr = colDescriptions[colDescrNum] || {}; |
|
|
|
let firstSeparator = true; |
|
while (colDescr.type === "separator") { |
|
|
|
|
|
if (!firstSeparator) { |
|
colSep = buildCommon.makeSpan(["arraycolsep"], []); |
|
colSep.style.width = |
|
makeEm(options.fontMetrics().doubleRuleSep); |
|
cols.push(colSep); |
|
} |
|
|
|
if (colDescr.separator === "|" || colDescr.separator === ":") { |
|
const lineType = (colDescr.separator === "|") ? "solid" : "dashed"; |
|
const separator = buildCommon.makeSpan( |
|
["vertical-separator"], [], options |
|
); |
|
separator.style.height = makeEm(totalHeight); |
|
separator.style.borderRightWidth = makeEm(ruleThickness); |
|
separator.style.borderRightStyle = lineType; |
|
separator.style.margin = `0 ${makeEm(-ruleThickness / 2)}`; |
|
const shift = totalHeight - offset; |
|
if (shift) { |
|
separator.style.verticalAlign = makeEm(-shift); |
|
} |
|
|
|
cols.push(separator); |
|
} else { |
|
throw new ParseError( |
|
"Invalid separator type: " + colDescr.separator); |
|
} |
|
|
|
colDescrNum++; |
|
colDescr = colDescriptions[colDescrNum] || {}; |
|
firstSeparator = false; |
|
} |
|
|
|
if (c >= nc) { |
|
continue; |
|
} |
|
|
|
let sepwidth; |
|
if (c > 0 || group.hskipBeforeAndAfter) { |
|
sepwidth = utils.deflt(colDescr.pregap, arraycolsep); |
|
if (sepwidth !== 0) { |
|
colSep = buildCommon.makeSpan(["arraycolsep"], []); |
|
colSep.style.width = makeEm(sepwidth); |
|
cols.push(colSep); |
|
} |
|
} |
|
|
|
let col = []; |
|
for (r = 0; r < nr; ++r) { |
|
const row = body[r]; |
|
const elem = row[c]; |
|
if (!elem) { |
|
continue; |
|
} |
|
const shift = row.pos - offset; |
|
elem.depth = row.depth; |
|
elem.height = row.height; |
|
col.push({type: "elem", elem: elem, shift: shift}); |
|
} |
|
|
|
col = buildCommon.makeVList({ |
|
positionType: "individualShift", |
|
children: col, |
|
}, options); |
|
col = buildCommon.makeSpan( |
|
["col-align-" + (colDescr.align || "c")], |
|
[col]); |
|
cols.push(col); |
|
|
|
if (c < nc - 1 || group.hskipBeforeAndAfter) { |
|
sepwidth = utils.deflt(colDescr.postgap, arraycolsep); |
|
if (sepwidth !== 0) { |
|
colSep = buildCommon.makeSpan(["arraycolsep"], []); |
|
colSep.style.width = makeEm(sepwidth); |
|
cols.push(colSep); |
|
} |
|
} |
|
} |
|
body = buildCommon.makeSpan(["mtable"], cols); |
|
|
|
|
|
if (hlines.length > 0) { |
|
const line = buildCommon.makeLineSpan("hline", options, ruleThickness); |
|
const dashes = buildCommon.makeLineSpan("hdashline", options, |
|
ruleThickness); |
|
const vListElems = [{type: "elem", elem: body, shift: 0}]; |
|
while (hlines.length > 0) { |
|
const hline = hlines.pop(); |
|
const lineShift = hline.pos - offset; |
|
if (hline.isDashed) { |
|
vListElems.push({type: "elem", elem: dashes, shift: lineShift}); |
|
} else { |
|
vListElems.push({type: "elem", elem: line, shift: lineShift}); |
|
} |
|
} |
|
body = buildCommon.makeVList({ |
|
positionType: "individualShift", |
|
children: vListElems, |
|
}, options); |
|
} |
|
|
|
if (tagSpans.length === 0) { |
|
return buildCommon.makeSpan(["mord"], [body], options); |
|
} else { |
|
let eqnNumCol = buildCommon.makeVList({ |
|
positionType: "individualShift", |
|
children: tagSpans, |
|
}, options); |
|
eqnNumCol = buildCommon.makeSpan(["tag"], [eqnNumCol], options); |
|
return buildCommon.makeFragment([body, eqnNumCol]); |
|
} |
|
}; |
|
|
|
const alignMap = { |
|
c: "center ", |
|
l: "left ", |
|
r: "right ", |
|
}; |
|
|
|
const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) { |
|
const tbl = []; |
|
const glue = new mathMLTree.MathNode("mtd", [], ["mtr-glue"]); |
|
const tag = new mathMLTree.MathNode("mtd", [], ["mml-eqn-num"]); |
|
for (let i = 0; i < group.body.length; i++) { |
|
const rw = group.body[i]; |
|
const row = []; |
|
for (let j = 0; j < rw.length; j++) { |
|
row.push(new mathMLTree.MathNode("mtd", |
|
[mml.buildGroup(rw[j], options)])); |
|
} |
|
if (group.tags && group.tags[i]) { |
|
row.unshift(glue); |
|
row.push(glue); |
|
if (group.leqno) { |
|
row.unshift(tag); |
|
} else { |
|
row.push(tag); |
|
} |
|
} |
|
tbl.push(new mathMLTree.MathNode("mtr", row)); |
|
} |
|
let table = new mathMLTree.MathNode("mtable", tbl); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const gap = (group.arraystretch === 0.5) |
|
? 0.1 |
|
: 0.16 + group.arraystretch - 1 + (group.addJot ? 0.09 : 0); |
|
table.setAttribute("rowspacing", makeEm(gap)); |
|
|
|
|
|
|
|
let menclose = ""; |
|
let align = ""; |
|
|
|
if (group.cols && group.cols.length > 0) { |
|
|
|
const cols = group.cols; |
|
let columnLines = ""; |
|
let prevTypeWasAlign = false; |
|
let iStart = 0; |
|
let iEnd = cols.length; |
|
|
|
if (cols[0].type === "separator") { |
|
menclose += "top "; |
|
iStart = 1; |
|
} |
|
if (cols[cols.length - 1].type === "separator") { |
|
menclose += "bottom "; |
|
iEnd -= 1; |
|
} |
|
|
|
for (let i = iStart; i < iEnd; i++) { |
|
if (cols[i].type === "align") { |
|
align += alignMap[cols[i].align]; |
|
|
|
if (prevTypeWasAlign) { |
|
columnLines += "none "; |
|
} |
|
prevTypeWasAlign = true; |
|
} else if (cols[i].type === "separator") { |
|
|
|
|
|
if (prevTypeWasAlign) { |
|
columnLines += cols[i].separator === "|" |
|
? "solid " |
|
: "dashed "; |
|
prevTypeWasAlign = false; |
|
} |
|
} |
|
} |
|
|
|
table.setAttribute("columnalign", align.trim()); |
|
|
|
if (/[sd]/.test(columnLines)) { |
|
table.setAttribute("columnlines", columnLines.trim()); |
|
} |
|
} |
|
|
|
|
|
if (group.colSeparationType === "align") { |
|
const cols = group.cols || []; |
|
let spacing = ""; |
|
for (let i = 1; i < cols.length; i++) { |
|
spacing += i % 2 ? "0em " : "1em "; |
|
} |
|
table.setAttribute("columnspacing", spacing.trim()); |
|
} else if (group.colSeparationType === "alignat" || |
|
group.colSeparationType === "gather") { |
|
table.setAttribute("columnspacing", "0em"); |
|
} else if (group.colSeparationType === "small") { |
|
table.setAttribute("columnspacing", "0.2778em"); |
|
} else if (group.colSeparationType === "CD") { |
|
table.setAttribute("columnspacing", "0.5em"); |
|
} else { |
|
table.setAttribute("columnspacing", "1em"); |
|
} |
|
|
|
|
|
let rowLines = ""; |
|
const hlines = group.hLinesBeforeRow; |
|
|
|
menclose += hlines[0].length > 0 ? "left " : ""; |
|
menclose += hlines[hlines.length - 1].length > 0 ? "right " : ""; |
|
|
|
for (let i = 1; i < hlines.length - 1; i++) { |
|
rowLines += (hlines[i].length === 0) |
|
? "none " |
|
|
|
: hlines[i][0] ? "dashed " : "solid "; |
|
} |
|
if (/[sd]/.test(rowLines)) { |
|
table.setAttribute("rowlines", rowLines.trim()); |
|
} |
|
|
|
if (menclose !== "") { |
|
table = new mathMLTree.MathNode("menclose", [table]); |
|
table.setAttribute("notation", menclose.trim()); |
|
} |
|
|
|
if (group.arraystretch && group.arraystretch < 1) { |
|
|
|
table = new mathMLTree.MathNode("mstyle", [table]); |
|
table.setAttribute("scriptlevel", "1"); |
|
} |
|
|
|
return table; |
|
}; |
|
|
|
|
|
const alignedHandler = function(context, args) { |
|
if (context.envName.indexOf("ed") === -1) { |
|
validateAmsEnvironmentContext(context); |
|
} |
|
const cols = []; |
|
const separationType = context.envName.indexOf("at") > -1 ? "alignat" : "align"; |
|
const isSplit = context.envName === "split"; |
|
const res = parseArray(context.parser, |
|
{ |
|
cols, |
|
addJot: true, |
|
autoTag: isSplit ? undefined : getAutoTag(context.envName), |
|
emptySingleRow: true, |
|
colSeparationType: separationType, |
|
maxNumCols: isSplit ? 2 : undefined, |
|
leqno: context.parser.settings.leqno, |
|
}, |
|
"display" |
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let numMaths; |
|
let numCols = 0; |
|
const emptyGroup = { |
|
type: "ordgroup", |
|
mode: context.mode, |
|
body: [], |
|
}; |
|
if (args[0] && args[0].type === "ordgroup") { |
|
let arg0 = ""; |
|
for (let i = 0; i < args[0].body.length; i++) { |
|
const textord = assertNodeType(args[0].body[i], "textord"); |
|
arg0 += textord.text; |
|
} |
|
numMaths = Number(arg0); |
|
numCols = numMaths * 2; |
|
} |
|
const isAligned = !numCols; |
|
res.body.forEach(function(row) { |
|
for (let i = 1; i < row.length; i += 2) { |
|
|
|
const styling = assertNodeType(row[i], "styling"); |
|
const ordgroup = assertNodeType(styling.body[0], "ordgroup"); |
|
ordgroup.body.unshift(emptyGroup); |
|
} |
|
if (!isAligned) { |
|
const curMaths = row.length / 2; |
|
if (numMaths < curMaths) { |
|
throw new ParseError( |
|
"Too many math in a row: " + |
|
`expected ${numMaths}, but got ${curMaths}`, |
|
row[0]); |
|
} |
|
} else if (numCols < row.length) { |
|
numCols = row.length; |
|
} |
|
}); |
|
|
|
|
|
|
|
|
|
for (let i = 0; i < numCols; ++i) { |
|
let align = "r"; |
|
let pregap = 0; |
|
if (i % 2 === 1) { |
|
align = "l"; |
|
} else if (i > 0 && isAligned) { |
|
pregap = 1; |
|
} |
|
cols[i] = { |
|
type: "align", |
|
align: align, |
|
pregap: pregap, |
|
postgap: 0, |
|
}; |
|
} |
|
res.colSeparationType = isAligned ? "align" : "alignat"; |
|
return res; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: ["array", "darray"], |
|
props: { |
|
numArgs: 1, |
|
}, |
|
handler(context, args) { |
|
|
|
|
|
|
|
|
|
const symNode = checkSymbolNodeType(args[0]); |
|
const colalign: AnyParseNode[] = |
|
symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body; |
|
const cols = colalign.map(function(nde) { |
|
const node = assertSymbolNodeType(nde); |
|
const ca = node.text; |
|
if ("lcr".indexOf(ca) !== -1) { |
|
return { |
|
type: "align", |
|
align: ca, |
|
}; |
|
} else if (ca === "|") { |
|
return { |
|
type: "separator", |
|
separator: "|", |
|
}; |
|
} else if (ca === ":") { |
|
return { |
|
type: "separator", |
|
separator: ":", |
|
}; |
|
} |
|
throw new ParseError("Unknown column alignment: " + ca, nde); |
|
}); |
|
const res = { |
|
cols, |
|
hskipBeforeAndAfter: true, |
|
maxNumCols: cols.length, |
|
}; |
|
return parseArray(context.parser, res, dCellStyle(context.envName)); |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: [ |
|
"matrix", |
|
"pmatrix", |
|
"bmatrix", |
|
"Bmatrix", |
|
"vmatrix", |
|
"Vmatrix", |
|
"matrix*", |
|
"pmatrix*", |
|
"bmatrix*", |
|
"Bmatrix*", |
|
"vmatrix*", |
|
"Vmatrix*", |
|
], |
|
props: { |
|
numArgs: 0, |
|
}, |
|
handler(context) { |
|
const delimiters = { |
|
"matrix": null, |
|
"pmatrix": ["(", ")"], |
|
"bmatrix": ["[", "]"], |
|
"Bmatrix": ["\\{", "\\}"], |
|
"vmatrix": ["|", "|"], |
|
"Vmatrix": ["\\Vert", "\\Vert"], |
|
}[context.envName.replace("*", "")]; |
|
|
|
let colAlign = "c"; |
|
const payload = { |
|
hskipBeforeAndAfter: false, |
|
cols: [{type: "align", align: colAlign}], |
|
}; |
|
if (context.envName.charAt(context.envName.length - 1) === "*") { |
|
|
|
|
|
const parser = context.parser; |
|
parser.consumeSpaces(); |
|
if (parser.fetch().text === "[") { |
|
parser.consume(); |
|
parser.consumeSpaces(); |
|
colAlign = parser.fetch().text; |
|
if ("lcr".indexOf(colAlign) === -1) { |
|
throw new ParseError("Expected l or c or r", parser.nextToken); |
|
} |
|
parser.consume(); |
|
parser.consumeSpaces(); |
|
parser.expect("]"); |
|
parser.consume(); |
|
payload.cols = [{type: "align", align: colAlign}]; |
|
} |
|
} |
|
const res: ParseNode<"array"> = |
|
parseArray(context.parser, payload, dCellStyle(context.envName)); |
|
|
|
const numCols = Math.max(0, ...res.body.map((row) => row.length)); |
|
res.cols = new Array(numCols).fill( |
|
{type: "align", align: colAlign} |
|
); |
|
return delimiters ? { |
|
type: "leftright", |
|
mode: context.mode, |
|
body: [res], |
|
left: delimiters[0], |
|
right: delimiters[1], |
|
rightColor: undefined, |
|
} : res; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: ["smallmatrix"], |
|
props: { |
|
numArgs: 0, |
|
}, |
|
handler(context) { |
|
const payload = {arraystretch: 0.5}; |
|
const res = parseArray(context.parser, payload, "script"); |
|
res.colSeparationType = "small"; |
|
return res; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: ["subarray"], |
|
props: { |
|
numArgs: 1, |
|
}, |
|
handler(context, args) { |
|
|
|
const symNode = checkSymbolNodeType(args[0]); |
|
const colalign: AnyParseNode[] = |
|
symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body; |
|
const cols = colalign.map(function(nde) { |
|
const node = assertSymbolNodeType(nde); |
|
const ca = node.text; |
|
|
|
if ("lc".indexOf(ca) !== -1) { |
|
return { |
|
type: "align", |
|
align: ca, |
|
}; |
|
} |
|
throw new ParseError("Unknown column alignment: " + ca, nde); |
|
}); |
|
if (cols.length > 1) { |
|
throw new ParseError("{subarray} can contain only one column"); |
|
} |
|
let res = { |
|
cols, |
|
hskipBeforeAndAfter: false, |
|
arraystretch: 0.5, |
|
}; |
|
res = parseArray(context.parser, res, "script"); |
|
if (res.body.length > 0 && res.body[0].length > 1) { |
|
throw new ParseError("{subarray} can contain only one column"); |
|
} |
|
return res; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: [ |
|
"cases", |
|
"dcases", |
|
"rcases", |
|
"drcases", |
|
], |
|
props: { |
|
numArgs: 0, |
|
}, |
|
handler(context) { |
|
const payload = { |
|
arraystretch: 1.2, |
|
cols: [{ |
|
type: "align", |
|
align: "l", |
|
pregap: 0, |
|
|
|
|
|
|
|
|
|
postgap: 1.0, |
|
}, { |
|
type: "align", |
|
align: "l", |
|
pregap: 0, |
|
postgap: 0, |
|
}], |
|
}; |
|
const res: ParseNode<"array"> = |
|
parseArray(context.parser, payload, dCellStyle(context.envName)); |
|
return { |
|
type: "leftright", |
|
mode: context.mode, |
|
body: [res], |
|
left: context.envName.indexOf("r") > -1 ? "." : "\\{", |
|
right: context.envName.indexOf("r") > -1 ? "\\}" : ".", |
|
rightColor: undefined, |
|
}; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: ["align", "align*", "aligned", "split"], |
|
props: { |
|
numArgs: 0, |
|
}, |
|
handler: alignedHandler, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
|
|
|
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: ["gathered", "gather", "gather*"], |
|
props: { |
|
numArgs: 0, |
|
}, |
|
handler(context) { |
|
if (utils.contains(["gather", "gather*"], context.envName)) { |
|
validateAmsEnvironmentContext(context); |
|
} |
|
const res = { |
|
cols: [{ |
|
type: "align", |
|
align: "c", |
|
}], |
|
addJot: true, |
|
colSeparationType: "gather", |
|
autoTag: getAutoTag(context.envName), |
|
emptySingleRow: true, |
|
leqno: context.parser.settings.leqno, |
|
}; |
|
return parseArray(context.parser, res, "display"); |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
|
|
|
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: ["alignat", "alignat*", "alignedat"], |
|
props: { |
|
numArgs: 1, |
|
}, |
|
handler: alignedHandler, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: ["equation", "equation*"], |
|
props: { |
|
numArgs: 0, |
|
}, |
|
handler(context) { |
|
validateAmsEnvironmentContext(context); |
|
const res = { |
|
autoTag: getAutoTag(context.envName), |
|
emptySingleRow: true, |
|
singleRow: true, |
|
maxNumCols: 1, |
|
leqno: context.parser.settings.leqno, |
|
}; |
|
return parseArray(context.parser, res, "display"); |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineEnvironment({ |
|
type: "array", |
|
names: ["CD"], |
|
props: { |
|
numArgs: 0, |
|
}, |
|
handler(context) { |
|
validateAmsEnvironmentContext(context); |
|
return parseCD(context.parser); |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineMacro("\\nonumber", "\\gdef\\@eqnsw{0}"); |
|
defineMacro("\\notag", "\\nonumber"); |
|
|
|
|
|
defineFunction({ |
|
type: "text", |
|
names: ["\\hline", "\\hdashline"], |
|
props: { |
|
numArgs: 0, |
|
allowedInText: true, |
|
allowedInMath: true, |
|
}, |
|
handler(context, args) { |
|
throw new ParseError( |
|
`${context.funcName} valid only within array environment`); |
|
}, |
|
}); |
|
|