Skip to content

Commit d76621f

Browse files
authored
Merge pull request #1004 from mathjax/sre-latex-braille-ext
Sre latex braille ext
2 parents 1bc54fb + 6aedea6 commit d76621f

File tree

5 files changed

+201
-8
lines changed

5 files changed

+201
-8
lines changed

ts/input/tex.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import TexError from './tex/TexError.js';
3737
import ParseOptions from './tex/ParseOptions.js';
3838
import {TagsFactory} from './tex/Tags.js';
3939
import {ParserConfiguration} from './tex/Configuration.js';
40+
import { TexConstant } from './tex/TexConstants.js';
4041
// Import base as it is the default package loaded.
4142
import './tex/base/BaseConfiguration.js';
4243

@@ -199,6 +200,7 @@ export class TeX<N, T, D> extends AbstractInputJax<N, T, D> {
199200
node = this.options.formatError(this, err);
200201
}
201202
node = this.parseOptions.nodeFactory.create('node', 'math', [node]);
203+
node.attributes.set(TexConstant.Attr.LATEX, this.latex);
202204
if (globalEnv?.indentalign) {
203205
NodeUtil.setAttribute(node, 'indentalign', globalEnv.indentalign);
204206
}

ts/input/tex/StackItem.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ export interface NodeStack {
8282
*/
8383
Clear(): void;
8484

85+
86+
/**
87+
* The LaTeX string at the moment item is called.
88+
*/
89+
startStr: string;
90+
91+
/**
92+
* Parser position in the global LaTeX string when item is called.
93+
*/
94+
startI: number;
95+
96+
/**
97+
* Parser position in the global LaTeX string when item is pushed.
98+
*/
99+
stopI: number;
100+
85101
/**
86102
* Returns nodes on the stack item's node stack as an Mml node. I.e., in case
87103
* the item contains more than one node, it creates an mrow.
@@ -97,10 +113,26 @@ export interface NodeStack {
97113

98114
export abstract class MmlStack implements NodeStack {
99115

116+
117+
/**
118+
* @override
119+
*/
120+
public startStr: string = '';
121+
122+
/**
123+
* @override
124+
*/
125+
public startI: number = 0;
126+
127+
/**
128+
* @override
129+
*/
130+
public stopI: number = 0;
131+
100132
/**
101133
* @constructor
102134
* @extends {NodeStack}
103-
* @param {MmlNode[]} nodes An initial list of nodes to put on the stack.
135+
* @param {MmlNode[]} _nodes An initial list of nodes to put on the stack.
104136
*/
105137
constructor(private _nodes: MmlNode[]) { }
106138

ts/input/tex/TexConstants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,9 @@ export namespace TexConstant {
166166
PC: 'pc'
167167
};
168168

169+
export const Attr = {
170+
LATEX: 'data-latex',
171+
LATEXITEM: 'data-latex-item'
172+
}
173+
169174
}

ts/input/tex/TexParser.ts

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ import TexError from './TexError.js';
3232
import {MmlNode, AbstractMmlNode} from '../../core/MmlTree/MmlNode.js';
3333
import {ParseInput, ParseResult} from './Types.js';
3434
import ParseOptions from './ParseOptions.js';
35-
import {StackItem, EnvList} from './StackItem.js';
35+
import {BaseItem, StackItem, EnvList} from './StackItem.js';
3636
import {Token} from './Token.js';
3737
import {OptionList} from '../../util/Options.js';
38+
import { TexConstant } from './TexConstants.js';
3839

3940

4041
/**
@@ -66,6 +67,11 @@ export default class TexParser {
6667
*/
6768
public currentCS: string = '';
6869

70+
/**
71+
* A stack to save the string positions when we restart the parser.
72+
*/
73+
private saveI: number = 0;
74+
6975
/**
7076
* @constructor
7177
* @param {string} _string The string to parse.
@@ -88,6 +94,7 @@ export default class TexParser {
8894
this.stack = new Stack(this.itemFactory, ENV, inner ? isInner : true);
8995
this.Parse();
9096
this.Push(this.itemFactory.create('stop'));
97+
this.updateResult(this.string, this.i);
9198
this.stack.env = ENV;
9299
}
93100

@@ -135,10 +142,14 @@ export default class TexParser {
135142
* @return {ParseResult} The output of the parsing function.
136143
*/
137144
public parse(kind: HandlerType, input: ParseInput): ParseResult {
138-
return this.configuration.handlers.get(kind).parse(input);
145+
const i = this.saveI;
146+
this.saveI = this.i;
147+
let result = this.configuration.handlers.get(kind).parse(input);
148+
this.updateResult(input[1], i);
149+
this.saveI = i;
150+
return result;
139151
}
140152

141-
142153
/**
143154
* Maps a token to its "parse value" if it exists.
144155
* @param {HandlerType} kind Configuration name.
@@ -195,6 +206,11 @@ export default class TexParser {
195206
* @param {StackItem|MmlNode} arg The new item.
196207
*/
197208
public Push(arg: StackItem | MmlNode) {
209+
if (arg instanceof BaseItem) {
210+
arg.startI = this.saveI;
211+
arg.stopI = this.i;
212+
arg.startStr = this.string;
213+
}
198214
if (arg instanceof AbstractMmlNode && arg.isInferred) {
199215
this.PushAll(arg.childNodes);
200216
} else {
@@ -223,6 +239,7 @@ export default class TexParser {
223239
}
224240
let node = this.stack.Top().First;
225241
this.configuration.popParser();
242+
node.attributes.set(TexConstant.Attr.LATEX, this.string);
226243
return node;
227244
}
228245

@@ -511,5 +528,111 @@ export default class TexParser {
511528
return this.configuration.nodeFactory.create(kind, ...rest);
512529
}
513530

531+
/**
532+
* Finalizes the LaTeX for the topmost Mml element on the stack after parsing
533+
* has been completed.
534+
*
535+
* @param {string} input The LaTeX input string for the parser.
536+
* @param {number} old The last parsing position.
537+
*/
538+
// Currently works without translating environments that generate typesetting.
539+
private updateResult(input: string, old: number) {
540+
let node = this.stack.Prev(true) as MmlNode;
541+
if (!node) {
542+
return;
543+
}
544+
// TODO: This can probably be removed once processed. But needs more
545+
// testing.
546+
let existing = node.attributes.get(TexConstant.Attr.LATEXITEM);
547+
if (existing !== undefined) {
548+
node.attributes.set(TexConstant.Attr.LATEX, existing);
549+
return;
550+
}
551+
old = old < this.saveI ? this.saveI : old;
552+
let str = old !== this.i ? this.string.slice(old, this.i) : input;
553+
str = str.trim();
554+
if (!str) {
555+
return;
556+
}
557+
if (input === '\\') {
558+
str = '\\' + str;
559+
}
560+
// These are the cases to handle sub and superscripts.
561+
if (node.attributes.get(TexConstant.Attr.LATEX) === '^' && str !== '^') {
562+
if (str === '}') {
563+
this.composeBraces(node.childNodes[2]);
564+
} else {
565+
node.childNodes[2].attributes.set(TexConstant.Attr.LATEX, str);
566+
}
567+
if (node.childNodes[1]) {
568+
const sub = node.childNodes[1].attributes.get(TexConstant.Attr.LATEX);
569+
this.composeLatex(node, `_${sub}^`, 0, 2);
570+
} else {
571+
this.composeLatex(node, '^', 0, 2);
572+
}
573+
return;
574+
}
575+
if (node.attributes.get(TexConstant.Attr.LATEX) === '_' && str !== '_') {
576+
if (str === '}') {
577+
this.composeBraces(node.childNodes[1]);
578+
} else {
579+
node.childNodes[1].attributes.set(TexConstant.Attr.LATEX, str);
580+
}
581+
if (node.childNodes[2]) {
582+
const sub = node.childNodes[2].attributes.get(TexConstant.Attr.LATEX);
583+
this.composeLatex(node, `^${sub}_`, 0, 1);
584+
} else {
585+
this.composeLatex(node, '_', 0, 1);
586+
}
587+
return;
588+
}
589+
if (str === '}') {
590+
this.composeBraces(node);
591+
return;
592+
}
593+
node.attributes.set(TexConstant.Attr.LATEX, str);
594+
}
595+
596+
/**
597+
* Composing the LaTeX expression for sub or superscript elements.
598+
*
599+
* @param {MmlNode} node The Mml node.
600+
* @param {string} comp Intermediate string.
601+
* @param {number} pos1 Position of child for lefthand side of string.
602+
* @param {number} pos2 Position of child for righthand side of string.
603+
*/
604+
private composeLatex(
605+
node: MmlNode, comp: string, pos1: number, pos2: number) {
606+
const expr = node.childNodes[pos1].attributes.get(TexConstant.Attr.LATEX) + comp +
607+
node.childNodes[pos2].attributes.get(TexConstant.Attr.LATEX);
608+
node.attributes.set(TexConstant.Attr.LATEX, expr);
609+
}
610+
611+
/**
612+
* Adds the LaTeX content for this node as a braced expression.
613+
*
614+
* @param {MmlNode} atom The current Mml node.
615+
*/
616+
private composeBraces(atom: MmlNode) {
617+
if (!atom) return;
618+
let str = this.composeBracedContent(atom);
619+
atom.attributes.set(TexConstant.Attr.LATEX, `{${str}}`);
620+
}
621+
622+
/**
623+
* Composes the content of a braced expression.
624+
*
625+
* @param {MmlNode} atom The current Mml node.
626+
*/
627+
private composeBracedContent(atom: MmlNode) {
628+
let children = atom.childNodes[0]?.childNodes;
629+
let expr = '';
630+
for (const child of children) {
631+
let att = (child.attributes?.get(TexConstant.Attr.LATEX) || '') as string;
632+
if (!att) continue;
633+
expr += (expr && expr.match(/[a-zA-Z]$/) && att.match(/^[a-zA-Z]/)) ? ' ' + att : att;
634+
}
635+
return expr;
636+
}
514637

515638
}

ts/input/tex/base/BaseItems.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {Property, PropertyList} from '../../../core/Tree/Node.js';
3737
import StackItemFactory from '../StackItemFactory.js';
3838
import {CheckType, BaseItem, StackItem, EnvList} from '../StackItem.js';
3939
import {TRBL} from '../../../util/Styles.js';
40+
import { TexConstant } from '../TexConstants.js';
4041

4142
/**
4243
* Initial item on the stack. It's pushed when parsing begins.
@@ -145,6 +146,7 @@ export class OpenItem extends BaseItem {
145146
// @test PrimeSup
146147
let mml = this.toMml();
147148
const node = this.create('node', 'TeXAtom', [mml]);
149+
addLatexItem(node, item);
148150
return [[this.factory.create('mml', node)], true];
149151
}
150152
return super.checkItem(item);
@@ -325,6 +327,7 @@ export class OverItem extends BaseItem {
325327
this.getProperty('ldelim') as string, mml,
326328
this.getProperty('rdelim') as string);
327329
}
330+
mml.attributes.set(TexConstant.Attr.LATEXITEM, this.getProperty('name') as string);
328331
return [[this.factory.create('mml', mml), item], true];
329332
}
330333
return super.checkItem(item);
@@ -390,10 +393,18 @@ export class LeftItem extends BaseItem {
390393
//
391394
// Create the fenced structure as an mrow
392395
//
393-
return [[this.factory.create('mml', ParseUtil.fenced(
396+
let fenced = ParseUtil.fenced(
394397
this.factory.configuration,
395398
this.getProperty('delim') as string, this.toMml(),
396-
item.getProperty('delim') as string, '', item.getProperty('color') as string))], true];
399+
item.getProperty('delim') as string, '', item.getProperty('color') as string);
400+
let left = fenced.childNodes[0];
401+
let right = fenced.childNodes[fenced.childNodes.length - 1];
402+
let mrow = this.factory.create('mml', fenced);
403+
addLatexItem(left, this, '\\left');
404+
addLatexItem(right, item, '\\right');
405+
mrow.Peek()[0].attributes.set(
406+
TexConstant.Attr.LATEXITEM, '\\left' + item.startStr.slice(this.startI, item.stopI));
407+
return [[mrow], true];
397408
}
398409
if (item.isKind('middle')) {
399410
//
@@ -403,9 +414,11 @@ export class LeftItem extends BaseItem {
403414
if (item.getProperty('color')) {
404415
def.mathcolor = item.getProperty('color');
405416
}
417+
let middle = this.create('token', 'mo', def, item.getProperty('delim'));
418+
addLatexItem(middle, item, '\\middle');
406419
this.Push(
407420
this.create('node', 'TeXAtom', [], {texClass: TEXCLASS.CLOSE}),
408-
this.create('token', 'mo', def, item.getProperty('delim')),
421+
middle,
409422
this.create('node', 'TeXAtom', [], {texClass: TEXCLASS.OPEN})
410423
);
411424
this.env = {}; // Since \middle closes the group, clear the environment
@@ -563,7 +576,9 @@ export class BeginItem extends BaseItem {
563576
}
564577
if (!this.getProperty('end')) {
565578
// @test Hfill
566-
return [[this.factory.create('mml', this.toMml())], true];
579+
const node = this.toMml();
580+
addLatexItem(node, item);
581+
return [[this.factory.create('mml', node)], true];
567582
}
568583
return BaseItem.fail; // TODO: This case could probably go!
569584
}
@@ -1290,6 +1305,7 @@ export class ArrayItem extends BaseItem {
12901305
NodeUtil.setAttribute(node, 'data-break-align', this.breakAlign.row);
12911306
this.breakAlign.row = '';
12921307
}
1308+
addLatexItem(node, this);
12931309
this.table.push(node);
12941310
this.row = [];
12951311
this.atEnd = false;
@@ -1571,3 +1587,18 @@ export class EquationItem extends BaseItem {
15711587
}
15721588

15731589
}
1590+
1591+
/**
1592+
* Adds auxiliary attributes for LaTeX output to node.
1593+
*
1594+
* @param {MmlNode} node The current node.
1595+
* @param {StackItem} item The stack item.
1596+
* @param {string=} prefix A prefix for the LaTeX command.
1597+
*/
1598+
function addLatexItem(node: MmlNode, item: StackItem, prefix: string = '') {
1599+
let str = item.startStr.slice(item.startI, item.stopI);
1600+
if (str) {
1601+
node.attributes.set(TexConstant.Attr.LATEXITEM, prefix ? prefix + str : str);
1602+
node.attributes.set(TexConstant.Attr.LATEX, prefix ? prefix + str : str);
1603+
}
1604+
}

0 commit comments

Comments
 (0)