Skip to content

MathML Polyfill Task Force Guidelines

NSoiffer edited this page Nov 25, 2020 · 24 revisions

Note: this is meant to be an evolving wiki page that starts with examples, moves on to possible strategies for the polyfills, and ends up with strategy/guidelines for how the polyfills should be written and work together. The focus is on polyfill writers, with a separate page eventually for those who (might) want to use the MathML polyfills.

Note: some discussion on polyfills are in this issue.

Work to do

  • Need to figure out what to do for automated testing of polyfills

Lessons Learned from Polyfills

  • many polyfills are rewrites of the MathML -- if they target only core MathML, they only need to be run once.

  • some polyfills require measuring. If these change the size (some don't), they may require other polyfills that measure need to be rerun

  • there is not a way to do these without changing the DOM

  • there is not a good way to find the height/depth of a MathML element

  • using Shadow DOM can be helpful for presenting a clean light DOM, but there are issues:

    • cloning nodes into the shadow DOM -- shadow roots aren't cloned, so display has problems
    • CSS used to display elements is blocked -- it needs to be copied into the shadow DOM.

    These are solvable, but require workarounds and knowledge to use special functions when creating shadow roots and cloning nodes.

MathML polyfills that transform MathML to MathML

Items marked with "✓" have been implemented

  • ✓[small] mfenced -- rewrite mfenced to use the equivalent MathML elements via mrow and mo.
  • ✓[small]mfrac@bevelled -- #29. This looks like it can rewrite to mrow with mpadded around the numerator to shift it up a fixed amount. "/" (maybe stretchy?) would be used for the division sign.
  • ✓[small]mlabeledtr -- #72. At a minimum, this needs to turn into a tr with the first element (the label) dropped. But dropping the label is bad. The initial comment in the issue suggests that CSS could be used to place it properly outside the table, but I don't see how to do that and keep the baseline's aligned. The alternative is to expand the table to add an extra column at the front or end (maybe playing some games to get it to be all the way on the left/right of the page). That column is empty except for the label(s) -- care needs to be taken to make sure only one extra column is created even if there are multiple mlabeledtrs.
  • [small] replace a character -- #146. This is about changing "-" to U+2212. Given that this would break a ton of existing MathML, it seems like a bad idea (and also trivial to do in core). It is symptomatic of a bigger issue in that there are a lot of equivalent characters. E.g, what character should be used to draw an overbar or underbar? There are several possibilities... This would be a polyfill that just changes content. Probably care needs to be taken as to when to make specific changes (e.g., the underbar/overbar change only happens in the mo parts of limits in munder/mover/munderover. Same for primes, etc. The full spec lists some equivalents.
  • ✓[small] mo@accent -- #210. The accent property is given in the operator dictionary and can be specified directly on mo. If core doesn't pick this out of the operator dictionary, virtually all existing MathML that uses this (which is probably a majority of munder/mover usages) will break. To write a polyfill, we would need to look at every mo that is part of a munder/mover/munderover element and see if it has the accent attr, and if not, if its content is a single character that has the accent property in the operator dictionary. If so, then set the accent/accentunder on the parent munder/mover/munderover element.
  • ✓[small] mglpyh -- #25 should be a relatively simple transform to img which is legal HTML inside token elements. Some attrs such as valign will need to make use of CSS styling (in this case, vertical-align).
  • [medium] mstyle -- #1 most of the attributes that you can set on mstyle are not supported in core. The simple implementation would push every mstyle attr (could include the supported ones) onto every descendant, stopping for a specific attribute if it is already present on a node (i.e., the child would override the mstyle setting). This simple implementation would not technically be correct, but likely would not cause a problem unless run through a validator. The better/correct solution is to only put the attribute on the nodes that accept that attribute. Note that this "push until attr is encountered" solution works even for nested mstyles.
  • ✓[small] mfrac@linethickness -- thin, medium, thick not supported in core. Simple transformation to convert those names to % values. Implementation listed here
  • ✓[small] mathsize -- the mathsize attr supports named values 'small', 'normal', and 'big'. These are not in core. Simple rewrite to % values. The polyfill is here.
  • [small] semantics -- in core, we added the requirement that the first child be presentation MathML. Full allows it anywhere (but almost everyone makes it the first child...). This polyfill just needs to potentially reorder the children.
  • [small] named spaces (thinspace, thickspace, ...): these were removed from core. This is a simple translation that turns them into 'em's. When used in mpadded, there is a little complication because they can be multiplied by a constant, so simple replacement doesn't work for mpadded attrs.
  • [small] scriptshift/superscriptshift attributes of msubsup, etc: these are removed from core. Probably could fake them out via adding mpadded around the script element and set the voffset, height, and depth attrs.

The remaining polyfills may be a bit different than the above ones:

  • ✓[small] ms -- ms is supported in core, but not the attributes and not escaping quotes. The existing polyfill adds the quotes and escapes to the contents of the 'ms' and leaves it to core to render the 'ms'. This means that core is responsible for handling writing direction. An alternative is to put the quotes in mo and build an mrow around the ms. The content of the ms still needs to be potentially changed to handling escaping. The polyfill leaves the attrs unchanged so they can be examined if needed.
    • Because Firefox implements full MathML's 'ms', this polyfill is not compatible with Firefox or Safari.
  • [medium] mtable/mtr/mtd attrs -- none are supported in core. Need to map to CSS if possible and write code otherwise. Some might not be worth trying to get to work due lack of CSS equivalents.
  • ✓[medium] menclose -- It can not in general be transformed to anything in MathML core, but the values can be handled by adding a style to the child and using CSS borders. MathJax has a clever way to do the arrows and I have used that idea so that all the standard notations plus all the arrows mentioned as "optional" are handled.
  • ✓[large] elementary math -- elementary math consists of many elements. The top-level ones are mstack and mlondiv. Children elements include msrow, mscarries, mline, and others. The current implementation converts the elementary math to an HTML table inside of a shadow DOM. To do this, it adds mtext and span around the elementary math.
    • It would probably be better to use mtable. That would allow other MathML content besides elementary math inside the math element (that is very rare, I believe). It would allow the crossout to be done better via menclose... except that menclose appears to have been deleted from core.
    • Another implementation option might have been to use display: inline-grid. I didn't explore how feasible this is.
    • Using a shadow DOM for a complex element potentially creates problems because the children of the toplevel elements have no links to what is created in the shadow DOM. slot unfortunately doesn't help out because it only works for direct children of the top level element (mstack or mlongdiv)

Polyfills that need to measure the rendered MathML

  • ✓[small] mpadded@"pseudo-unit" -- #103. What is missing from core is the ability to size vertically based on the width and size horizontally based on the height or depth. Although full is not specific, a circularity where width depends on height and height on width is resolved by first calculating the intrinsic dimensions and then changing them. Potentially a polyfill could work by laying out mpadded without the attrs, getting it's bounds, and then modifying the attrs with pseudo-units to use the measured quantities in place of the pseudo-units.
  • ✓[small] mfrac@numalign/denomalign -- the 'left' and 'right' values are not supported in core. 30. The seemingly simple choice is to use CSS's text-align, but we can't because it's value is inherited. The issue mentions maybe taking advantage CSS Box Alignment Module Level 3 properties (justify-self?), but not clear this is supported in core. If CSS alignment can't be used, this might require measuring the numerator and denominator and adding an invisible box (or mspace which is the same thing) to the smaller of the two, either after or before it to force left or right alignment.
  • ✓[small] munderover/munder/mover@align -- the 'left' and 'right' values are not supported in core. 30. This is basically the same as mfrac@numalign/denomalign.

✓Line breaking/indentation -- requires measuring MathML and access to full DOM (for alignment)

mo has a set of attributes used for linebreaking and also a set that is used for indentation following the linebreak. None of these are in core.

One of the linebreaking attributes is linebreak and has values "auto" | "newline" | "nobreak" | "goodbreak" | "badbreak". For legacy reasons, mspace also allows linebreak. newline forces a linebreak; this is an obvious starting point for getting linebreaking to work. The indentation after the linebreak is important; that is controlled by a number of mo indent attrs. A reasonable linebreaking algorithm is given in the full spec. It is based on breaking before or after mo (based on the value of the linebreakstyle attr). It requires knowing:

  1. the block width,
  2. the amount of the line that is used up to the point of the mo,
  3. the depth in the mathml tree at the point of the mo,
  4. the amount the next line will indent, and
  5. whether a linebreak value has been specified

To do this polyfill, the polyfill will need to layout the mrow to be line wrapped and be able to determine the horizontal offset from the start of the line/mrow at each mo element. Maybe the result ends up using absolute positions to position (x,y) of the subsequent lines, or maybe a table is used (one line == one row in the table with columns to deal with alignment), maybe grid or flexbox layout can be used. Or maybe this is a case where a little pre-planning for supporting linebreaks in core leads to a few features that a polyfill could take advantage of such as allowing children to get a block layout.

Labeled as needing polyfills but probably not needed

  • maligngroup/malignmark -- #181. Very hard to do. Might deprecate from full, so probably nothing to do unless we replace them with something else.
  • deprecated in full attrs -- #5. There are some deprecated atts in full. Fred wrote a polyfill. I don't see the need to handle deprecated attrs that probably haven't been used in over a decade.