mathjax / MathJax-node

MathJax for Node
Apache License 2.0
615 stars 97 forks source link

Font size is reduced in page2html #204

Closed yan-zaretskiy closed 8 years ago

yan-zaretskiy commented 8 years ago

When applying the page2html, the font size of the resulting equations becomes about half of what regular MathJax generates. At the same time, page2svg does not mess the font size. What gives?

pkra commented 8 years ago

Thanks for the report. I'm afraid I can't reproduce this issue. Please add a sample.

dpvc commented 8 years ago

MathJax in the browser is able to determine the ex-height and em-size of the surrounding font and scale the MathJax output so that the lower-case letters in the math align properly with the lower-case letters of the surrounding font. On the server, however, the DOM implementation that MathJax-node uses does not provide the sizing information that MahtJax needs to do that. So it just uses the font at its unscaled size. Apparently for the page you are using, that is fairly small.

It might be possible for mathjax-node to use the value given in the --ex option to scale the font, but I think we would also need an --em parameter to get it right, which we don't currently have.

On the other hand, your could add CSS to enlarge all the math to the size you need. (Perhaps a --scale option would make that more convenient). In my experience, the scaling factor MathJAx applies usually is around 120%, so try adding

<style>
.mjx-chtml {font-size: 120% ! important}
</style>

to the <head> of your document and see if that produces a better result.

yan-zaretskiy commented 8 years ago

@dpvc Thanks for the explanation. Your suggestion regarding CSS is what I ended up with. However, does this logic explain why the SVG output is scaled properly? I was under the impression that different MathJax outputs would look the same except for the inherent limitations of the output format (like the jagged-looking SVG scaling).

dpvc commented 8 years ago

does this logic explain why the SVG output is scaled properly?

An SVG element acts like an image, and can be scaled so that its height is given in terms of ex's. Since we know the size of the ex internally, we can determine the size of the svg in terms of internal ex's and scaling the svg to that many external ex's will make the svg match the outer text without having to know the external ex size itself. Font scaling doesn't work that same way. But it has given me an idea I want to try out.

dpvc commented 8 years ago

Try using this CSS in the head:

<style>
.mjx-chtml {font-size: 2.26ex ! important}
.mjx-chtml .mjx-chtml {font-size: inherit ! important}
</style>

This should accomplish the same kind of font-relative scaling as the SVG does. I think this should produce the correct matching without having to know the actual ex size. Not sure why I didn't think of this before, but explaining how the SVG scaling worked made me think of it now.

@Peter, we should be able to use this to avoid having to measure anything but the container width in CommonHTML (and could also use it in the other output modes as well).

yan-zaretskiy commented 8 years ago

Many thanks!!

pkra commented 8 years ago

So I guess upstream is appropriate given https://github.com/mathjax/MathJax/issues/1438?

dpvc commented 8 years ago

Short-term, the page2html output could include the CSS listed above to improve the current situation. I'd want to check the results for undefined characters and mtextFontInherit, though. But long-term, yes, an upstream fix is better.

pkra commented 8 years ago

We decided against this upstream after all due to browser bugs. so I don't think we want to add the fix here either.

I'm closing this for now; we should update this if we change it upstream.

SuzanneSoy commented 7 years ago

Computing the size of 2.26ex in pixels with a small bit of JavaScript seems to work, and does not trigger the font-size bug on WebKit when zooming (at least not on chromium 57, which does have the zooming bug with font-size).

(function() { var outer = document.createElement('div'); outer.style.width = '0px'; outer.style.height = '0px'; outer.style.overflow = 'hidden'; var d = document.createElement('div'); d.style.width = '2260ex'; outer.appendChild(d); document.body.appendChild(outer); window.setTimeout(function() { var sz = d.clientWidth / 1000; document.body.removeChild(outer); if (sz > 3) { var st = document.createElement('style'); st.appendChild(document.createTextNode('html .mjx-chtml { font-size: '+sz+'px; } html .mjx-chtml .mjx-chtml { font-size: inherit; }')); document.head.appendChild(st); } }, 0)})();

