Slight changes to MathJax source

74 views
Skip to first unread message

Raffaele

unread,
Oct 21, 2011, 6:09:34 PM10/21/11
to MathJax Development
Hello everyone,

I'm going to write a little equation-authoring web-app. A very
preliminary version is at http://levomano.altervista.org.
Mathjax is used to render the tex code entered by the user while he is
typing. To make navigating through code of complex equation easier, I
implemented "inverse search" function. If you click on a token into
the rendered formula, the caret of the textarea holding the tex source
jumps to the point where the token is defined.
That functionality is obtained by means of a MathJax extension and of
slight changes to the MathJax source files: jax/input/TeX/jax.js and
jax/output/HTML-CSS/jax.js.

jax/input/TeX/jax.js

I added two properties to the PARSE object: "parent" and "defStart".
The first one holds a reference to the PARSE object (if there is one)
that created (via ParseArg or ParseUpTo methods) the actual PARSE
object.
The second one holds a reference to the starting point of the
substring being processed at the moment the MmlToken method is called.
My extension overrides the standard implementation of MmlToken and
uses "parent" and "defStart" to calculate the absolute position of the
token definiton in the tex source code.

jax/output/HTML-CSS/jax.js

I added a new method, "Click", to the objects: MML.mi, MML.mn and
MML.mo. In the base implementation, it does nothing. In my extension
it is used to append to the token span an handler for the click event.

I wonder if those changes (or something equivalent) can enter the
mainline MathJax source.

Thanks a lot for your great work... and for the patient in reading
me... ;)

Raffaele

Output of diff between my version and original version of jax/input/
TeX/jax.js

917,919c917,918
< Init: function (string,env, parent) {
< this.parent = parent;
< this.string = string; this.i = 0; this.defStart = 0;
this.macroCount = 0;
---
> Init: function (string,env) {
> this.string = string; this.i = 0; this.macroCount = 0;
928d926
< this.defStart = this.i;
1519d1516
< this.defStart = this.i;
1529d1525
< this.defStart = j;
1536,1537c1532
< }
< this.defStart = this.i;
---
> }
1620,1621c1615,1616
< ParseArg: function (name) {return
TEX.Parse(this.GetArgument(name),this.stack.env,this).mml()},
< ParseUpTo: function (name,token) {return
TEX.Parse(this.GetUpTo(name,token),this.stack.env,this).mml()},
---
> ParseArg: function (name) {return TEX.Parse(this.GetArgument(name),this.stack.env).mml()},
> ParseUpTo: function (name,token) {return TEX.Parse(this.GetUpTo(name,token),this.stack.env).mml()},

Output of diff between my version and original version of jax/output/
HTML-CSS/jax.js

1472d1471
< this.Click(span);
1474,1476c1473
< },
<
< Click: function (span) {return span;}
---
> }
1489d1485
< this.Click(span);
1491,1493c1487
< },
<
< Click: function (span) {return span;}
---
> }
1535d1528
< this.Click(span);
1575d1567
< this.Click(span);
1577,1579c1569
< },
<
< Click: function (span) {return span;}
---
> }

Davide P. Cervone

unread,
Oct 24, 2011, 7:27:01 PM10/24/11
to mathj...@googlegroups.com
There is a problem with your parent/defStart approach, and that is
that MathJax can replace the string being processed by a new string
and restart the index at 0. This occurs, for example, when macros are
processed (the head of the string is removed and the macro name and
arguments are replaced by the macro body with the arguments
substituted). There are also other situations where the string can be
replaced. This means that the defStart values are not directly
related to the original TeX string. Indeed, with macro substitutions,
many of the elements in the final MathML may not directly correspond
to ANY substring of the original source (since they are part of a
macro replacement).

As for the Click macro, I understand the desire for it, but have not
yet worked out the details of how to deal with event handling in the
HTML-CSS output. Whatever we do needs to be something that is (or can
be) mirrored in ALL the output jax, since the user can change from one
to another via the MathJax contextual menu. One of the things that
concerns me here is that the bounding boxes of the spans for the
various elements do not always correspond to the glyphs they contain
(e.g., many are zero height in order to make the placement of their
contents work properly, and others have height that is the line height
even when their contents are much larger). This may not be the case
for mo, mi, and mn, but the event handling that I have in mind needs
to be usable for other elements (like mfrac) as well, and so would
have to deal with those issues. The current spans are not well suited
to that.

So at this point, I don't think these changes will be added to the
core. If course, you can override the methods that require these
changes in your own extension (as I'm sure you are doing now), though
that is certainly more cumbersome to maintain.

Best of luck with your application. Please keep us informed of your
progress.

Davide

Raffaele

unread,
Oct 27, 2011, 7:25:38 AM10/27/11
to MathJax Development
Hi Davide,
to address the first of the issues you reported, I propose the
following strategy:

1. Instead of changing the value of the "string" property of a "Parse"
object,
create a new "Parse" object with the new value as "string".
For example, within the "macro" function source, replace the
following rows:

this.string = this.AddArgs(macro,this.string.slice(this.i));
this.i = 0;

with something like:

this.Push(TEX.Parse(macro).mml());

2. Add to the "Parse" object two new properties: "parent" and
"translate".
"parent" holds a reference to the "Parse" object that created the
actual one.
"translate" is a function that relates a point in the child "Parse"
object string
to a point in the parent "Parse" object string (it returns -1 for
child points not
related to any parent points, though I doubt such points exist).
When the child string is simply a substring of the parent string,
as in the
case of "Parse" objects created by the "parseArg" method, the
"translate"
function will be something like:

translate = function(x) {return x + p;}

where p is the position of the child substring within the parent
string.
For the "Parse" object created elaborating TeX macros,
"translate(x)" will
return:

p_m if x is a point coming from the macro definition
p_x if x is a point coming from one of the macro arguments

where p_m and p_x are respectively the macro position and the x
position
within the parent string.
Let's see a concrete example.
Parsing the following TeX code:

\Lambda + \mathcal {C + D}

a child "Parse" object will be created with string property set to
"{\cal C + D}",
the "translate" function passed to that object will be described by
the table:

in out
0 10
1 10
2 10
3 10
4 10
5 10
6 20
7 21
8 22
9 23
10 24
11 10

I update "http:levomano.altervista.org" so you can test my ideas in
the "real life".
I don't know very well MathJax source, so I couldn't identify the
places where
"string" substitution happens apart from the "macro" method. I you
show me them,
I will apply the above described changes to those as well.

I wait for your comments...
Thanks...

Raffaele


diff between my jax,js and the original one

917,920c917
< Init: function (string,env,parent,translate) {
< this.parent = parent;
< this.translate = translate;
< this.defStart = 0;
---
> Init: function (string,env) {
930d926
< this.defStart = this.i;
1357,1358d1352
< var macroStartPos = this.defStart;
< var f;
1361,1377c1355
< var argsPos = [];
< for (var i = 0; i < argcount; i++) {
< args.push(this.GetArgument(name));
< argsPos.push(this.defStart);
< }
< var a = [];
< for (var i = 0; i < macro.length; i++) {
< if (macro.charAt(i) != '#')
< a.push(macroStartPos);
< else {
< i++;
< var n = parseInt(macro.charAt(i)) - 1;
< for (var j = 0; j < args[n].length; j++)
< a.push(argsPos[n] + j)
< }
< }
< f = function(x) {return a[x]};
---
> for (var i = 0; i < argcount; i++) {args.push(this.GetArgument(name))}
1379,1380d1356
< } else {
< f = function(x) {return macroStartPos};
1382c1358,1359
< this.Push(TEX.Parse(macro, null, this, f).mml());
---
> this.string = this.AddArgs(macro,this.string.slice(this.i));
> this.i = 0;
1540d1516
< this.defStart = this.i;
1550d1525
< this.defStart = j;
1557,1558c1532
< }
< this.defStart = this.i;
---
> }
1641,1647c1615
< ParseArg: function (name) {
< var arg = this.GetArgument(name);
< var argPos = this.defStart;
< var f = function(x) {return x + argPos};
< return TEX.Parse(arg,this.stack.env,this,f).mml();
< },
<
---
> ParseArg: function (name) {return TEX.Parse(this.GetArgument(name),this.stack.env).mml()},


> > preliminary version is athttp://levomano.altervista.org.

Davide P. Cervone

unread,
Nov 2, 2011, 4:26:29 PM11/2/11
to mathj...@googlegroups.com
Raffaele:

This suggestion also has problems. For one thing, it doesn't properly
remove arguments from the original string nor insert them into the
macro, but that could be fixed. The real problem is that the string
handled by TEX.Parse() must be a complete TeX expression that can be
processed in isolation, but TeX macros can contain partial expressions
that interact with the previous and following content. For example,
you can do

\def\bbb{\begin{array}}
\def\eee{\end{array}}
\bbb{cc}
a&b\\
c&d
\eee

which is equivalent to

\begin{array}{cc}
a&b\\
c&d
\end{array}

Your implementation would not handle this properly (try it out in your
test page and see). Similarly, you could do

\def\R{{\bf R}^}
f\colon\R2\to\R3

to get a function mapping R^2 to R^3. Again, your approach will fail
to handle this. Macro substitutions are not like function calls
(which create their own local environments); they are just string
replacements (which is why they are implemented the way they are). I
don't think you are going to manage this through separate Parse
instances.

While I understand your desire to have this reverse mapping, it is
probably not something that is going to be included in the core
MathJax functionality.

Davide

Reply all
Reply to author
Forward
0 new messages