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

Avoid underestimation of min/max stretchy horizontal operators #123

Open
fred-wang opened this issue Sep 4, 2019 · 7 comments
Open

Avoid underestimation of min/max stretchy horizontal operators #123

fred-wang opened this issue Sep 4, 2019 · 7 comments

Comments

@fred-wang
Copy link
Contributor

cc @bfgeek

approximate-stretching

As you can see on the screenshot, with how math fonts are designed:

  • The width of a vertical stretchy operator (e.g. a left parenthesis) is not constant.
  • It is not always possible to perfectly stretch to a given target size. In particular for horizontal stretching, the operator (e.g. horizontal over brace) might end up a bit larger than its siblings.

One can use scale transform to distort the glyph, but that would go against the will of the font designer and might give poor math rendering.

This has implications in min/max size computation (https://mathml-refresh.github.io/mathml-core/#layout-of-operators):

  • We cannot determine the preferred width of a vertical stretchy operator since vertical metrics are unknown during min/max size computation. MathML Core addresses that by taking the maximum of all widths. This may be an overestimation of the final width.

  • We cannot even determine the preferred width of a horizontal stretchy operators since we cannot use layout constraint during min/max size computation. MathML Core addresses that by taking the width of the unstretched operator. This may be an underestimation of the final width.

and has consequences for the rest of the page layout:

  • When the min/max size is overestimated, linebreaking might not be optimal. For example, an inline MathML formula in a paragraph of text can be moved to the next line even if there is enough room for it on the current line. Or a table cell containing a MathML formula might be larger than what is needed to contain the formula.

  • When the min/max size is underestimated, the situation is the opposite: the inline formula in a paragraph may overflow instead of forcing a line break and moving to the next line ; a MathML formula inside a table may overflow its containing cell.

I think overestimation is an acceptable approximation, at worst it adds some extra white space. Underestimation can cause more annoying overflow bugs, so should be preferably avoided. Hence I'd like to propose the following change:
( https://mathml-refresh.github.io/mathml-core/#underscripts-and-overscripts-munder-mover-munderover )

  • When calculating the inline size of munder/mover/munderover, the unstretched size of horizontal stretchy embellished operators is used.

(Note that these operators remain laid out with the stretched size and the horizontal position remains as defined in the spec)

This means that the width matches the min/max calculation and we get rid of the approximation error. Or said otherwise, we just have some possible overflow on each side of each munderover rather than an error accumulated for the min/max size of the math root.

Note that with MathML Core, one may already have to layout stretchy horizontal operators twice (when all children are of this kind) and per discussion in w3c/mathml#124, Neil thinks it is important to keep this anyway. (I would still prefer to avoid this second layout for vertical operators and keep the current description in MathML Core)

@NSoiffer
Copy link
Contributor

I'm not a big fan of estimation, but if it is needed, I agree over estimation is better than under estimation in terms of it's consequences... except that in most cases, probably the smallest size character will be used (e.g., parens tend not to stretch unless there fractions or other tall notations in the expr). Hence, over estimation will almost always be wrong, likely significantly wrong.

Is it possible to be more sophisticated in the estimate and take into account whether there are tall elements in the mrow (mfrac and mtable) in doing the estimate? Other options for a heuristic are to check for big operators, munder/over's that are nested or have a big operator at their core, nested roots, and likely others. At some point though, the time/complexity it takes to develop heuristics to improve the estimate leads one to say that one shouldn't do the estimate and instead should just do two passes so the widths are properly calculated.

Bottom line: at least a few heuristics should be used for the estimate because using the max size is the worst possible choice in the vast majority of cases.

@fred-wang
Copy link
Contributor Author

@NSoiffer Let me highlight again these two points from my initial description:

vertical metrics are unknown during min/max size computation

we cannot use layout constraint during min/max size computation

Also, when we asked about this to Google last July, the last point is actually stronger:

The intrinsicSizes() shouldn't depend on any information from the parent layout.

AFAIK, these are general invariants for CSS / LayoutNG / CSS Layout API / browser implementations.

I also understand it is possible to do "multi-pass layout" but that does not help here since the error happens during the preferred width computation not during layout.

@fred-wang
Copy link
Contributor Author

Some additional notes:

  • Just to be sure that there is no confusion, the legacy implementation in WebKit/Chrome had issue during layout which mean either very poor rendering OR violating browser invariants. The current discussion is only for preferred width calculation, the formulas are always laid out with correct spacing, the current MathML Core spec is compatible with how browser/CSS work and Google confirmed the design is correct for LayoutNG.
  • This is how it is implemented in Gecko and WebKit, off the top of head I don't think any bug has been reported for that.
  • Display equations are generally centered with extra space on the left/right so the preferred width is actually irrelevant here.
  • One can always use a post-layout JS script to wrap <math> tags in a display: inline-block; width: ${measured-math-tag-width}px; (with measured-math-tag-width obtained by getBoundingClientRect() for example) and force a reflow. Then the page will use the specified width as the preferred width, for all <math> elements and the error goes away.

@davidcarlisle
Copy link
Collaborator

@fred-wang ah actually I think I knew that when you originally commented but clearly I merged these issues in my head this morning, I deleted my comment above to avoid confusion.

So I'll repeat here what I said in the deleted comment, I'm happy to go with your suggestion at the start of this issue.

@fred-wang
Copy link
Contributor Author

I we want to #123 ; then I think preferred min should take into account minsize (% still resolved as multipled of preferred min for mo for unstretched size).

@fred-wang
Copy link
Contributor Author

fred-wang commented Nov 12, 2019

Consensus from 2019/11/11: "When calculating the inline size of munder/mover/munderover, the unstretched size of horizontal stretchy embellished operators is used."

There is a potential problem with minsize/maxsize but for now the core spec does not say anything about it: #64

@fred-wang
Copy link
Contributor Author

Consensus from 2019/11/11: "When calculating the inline size of munder/mover/munderover, the unstretched size of horizontal stretchy embellished operators is used."

I think we would need to be careful when a stretch size contrainst is passed to the element, as in that case we might prefer to use the passed constraint size for the base/element which may be significantly wider than the scripts e.g.

<math>
  <mover>
    <munder>
      <mo>_</mo>
      <mspace width="100px"/>
    </munder>
    <mspace width="200px"/>
    </mover>
</math>

I believe the proposal would still work though since the embellished ancestor will calculate the proper width.

However I'd postpone this change for a next version of core and keep the logic similar for inline/block stretching for now. We can see later how to refine it depending on the feedback during code upstreaming.

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

No branches or pull requests

3 participants