In order to add this script at the beginning of the body, I added the following to my node.js code (where window was created using jsdom.env from the jsdom node package):

  // Code before this to load the HTML file into the `window` variable/argument using jsdom.env
  var patchFontSizeCode = "(function() { var outer = document.createElement('div'); outer.style.width = '0px'; outer.style.height = '0px'; outer.style.overflow = 'hidden'; var d = document.createElement('div'); d.style.width = '2260ex'; outer.appendChild(d); document.body.appendChild(outer); window.setTimeout(function() { var sz = d.clientWidth / 1000; document.body.removeChild(outer); if (sz > 3) { var st = document.createElement('style'); st.appendChild(document.createTextNode('html .mjx-chtml { font-size: '+sz+'px; } html .mjx-chtml .mjx-chtml { font-size: inherit; }')); document.head.appendChild(st); } }, 0)})();";
  var patchFontSize = window.document.createElement('script');
  patchFontSize.appendChild(window.document.createTextNode(patchFontSizeCode));
  patchFontSize.setAttribute('type', 'text/javascript');
  window.document.body.insertBefore(patchFontSize, window.document.body.childNodes[0] || null);
  // Code after this to call mjAPI.typeset and save the HTML file with jsdom.serializeDocument

To avoid any issues, I release the above snippet in the public domain (or under the CC0 license if public domain is not applicable). Feel free to copy it in your own web pages or in the code of mathjax-node.

dpvc commented 7 years ago

@jsmaniac, I don't think the approach you suggest is going to work. This is because jsdom does not actually perform layout, and so doesn't compute the positional values like clientWidth or offsetWidth. In my hand test, these are both undefined, but I also recall having seen them as 0 in the past.

In the browser, MathJax uses the type of measurements you are trying to make in order to determine the ex- and em-size, but in jsdom, that just doesn't work. That is why mathjax-node requires you to specify the ex-size with the --ex option. It just doesn't have a way of determining it outside of an actual DOM in a browser.

SuzanneSoy commented 7 years ago

@dpvc I'm not computing the size of clientWidth and such with jsdom. I'm merely injecting the following script (the first one in my previous post)

(function() { var d = document.createElement('div'); d.style.width = '2260ex'; document.body.appendChild(d); window.setTimeout(function() { var sz = d.clientWidth / 1000; document.body.removeChild(d); if (sz > 3) { var st = document.createElement('style'); st.appendChild(document.createTextNode('html .mjx-chtml { font-size: '+sz+'px; } html .mjx-chtml .mjx-chtml { font-size: inherit; }')); document.head.appendChild(st); } }, 0)})();

That script is executed by the browser, computes the size of ex in the body of the document, and inserts the following tag in the head:

html .mjx-chtml { font-size: COMPUTED_SIZE px; }
html .mjx-chtml .mjx-chtml { font-size: inherit; }

All this happens client-side, I'm only using jsdom to inject the script in the page.

dpvc commented 7 years ago

Sorry, didn't parse it out correctly. You are right. Just make sure that your jsdom instance does not process scripts.

Note, however, that using such a large width may cause the browser to show a scroll bar momentarily before you remove it (the setTimeout() will allow that). It might also adversely affect mobile device layout that sets the scale based on the content (we had some similar problems with MathJax's em- and ex-size detection originally).

You might try putting your measurement div inside another one that has height and width set to 0 with overflow: hidden so that it will not interfere with the page size while you are doing the measuring.

SuzanneSoy commented 7 years ago

@dpvc Thanks for the tip concerning the scrolling issue. I updated the script in my post above following your overflow: hidden suggestion.

Worth noting for future lurkers: if the body contains a mix of font sizes, my script will (incorrectly) scale all equations in the same way, based on the ex size for a span located directly inside the body. It should be relatively easy to adjust it to scale each equation independently, based on the ex size just before or just after that equation's span or div.