Skip to content

Support for \raisebox#685

Merged
k4b7 merged 9 commits intoKaTeX:masterfrom
edemaine:raisebox
Sep 5, 2017
Merged

Support for \raisebox#685
k4b7 merged 9 commits intoKaTeX:masterfrom
edemaine:raisebox

Conversation

@edemaine
Copy link
Copy Markdown
Member

@edemaine edemaine commented Apr 27, 2017

I started working on \raisebox support which will help for #657 and #224. Currently \raisebox{...ex}{...text...} "works", except that the bounding box of the resulting typeset text isn't set correctly, so raised/lowered objects cut into the lines before/after. I guess I'm still not fully up to speed on how HTML generation works here -- based on other code in buildHTML.js, I tried manually updating .height or .depth according to whether the box moved up or down, but that didn't work. If anyone has a chance to look at this, I'd appreciate help fixing it.

Here's a sample render with the test server:

raisebox

Here's the same example rendered in TeX, so seems good except for the bounding box issue:

raisebox_tex

Also fixed a typo where parsed sizes are accidentally labeled as "color" nodes instead of "size" nodes.

k4b7
k4b7 previously requested changes Apr 29, 2017
src/buildHTML.js Outdated
const span = groupTypes.text(group, options);
if (group.value.dy) {
const dy = group.value.dy.value;
span.style.top = "" + (-dy.number) + dy.unit;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should handle mu units right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point -- indeed, it currently does not, but would be easy to tweak to. (A bigger issue is that calculateSize only supports mu and em.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like calculateSize supports ex.

const calculateSize = function(sizeValue, style) {
    let x = sizeValue.number;
    if (sizeValue.unit === "ex") {
        x *= style.metrics.emPerEx;
    } else if (sizeValue.unit === "mu") {
        x /= 18;
    }
    return x;
};

src/buildHTML.js Outdated
span.height += calculateSize(dy.number, options.style);
} else {
span.depth -= calculateSize(dy.number, options.style);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't height and depth both be updated regardless of the sign of dy.number?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that the height increases if the box is moved up, and the depth increases if the box is moved down. At least, my mental model is that height is the extent above the baseline (y-max) while depth is the extent below the baseline (y-min) -- please correct me if I'm wrong. However, this code doesn't seem to be working, and I'm not really sure how height and depth end up getting used (or not).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least, my mental model is that height is the extent above the baseline (y-max) while depth is the extent below the baseline (y-min) -- please correct me if I'm wrong.

That's right, but if a glyph is shifted vertically, the the extent below the baseline will be different.

What happens if you put the test expression inside a \sqrt or a \frac? It may be that just our wrapper container isn't doing the right thing with the height and depth you're setting on the span.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside a \frac or \sqrt, the bounding box looks good:
raisebox

This screenshot, by the way, was done with code that was slightly tweaked:

groupTypes.transform = function(group, options) {
    const span = groupTypes.text(group, options);
    const dy = calculateSize(group.value.dy.value, options.style);
    if (dy > 0) {
        span.height += dy;
    } else if (dy < 0) {
        span.depth -= dy;
    }
    span.style.top = -dy + "em";
    span.style.position = "relative";
    return span;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ronkok did your tweaks have any effect on the top level bounding box?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you ask me, yes they did. Odd. I was only trying to clean it up, not change how it worked. Here's a screenshot from the original code:
raisebox2
Now that I need to take this more seriously, here’s a version that restores the outer if statement.

groupTypes.transform = function(group, options) {
    const span = groupTypes.text(group, options);
    if (group.value.dy) {
        const dy = calculateSize(group.value.dy.value, options.style);
        if (dy > 0) {
            span.height += dy;
        } else if (dy < 0) {
            span.depth -= dy;
        }
        span.style.top = -dy + "em";
        span.style.position = "relative";
    }
    return span;
};

That code gives me the good results.

@edemaine
Copy link
Copy Markdown
Member Author

Awesome, thanks @kevinbarabash and @ronkok for finding the solution here! Looks like it was close indeed, and switching to calculateSize at the top level was the missing link.

Here is what my original example A\raisebox{-0.5em}{B}\raisebox{0.5em}{C}D looks like with the new code (now pushed):

abcd

It's better, though the top margin is still a lot smaller (zero or even slightly less) than a straight \text{C}:

c

@ronkok
Copy link
Copy Markdown
Collaborator

ronkok commented May 15, 2017

You mentioned a smaller margin that a straight \text{C}. I wonder if that is why buildCommon.makeVList spends the effort to call makeFontSizer.

@edemaine
Copy link
Copy Markdown
Member Author

Hey, I got it! I was looking at the definition of the bottom strut in buildHTML.js, and it uses a vertical-align to put the bottom strut below the top strut. I didn't know you could put numbers into vertical-align. So I tried that instead of a top style (see this trivial diff), and it now has exactly the right bounding box!

abcd

@k4b7
Copy link
Copy Markdown
Member

k4b7 commented May 16, 2017

I didn't know you could put numbers into vertical-align

Neither did I. Cool!

@k4b7
Copy link
Copy Markdown
Member

k4b7 commented May 16, 2017

@edemaine now that everything's working correctly, can you add a screenshot test?

@edemaine
Copy link
Copy Markdown
Member Author

In adding a screenshot test similar to @ronkok's, I realize that we're not quite there yet: vertical-align behaves well at the top level (and in \fracs) but doesn't seem to interact properly with \sqrt...

abc

It seems that the positive vertical-align in the b is causing the ab to move down... weird... Hmm, I think this is related to makeFontSizer that @ronkok mentioned, as the contents of the sqrt are put in a vlist (although so are the \fracs...).

@ronkok
Copy link
Copy Markdown
Collaborator

ronkok commented May 16, 2017

I don't think the problem is yours. If I understand matters correctly, KaTeX does not change the radical height in a continuous manner. It's a step function. You happened to land on a bad height.

@edemaine
Copy link
Copy Markdown
Member Author

@ronkok Here's what it looks like, switching back to top setting:

abc

The only difference is the vertical shift in the a\raisebox{0.5em}{b}.

I believe the issue is Movement Of the Line Box’s Baseline (quirks of vertical-align). Still grokking...

@edemaine
Copy link
Copy Markdown
Member Author

Upon further investigation, I think the top version (essentially @ronkok's version) was actually correct. There is a larger space at the top of \text{a} simply because of the overall line height. This margin disappears even in a simple example like a^{a^{a^a}}:

aaaa

So I've reverted to that version, and added a screenshot test. I think we're back to good-to-go.

@k4b7
Copy link
Copy Markdown
Member

k4b7 commented May 16, 2017

@edemaine you may want to use the texcmp docker to compare the output against LaTeX's output.

@edemaine
Copy link
Copy Markdown
Member Author

One more question: I currently call the parse node "transform", with the thought that it could be used for rotatebox and such in the future. But I wonder if just calling it "raisebox" and keeping it simple would be better. Let me know your opinion.

@k4b7
Copy link
Copy Markdown
Member

k4b7 commented May 16, 2017

Good question. I think having separate raisebox and rotatebox methods probably makes since we'll need to do something pretty different to implement rotatebox.

@edemaine
Copy link
Copy Markdown
Member Author

edemaine commented May 16, 2017

@kevinbarabash Good call on using texcmp. I see now that LaTeX resets font size to full size when switching to text mode via \raisebox (which \text probably is careful to avoid), and I need to do the same:

raisebox

I've changed the parser node name from "transform" to "raisebox".

@edemaine edemaine changed the title Rough support for \raisebox Support for \raisebox May 16, 2017
@edemaine
Copy link
Copy Markdown
Member Author

edemaine commented May 16, 2017

In principle I fixed the switch-to-normalsize issue, but I've run into a bug: #709

@k4b7
Copy link
Copy Markdown
Member

k4b7 commented Jun 28, 2017

@edemaine I've merged the fix for #709. Do you know if there will be additional work after rebasing?

@edemaine
Copy link
Copy Markdown
Member Author

edemaine commented Jun 30, 2017

I rebased. The switch to \normalsize is working now, but the bounding box computation seems completely broken (unphased by the \raisebox). In this texcmp, the red (KaTeX) raised b's intersect whatever is above them (\frac line or \sqrt line).

raisebox

I get the sense that this is the result of some other changes... and presumably #746?

@edemaine
Copy link
Copy Markdown
Member Author

edemaine commented Sep 4, 2017

I got another chance to look this over and revise substantially. I believe this is now working almost perfectly. I use makeVList to do the vertical shift, so it should be well-behaved (and it appears at least reasonably so). Internal to buildHTML, I use sizing and text nodes to switch to the equivalent of \textrm{\normalsize...}. texcmp is almost perfect (slight horizontal spacing difference?):

raisebox

I also slightly cleaned up the parse tree: text nodes were using a style attribute to specify a font, but styles mean something else in KaTeX; switched the attribute to font.

Probably best to look at this as a complete diff but if you prefer you can look at just the last commit for the changes relative to previous versions.

Copy link
Copy Markdown
Member

@k4b7 k4b7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I'm going to rename textFunctionStyles to fontFunctionStyles before merging.


groupTypes.text = function(group, options) {
const newOptions = options.withFont(group.value.style);
const newOptions = options.withFont(group.value.font);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling withFont with a font makes more sense.


// Transforms (translation/rotation) don't seem to have a representation
// in MathML, so just treat them like \text{...}
groupTypes.raisebox = groupTypes.text;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

src/functions.js Outdated
type: "text",
body: ordargument(body),
style: textFunctionStyles[context.funcName],
font: textFunctionStyles[context.funcName],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're calling these fonts now, should probably rename this to textFunctionFonts.

@k4b7
Copy link
Copy Markdown
Member

k4b7 commented Sep 5, 2017

@edemaine thanks for picking this one up again and finishing it off. The results look great!

@k4b7 k4b7 merged commit ca224ed into KaTeX:master Sep 5, 2017
@edemaine
Copy link
Copy Markdown
Member Author

edemaine commented Sep 5, 2017

Thanks @kevinbarabash for all the reviews today!!

@ronkok
Copy link
Copy Markdown
Collaborator

ronkok commented Sep 11, 2017

I think that one can get effective presentation MathML for \raisebox with code something like:

const node = new mathMLTree.MathNode(
        "mpadded", [buildGroup(group.value.body, options)]);
node.setAttribute("voffset", group.value.dy.value);

I have not tested that code, but I have put the HTML below into Firefox

<math xmlns="http://www.w3.org/198/Math/MathML">
    <mi>a</mi><mpadded voffset="0.5em">b</mpadded><mi>c</mi>
</math>

... and it comes out looking like this:
raisebox

This comment is coming very late, I know. Sorry.

@edemaine edemaine mentioned this pull request Sep 11, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants