Skip to content

Commit ed17439

Browse files
authored
Merge pull request #708 from mathjax/amsmath-update
Amsmath update + flalign, xalign, xxalign.
2 parents 2c8f278 + e5e98f9 commit ed17439

13 files changed

Lines changed: 318 additions & 51 deletions

File tree

ts/input/tex.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,13 @@ export class TeX<N, T, D> extends AbstractInputJax<N, T, D> {
181181
this.latex = math.math;
182182
let node: MmlNode;
183183
this.parseOptions.tags.startEquation(math);
184+
let globalEnv;
184185
try {
185186
let parser = new TexParser(this.latex,
186187
{display: display, isInner: false},
187188
this.parseOptions);
188189
node = parser.mml();
190+
globalEnv = parser.stack.global;
189191
} catch (err) {
190192
if (!(err instanceof TexError)) {
191193
throw err;
@@ -194,6 +196,9 @@ export class TeX<N, T, D> extends AbstractInputJax<N, T, D> {
194196
node = this.options.formatError(this, err);
195197
}
196198
node = this.parseOptions.nodeFactory.create('node', 'math', [node]);
199+
if (globalEnv?.indentalign) {
200+
NodeUtil.setAttribute(node, 'indentalign', globalEnv.indentalign);
201+
}
197202
if (display) {
198203
NodeUtil.setAttribute(node, 'display', 'block');
199204
}

ts/input/tex/ParseUtil.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import NodeUtil from './NodeUtil.js';
3030
import TexParser from './TexParser.js';
3131
import TexError from './TexError.js';
3232
import {entities} from '../../util/Entities.js';
33+
import {em} from '../../util/lengths.js';
3334

3435

3536
namespace ParseUtil {
@@ -104,11 +105,18 @@ namespace ParseUtil {
104105
* @param {number} m The number.
105106
* @return {string} The em dimension string.
106107
*/
107-
export function Em(m: number): string {
108-
if (Math.abs(m) < .0006) {
109-
return '0em';
110-
}
111-
return m.toFixed(3).replace(/\.?0+$/, '') + 'em';
108+
export function Em(m: number): string {
109+
return em(m);
110+
}
111+
112+
113+
/**
114+
* Takes an array of numbers and returns a space-separated string of em values.
115+
* @param {number[]} W The widths to be turned into em values
116+
* @return {string} The numbers with em units, separated by spaces.
117+
*/
118+
export function cols(...W: number[]): string {
119+
return W.map(n => Em(n)).join(' ');
112120
}
113121

114122

ts/input/tex/Tags.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,6 @@ export namespace TagsFactory {
616616
tagSide: 'right',
617617
// This is the amount of indentation (from right or left) for the tags.
618618
tagIndent: '0.8em',
619-
// This is the width to use for the multline environment
620-
multlineWidth: '85%',
621619
// make element ID's use \label name rather than equation number
622620
// MJ puts in an equation prefix: mjx-eqn
623621
// When true it uses the label name XXX as mjx-eqn:XXX

ts/input/tex/ams/AmsConfiguration.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*/
2424

2525
import {Configuration, ParserConfiguration} from '../Configuration.js';
26-
import {MultlineItem} from './AmsItems.js';
26+
import {MultlineItem, FlalignItem} from './AmsItems.js';
2727
import {AbstractTags} from '../Tags.js';
2828
import {NEW_OPS} from './AmsMethods.js';
2929
import './AmsMappings.js';
@@ -57,9 +57,28 @@ export const AmsConfiguration = Configuration.create(
5757
'AMSmath-mathchar0mo', 'AMSmath-macros', 'AMSmath-delimiter'],
5858
environment: ['AMSmath-environment']
5959
},
60-
items: {[MultlineItem.prototype.kind]: MultlineItem},
60+
items: {
61+
[MultlineItem.prototype.kind]: MultlineItem,
62+
[FlalignItem.prototype.kind]: FlalignItem,
63+
},
6164
tags: {'ams': AmsTags},
62-
init: init
65+
init: init,
66+
config: (_config: ParserConfiguration, jax: any) => {
67+
//
68+
// Move multlineWidth from old location to ams block (remove in next version)
69+
//
70+
if (jax.parseOptions.options.multlineWidth) {
71+
jax.parseOptions.options.ams.multlineWidth = jax.parseOptions.options.multlineWidth;
72+
}
73+
delete jax.parseOptions.options.multlineWidth;
74+
},
75+
options: {
76+
multlineWidth: '',
77+
ams: {
78+
multlineWidth: '100%', // The width to use for multline environments.
79+
multlineIndent: '1em', // The margin to use on both sides of multline environments.
80+
}
81+
}
6382
}
6483
);
6584

ts/input/tex/ams/AmsItems.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@
2323
*/
2424

2525

26-
import {ArrayItem} from '../base/BaseItems.js';
26+
import {ArrayItem, EqnArrayItem} from '../base/BaseItems.js';
2727
import ParseUtil from '../ParseUtil.js';
2828
import NodeUtil from '../NodeUtil.js';
2929
import TexError from '../TexError.js';
3030
import {TexConstant} from '../TexConstants.js';
31+
import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
3132

3233

3334
/**
@@ -117,3 +118,107 @@ export class MultlineItem extends ArrayItem {
117118
this.factory.configuration.tags.end();
118119
}
119120
}
121+
122+
/**
123+
* StackItem for handling flalign, xalignat, and xxalignat environments.
124+
*/
125+
export class FlalignItem extends EqnArrayItem {
126+
127+
/**
128+
* @override
129+
*/
130+
get kind() {
131+
return 'flalign';
132+
}
133+
134+
135+
/**
136+
* @override
137+
*/
138+
constructor(factory: any, public name: string, public numbered: boolean,
139+
public padded: boolean, public center: boolean) {
140+
super(factory);
141+
this.factory.configuration.tags.start(name, numbered, numbered);
142+
}
143+
144+
/**
145+
* @override
146+
*/
147+
public EndEntry() {
148+
super.EndEntry();
149+
const n = this.getProperty('xalignat') as number;
150+
if (!n) return;
151+
if (this.row.length > n) {
152+
throw new TexError('XalignOverflow', 'Extra %1 in row of %2', '&', this.name);
153+
}
154+
}
155+
156+
157+
/**
158+
* @override
159+
*/
160+
public EndRow() {
161+
let cell: MmlNode;
162+
let row = this.row;
163+
//
164+
// For xalignat and xxalignat, pad the row to the expected number if it is too short.
165+
//
166+
const n = this.getProperty('xalignat') as number;
167+
while (row.length < n) {
168+
row.push(this.create('node', 'mtd'));
169+
}
170+
//
171+
// Insert padding cells between pairs of entries, as needed for "fit" columns,
172+
// and include initial and end cells if that is needed.
173+
//
174+
this.row = [];
175+
if (this.padded) {
176+
this.row.push(this.create('node', 'mtd'));
177+
}
178+
while ((cell = row.shift())) {
179+
this.row.push(cell);
180+
cell = row.shift();
181+
if (cell) this.row.push(cell);
182+
if (row.length || this.padded) {
183+
this.row.push(this.create('node', 'mtd'));
184+
}
185+
}
186+
//
187+
if (this.row.length > this.maxrow) {
188+
this.maxrow = this.row.length;
189+
}
190+
super.EndRow();
191+
//
192+
// For full-width environments with labels that aren't supposed to take up space,
193+
// move the label into a zero-width mpadded element that laps in the proper direction.
194+
//
195+
const mtr = this.table[this.table.length - 1];
196+
if (this.getProperty('zeroWidthLabel') && mtr.isKind('mlabeledtr')) {
197+
const mtd = NodeUtil.getChildren(mtr)[0];
198+
const side = this.factory.configuration.options['tagSide'];
199+
const def = {width: 0, ...(side === 'right' ? {lspace: '-1width'} : {})};
200+
const mpadded = this.create('node', 'mpadded', NodeUtil.getChildren(mtd), def);
201+
mtd.setChildren([mpadded]);
202+
}
203+
}
204+
205+
206+
/**
207+
* @override
208+
*/
209+
public EndTable() {
210+
super.EndTable();
211+
if (this.center) {
212+
//
213+
// If there is only one equation (one pair):
214+
// Don't make it 100%, and don't change the indentalign.
215+
//
216+
if (this.maxrow <= 2) {
217+
const def = this.arraydef;
218+
delete def.width;
219+
delete this.global.indentalign;
220+
}
221+
}
222+
}
223+
224+
}

ts/input/tex/ams/AmsMappings.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,7 @@ import {TexConstant} from '../TexConstants.js';
2929
import ParseMethods from '../ParseMethods.js';
3030
import ParseUtil from '../ParseUtil.js';
3131
import {TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
32-
import {MATHSPACE, em} from '../../../util/lengths.js';
33-
34-
35-
let COLS = function(W: number[]) {
36-
const WW: string[] = [];
37-
for (let i = 0, m = W.length; i < m; i++) {
38-
WW[i] = ParseUtil.Em(W[i]);
39-
}
40-
return WW.join(' ');
41-
};
32+
import {MATHSPACE} from '../../../util/lengths.js';
4233

4334

4435
/**
@@ -107,28 +98,32 @@ new sm.CommandMap('AMSmath-macros', {
10798
* Environments from the AMS Math package.
10899
*/
109100
new sm.EnvironmentMap('AMSmath-environment', ParseMethods.environment, {
101+
'equation*': ['Equation', null, false],
110102
'eqnarray*': ['EqnArray', null, false, true, 'rcl',
111-
'0 ' + em(MATHSPACE.thickmathspace), '.5em'],
112-
align: ['EqnArray', null, true, true, 'rlrlrlrlrlrl',
113-
COLS([0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0])],
114-
'align*': ['EqnArray', null, false, true, 'rlrlrlrlrlrl',
115-
COLS([0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0])],
103+
ParseUtil.cols(0, MATHSPACE.thickmathspace), '.5em'],
104+
align: ['EqnArray', null, true, true, 'rl', ParseUtil.cols(0, 2)],
105+
'align*': ['EqnArray', null, false, true, 'rl', ParseUtil.cols(0, 2)],
116106
multline: ['Multline', null, true],
117107
'multline*': ['Multline', null, false],
118-
split: ['EqnArray', null, false, false, 'rl', COLS([0])],
108+
split: ['EqnArray', null, false, false, 'rl', ParseUtil.cols(0)],
119109
gather: ['EqnArray', null, true, true, 'c'],
120110
'gather*': ['EqnArray', null, false, true, 'c'],
121111

122112
alignat: ['AlignAt', null, true, true],
123113
'alignat*': ['AlignAt', null, false, true],
124114
alignedat: ['AlignAt', null, false, false],
125115

126-
aligned: ['AmsEqnArray', null, null, null, 'rlrlrlrlrlrl',
127-
COLS([0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0]), '.5em', 'D'],
116+
aligned: ['AmsEqnArray', null, null, null, 'rl', ParseUtil.cols(0, 2), '.5em', 'D'],
128117
gathered: ['AmsEqnArray', null, null, null, 'c', null, '.5em', 'D'],
129118

130-
subarray: ['Array', null, null, null, null, COLS([0]), '0.1em', 'S', 1],
131-
smallmatrix: ['Array', null, null, null, 'c', COLS([1 / 3]),
119+
xalignat: ['XalignAt', null, true, true],
120+
'xalignat*': ['XalignAt', null, false, true],
121+
xxalignat: ['XalignAt', null, false, false],
122+
flalign: ['FlalignArray', null, true, false, true, 'rlc', 'auto auto fit'],
123+
'flalign*': ['FlalignArray', null, false, false, true, 'rlc', 'auto auto fit'],
124+
125+
subarray: ['Array', null, null, null, null, ParseUtil.cols(0), '0.1em', 'S', 1],
126+
smallmatrix: ['Array', null, null, null, 'c', ParseUtil.cols(1 / 3),
132127
'.2em', 'S', 1],
133128
matrix: ['Array', null, null, null, 'c'],
134129
pmatrix: ['Array', null, '(', ')', 'c'],

ts/input/tex/ams/AmsMethods.ts

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import TexError from '../TexError.js';
3333
import {Macro} from '../Symbol.js';
3434
import {CommandMap} from '../SymbolMap.js';
3535
import {ArrayItem} from '../base/BaseItems.js';
36+
import {FlalignItem} from './AmsItems.js';
3637
import BaseMethods from '../base/BaseMethods.js';
3738
import {TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
3839
import {MmlMunderover} from '../../../core/MmlTree/MmlNodes/munderover.js';
@@ -118,15 +119,81 @@ AmsMethods.Multline = function (parser: TexParser, begin: StackItem, numbered: b
118119
item.arraydef = {
119120
displaystyle: true,
120121
rowspacing: '.5em',
121-
columnwidth: '100%',
122-
width: parser.options['multlineWidth'],
122+
columnspacing: '100%',
123+
width: parser.options.ams['multlineWidth'],
123124
side: parser.options['tagSide'],
124-
minlabelspacing: parser.options['tagIndent']
125+
minlabelspacing: parser.options['tagIndent'],
126+
framespacing: parser.options.ams['multlineIndent'] + ' 0',
127+
frame: '', // Use frame spacing with no actual frame
128+
'data-width-includes-label': true // take label space out of 100% width
125129
};
126130
return item;
127131
};
128132

129133

134+
/**
135+
* Generate an align at environment.
136+
* @param {TexParser} parser The current TeX parser.
137+
* @param {StackItem} begin The begin stackitem.
138+
* @param {boolean} numbered Is this a numbered array.
139+
* @param {boolean} padded Is it padded.
140+
*/
141+
AmsMethods.XalignAt = function(parser: TexParser, begin: StackItem,
142+
numbered: boolean, padded: boolean) {
143+
let n = parser.GetArgument('\\begin{' + begin.getName() + '}');
144+
if (n.match(/[^0-9]/)) {
145+
throw new TexError('PositiveIntegerArg',
146+
'Argument to %1 must me a positive integer',
147+
'\\begin{' + begin.getName() + '}');
148+
}
149+
const align = (padded ? 'crl' : 'rlc');
150+
const width = (padded ? 'fit auto auto' : 'auto auto fit');
151+
const item = AmsMethods.FlalignArray(parser, begin, numbered, padded, false, align, width, true) as FlalignItem;
152+
item.setProperty('xalignat', 2 * parseInt(n));
153+
return item;
154+
};
155+
156+
157+
/**
158+
* Generate an flalign environment.
159+
* @param {TexParser} parser The current TeX parser.
160+
* @param {StackItem} begin The begin stackitem.
161+
* @param {boolean} numbered Is this a numbered array.
162+
* @param {boolean} padded Is it padded.
163+
* @param {boolean} center Is it centered.
164+
* @param {string} align The horizontal alignment for columns
165+
* @param {string} width The column widths of the table
166+
* @param {boolean} zeroWidthLabel True if the label should be in llap/rlap
167+
*/
168+
AmsMethods.FlalignArray = function(parser: TexParser, begin: StackItem, numbered: boolean,
169+
padded: boolean, center: boolean, align: string,
170+
width: string, zeroWidthLabel: boolean = false) {
171+
parser.Push(begin);
172+
ParseUtil.checkEqnEnv(parser);
173+
align = align
174+
.split('')
175+
.join(' ')
176+
.replace(/r/g, 'right')
177+
.replace(/l/g, 'left')
178+
.replace(/c/g, 'center');
179+
const item = parser.itemFactory.create(
180+
'flalign', begin.getName(), numbered, padded, center, parser.stack) as FlalignItem;
181+
item.arraydef = {
182+
width: '100%',
183+
displaystyle: true,
184+
columnalign: align,
185+
columnspacing: '0em',
186+
columnwidth: width,
187+
rowspacing: '3pt',
188+
side: parser.options['tagSide'],
189+
minlabelspacing: (zeroWidthLabel ? '0' : parser.options['tagIndent']),
190+
'data-width-includes-label': true,
191+
};
192+
item.setProperty('zeroWidthLabel', zeroWidthLabel);
193+
return item;
194+
};
195+
196+
130197
export const NEW_OPS = 'ams-declare-ops';
131198

132199
/**
@@ -405,3 +472,5 @@ AmsMethods.Spacer = BaseMethods.Spacer;
405472
AmsMethods.NamedOp = BaseMethods.NamedOp;
406473

407474
AmsMethods.EqnArray = BaseMethods.EqnArray;
475+
476+
AmsMethods.Equation = BaseMethods.Equation;

0 commit comments

Comments
 (0)