Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get Bounding Boxes of Nodes #190

Closed
jacobp100 opened this issue Jan 30, 2019 · 10 comments
Closed

Get Bounding Boxes of Nodes #190

jacobp100 opened this issue Jan 30, 2019 · 10 comments
Labels

Comments

@jacobp100
Copy link

jacobp100 commented Jan 30, 2019

I'm using this in using the node example here.

I realise that there are some ways to access the tree structure. E.g. math.root.walkTree.

If I have a math node, is there a way to get the bounding box that was used when typesetting? I need the x, y baseline, and width of the node

@dpvc
Copy link
Member

dpvc commented Jan 31, 2019

The output jax have a getBBox() method that can be used to obtain the bounding-box information for a math item that has been compiled (it does not have to be typeset). For example:

import {MathJax} from './mathjax3/mathjax.js';

import {TeX} from './mathjax3/input/tex.js';
import {SVG} from './mathjax3/output/svg.js';
import {RegisterHTMLHandler} from './mathjax3/handlers/html.js';
import {chooseAdaptor} from './mathjax3/adaptors/chooseAdaptor.js';

import {HTMLMathItem} from './mathjax3/handlers/html/HTMLMathItem.js';

const adaptor = chooseAdaptor();
RegisterHTMLHandler(adaptor);

const tex = new TeX();
const svg = new SVG();

const html = MathJax.document('', {InputJax: tex, OutputJax: svg});

const latex = '\sqrt{x^2+1}';
const display = true;

const math = new HTMLMathItem(latex, tex, display);
math.setMetrics(16, 8, 80*16, 100000, 1);
math.compile(html);
console.log(svg.getBBox(math, html));

will output the bounding box information. The w property is the width (in units of ems), the h property is the height above the baseline, and the d property is the depth below the baseline. So the image will be h + d high and w wide, with the baseline at d from the bottom of the image.

I'm not sure what you have in mind for x and y, since the math is not actually in a page, so has no position. If you mean to try to get the locations of subexpressions within the main expression, MathJax does not keep track of that, so that is not easy to obtain. It could be done within the browser, but would be much harder in node.

If you are trying to get the sizes of individual subexpressions, that could be done, but would require a little more work. mathJax does not currently have a subexpression API for this.

@dpvc dpvc added the question label Jan 31, 2019
@jacobp100
Copy link
Author

jacobp100 commented Jan 31, 2019

Thanks for your reply!

My use-case is a math editor, and I want to display a cursor over a rendered equation. I only need the x/y relative to the origin of the SVG.

I'm using \cssId{...}{atom} on all the 'editable' nodes, so I can keep track of them.

I had some crazy code to iterate the SVG and add the transform()s to work out the co-ordinates. It seemed to work, but seems flaky. Was just wondering if there's a nicer way!

I'm trying to get this to work in react-native (via react-native-svg), so can't use browser measurement functions

@dpvc
Copy link
Member

dpvc commented Jan 31, 2019

Have you considered putting your cursor into the expression itself? I have had some luck with that in the past. You could use something like

\def\cursor{\cssId{cursor}{\llap{|\!}}}

and then put \cursor in the expression where you want it to be. Retypeset the expression as the cursor is moved. Not so efficient, I know, but easier to get placed correctly. You could use CSS to style the color, and even make it flash on and off if you like.

@jacobp100
Copy link
Author

Hmm - adding the \llap seems to affect the layout. I'm also noticing that I can't attach a cssId to a left( or \right).

I'm going from my own ast to latex, and then mathjax is going from latex to its own format. I think it might be better to fork this repo to let me construct the mathjax ast myself. Then I'd be able to keep track of all elements and layout. Unless there's a way to do this without forking 😄

@dpvc
Copy link
Member

dpvc commented Feb 5, 2019

adding the \llap seems to affect the layout

It can. For example, if you have x^2 and naively try x^\cursor 2, that will move the 2 out of the superscript, so you would have to do x^{\cursor 2} instead. I'm not sure if that is the sort of thing you are talking about.

Also, you may need to set the TeX class of the cursor depending on its location in order to preserve the spacing. In TeX, the spacing between two items is determined by their TeX classes, so the presence of an ORD element (what an \llap{} generates) between the two items can possibly change the spacing. For instance, in x := y, the colon and equals are both REL elements, and there is no space put between adjacent REL elements, so x :\mathrel{\cursor}= y would preserve the spacing (but x :\cursor= y would not).

For some situations, like +x, adding any element in front of the + will cause it to be a binary operator (spacing on both sides) rather than a unary one (no spacing afterward). Using braces around the + will preserve the spacing in that case: \cursor{+}x.

Unfortunately, there is no easy way around these spacing issues.

I can't attach a cssId to a \left( or \right).

That's correct. The way that TeX handles these, there is no way to isolate them. But you can do it in MathML. If your editor is keeping its own abstract syntax tree internally (which I expect you do, as that is the best way to do this sort of thing), and translating that into LaTeX notation for output, you could instead translate to MathML for MathJax (and could have a LaTeX option for copying-and-pasting). The MathML construct for \left(...\right) would be <mrow><mo>)</mo>...<mo>)</mo></mrow>. You can add ids to the two <mo> elements independently.

In order to do this sort of thing in TeX, you would have to write a custom macro (implemented in javascript) that generates the corresponding MathML with ids. That can certainly be done.

@jacobp100
Copy link
Author

I'll definitely look at going to MathML. I've not really looked at it before, but it seems like that's your canonical representation in this codebase. I'll let you know how it goes!

@jacobp100
Copy link
Author

jacobp100 commented Feb 7, 2019

So one thing I'm looking at is adding to the SVG wrapper the property origin = { x: 0, y: 0 }. Then when you call .place, it updates this value with the new co-ordinates. So it looks like,

public place(x: number, y: number) {
  this.origin.x += x;
  this.origin.y += y;
  ...
}

This seems to mostly work! However, .place seems to also have an overload where you can pass in an arbitrary element. I've managed to remove most of these cases, but am struggling mtable and mpadded. I'm not sure if I can just move the child components here - it's something I will have to play around with.

I was just wondering what your thoughts on this approach were! Would this be something you'd be interested in getting a PR for?

For my use-case, I'm only interested in the path data, position, and scale of path elements. If I can get it working, I should be able to almost completely replace the adapter with a no-op, which would remove the parser and make my bundle smaller!

@dpvc
Copy link
Member

dpvc commented Feb 7, 2019

Are you looking only for an offset of a child from its parent (MathML) element? If so, then this should work for that; if you want an offset from the location of the outermost math element, then you would need to update all the child node positions as well.

I've managed to remove most of these cases, but am struggling mtable and mpadded

These are the only two that are probably relevant to your usage. The ones in svg/Wrapper.ts can be ignored, as they are about placing individual characters inside a larger element, so aren't going to be something you need to get access to (and the characters don't have Wrappers in any case). The one in menclose.ts is for an esoteric use of <menclose> to produce square roots, but you are unlikely to use that (and if you are still using TeX input, it is never produced). The one in mmultiscripts.ts might be important if you are using MathML, but it is never produced by the (current) TeX input, though it may be in the future.

So that leaves mpadded.ts and mtable.ts, as you say. The usage in mpadded is for <mpadded> elements that use lspace or voffset attributes. TeX input does use this, so you do have to handle that. The child of the mpadded node will be an inferred mrow node, and you should adjust each of its children to add the x and y values to its origin.

The mtable wrapper has three uses of place() with an element being passed. Two of them are only in the case where the table includes <mlabeledtr> rows; these are generated from TeX that includes either automatic equation numbers, or those with \tag{} macros. So if you are not using those, then you can ignore those two places. If you do want to handle equations with tags or equation numbers, then the one in topTable() should apply to the mtable itself, I think (it is undoing an offset from when the table is added to the equation), while the one in subTable() is for placing the labels within the table's container. That would require adjusting the positions of all the initial <mtd> elements within <mlabeledtr> elements in the table. I think you would just have to scan through the table rows for those by hand.

Finally, the other usage is in handlePWidth(), and that should apply to the table itself, I think. I'm not sure which branch you are working on, but these descriptions of the mtable changes require a branch that has nested-tables merged into it.

Would this be something you'd be interested in getting a PR for?

Probably not. One of the places where we looked for speed improvements was the bounding box computations, since in v2 they can contribute about 10% of the output time. Maintaining any extra values like this that are only used by a tiny fraction of the use-cases is not something I would want to include in the core. It would be possible to make an extension that handles these computations (rather than editing the core code directly), and that would be the recommended path for making something like this available, I would think.

For my use-case, I'm only interested in the path data, position, and scale of path elements.

Well, since the individual characters don't have wrappers, I'm not sure how this does it for you. While most token elements will only contain one character, that is not always the case. In particular, <mtext> and <mn> elements are likely to have several.

f I can get it working, I should be able to almost completely replace the adapter with a no-op

I'm not sure I understand what you are saying. MathJax's DOMadaptor plays a central role in the construction of the SVG (and CommonHTML) output, so I don't see how you can get away without it. Perhaps I misunderstand what you are saying, here.

@jacobp100
Copy link
Author

I've got something working enough. I'll close this, as there's not much more MathJax can do. Thanks for your help though! :D

@dpvc
Copy link
Member

dpvc commented Feb 18, 2019

You're welcome, and good luck with your project!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants