summaryrefslogtreecommitdiffstats
path: root/layout/mathml
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /layout/mathml
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/mathml')
-rw-r--r--layout/mathml/crashtests/1028521-1.xhtml5
-rw-r--r--layout/mathml/crashtests/1061027.html12
-rw-r--r--layout/mathml/crashtests/151054-1.xml15
-rw-r--r--layout/mathml/crashtests/289180-1.xml16
-rw-r--r--layout/mathml/crashtests/307826-1.xhtml30
-rw-r--r--layout/mathml/crashtests/307839-1.xhtml15
-rw-r--r--layout/mathml/crashtests/307839-2.xhtml24
-rw-r--r--layout/mathml/crashtests/323733-1.xml7
-rw-r--r--layout/mathml/crashtests/323737-1.xml9
-rw-r--r--layout/mathml/crashtests/323738-1.xml11
-rw-r--r--layout/mathml/crashtests/323741-1.xml13
-rw-r--r--layout/mathml/crashtests/323742-1.xml16
-rw-r--r--layout/mathml/crashtests/336074-1.xhtml25
-rw-r--r--layout/mathml/crashtests/347355-1-inner.xhtml28
-rw-r--r--layout/mathml/crashtests/347355-1.gifbin0 -> 980 bytes
-rw-r--r--layout/mathml/crashtests/347355-1.html9
-rw-r--r--layout/mathml/crashtests/347495-1.xhtml10
-rw-r--r--layout/mathml/crashtests/347507-1.xhtml29
-rw-r--r--layout/mathml/crashtests/348492-1.xhtml31
-rw-r--r--layout/mathml/crashtests/348709-1.xhtml20
-rw-r--r--layout/mathml/crashtests/348811-1.xhtml26
-rw-r--r--layout/mathml/crashtests/348811-2.xhtml31
-rw-r--r--layout/mathml/crashtests/353612-1.xhtml17
-rw-r--r--layout/mathml/crashtests/355986-1.xhtml34
-rw-r--r--layout/mathml/crashtests/364685-1.xhtml19
-rw-r--r--layout/mathml/crashtests/366012-1.xhtml23
-rw-r--r--layout/mathml/crashtests/366564-1.xhtml44
-rw-r--r--layout/mathml/crashtests/367107-1.html22
-rw-r--r--layout/mathml/crashtests/368430-1.xhtml5
-rw-r--r--layout/mathml/crashtests/370791-1.xhtml33
-rw-r--r--layout/mathml/crashtests/370862-1.xhtml27
-rw-r--r--layout/mathml/crashtests/372483-1.xhtml7
-rw-r--r--layout/mathml/crashtests/373472-1.xhtml20
-rw-r--r--layout/mathml/crashtests/373472-2.xhtml9
-rw-r--r--layout/mathml/crashtests/375562-1.xhtml38
-rw-r--r--layout/mathml/crashtests/377824-1.xhtml5
-rw-r--r--layout/mathml/crashtests/379418-1.xhtml7
-rw-r--r--layout/mathml/crashtests/385226-1.xhtml29
-rw-r--r--layout/mathml/crashtests/393760-1.xhtml16
-rw-r--r--layout/mathml/crashtests/397518-1.xhtml15
-rw-r--r--layout/mathml/crashtests/398038-1.html8
-rw-r--r--layout/mathml/crashtests/400157.xhtml34
-rw-r--r--layout/mathml/crashtests/400475-1.xhtml16
-rw-r--r--layout/mathml/crashtests/402400-1.xhtml41
-rw-r--r--layout/mathml/crashtests/403156-1.xhtml12
-rw-r--r--layout/mathml/crashtests/404485-1.xhtml9
-rw-r--r--layout/mathml/crashtests/405187-1.xhtml10
-rw-r--r--layout/mathml/crashtests/405271-1.xml36
-rw-r--r--layout/mathml/crashtests/412237-1.xml11
-rw-r--r--layout/mathml/crashtests/413063-1.xhtml21
-rw-r--r--layout/mathml/crashtests/416907-1.xhtml5
-rw-r--r--layout/mathml/crashtests/420420-1.xhtml7
-rw-r--r--layout/mathml/crashtests/431072-1.xhtml25
-rw-r--r--layout/mathml/crashtests/443089-1.xhtml7
-rw-r--r--layout/mathml/crashtests/463763-1.xhtml10
-rw-r--r--layout/mathml/crashtests/463763-2.xhtml12
-rw-r--r--layout/mathml/crashtests/476547-1.xhtml5
-rw-r--r--layout/mathml/crashtests/477740-1.xhtml25
-rw-r--r--layout/mathml/crashtests/541620-1.xhtml6
-rw-r--r--layout/mathml/crashtests/557474-1.html11
-rw-r--r--layout/mathml/crashtests/654928-1.html3
-rw-r--r--layout/mathml/crashtests/655451-1.xhtml15
-rw-r--r--layout/mathml/crashtests/713606-1.html24
-rw-r--r--layout/mathml/crashtests/716349-1.html13
-rw-r--r--layout/mathml/crashtests/848725-1.html17
-rw-r--r--layout/mathml/crashtests/848725-2.html17
-rw-r--r--layout/mathml/crashtests/947557-1.html21
-rw-r--r--layout/mathml/crashtests/973322-1.xhtml8
-rw-r--r--layout/mathml/crashtests/crashtests.list66
-rw-r--r--layout/mathml/imptests/LICENSE32
-rw-r--r--layout/mathml/imptests/fonts/fraction-axisheight7000-rulethickness1000.woffbin0 -> 1208 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woffbin0 -> 1264 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woffbin0 -> 1280 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-denominatorgapmin4000-rulethickness1000.woffbin0 -> 1232 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-denominatorshiftdown3000-rulethickness1000.woffbin0 -> 1240 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woffbin0 -> 1256 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woffbin0 -> 1264 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-numeratorgapmin9000-rulethickness1000.woffbin0 -> 1220 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-numeratorshiftup11000-rulethickness1000.woffbin0 -> 1232 bytes
-rw-r--r--layout/mathml/imptests/fonts/fraction-rulethickness10000.woffbin0 -> 1164 bytes
-rw-r--r--layout/mathml/imptests/fonts/stack-axisheight7000.woffbin0 -> 1144 bytes
-rw-r--r--layout/mathml/imptests/fonts/stack-bottomdisplaystyleshiftdown5000.woffbin0 -> 1204 bytes
-rw-r--r--layout/mathml/imptests/fonts/stack-bottomshiftdown6000.woffbin0 -> 1172 bytes
-rw-r--r--layout/mathml/imptests/fonts/stack-displaystylegapmin4000.woffbin0 -> 1176 bytes
-rw-r--r--layout/mathml/imptests/fonts/stack-gapmin8000.woffbin0 -> 1136 bytes
-rw-r--r--layout/mathml/imptests/fonts/stack-topdisplaystyleshiftup3000.woffbin0 -> 1188 bytes
-rw-r--r--layout/mathml/imptests/fonts/stack-topshiftup9000.woffbin0 -> 1156 bytes
-rw-r--r--layout/mathml/imptests/fonts/xheight500.woffbin0 -> 1044 bytes
-rw-r--r--layout/mathml/imptests/mochitest.ini26
-rw-r--r--layout/mathml/imptests/test_fraction-parameters.html244
-rw-r--r--layout/mathml/imptests/test_lengths-3.html158
-rw-r--r--layout/mathml/imptests/test_stack-parameters.html176
-rw-r--r--layout/mathml/jar.mn6
-rw-r--r--layout/mathml/mathfont.properties1164
-rw-r--r--layout/mathml/mathfontSTIXGeneral.properties128
-rw-r--r--layout/mathml/mathfontSymbol.properties46
-rw-r--r--layout/mathml/mathfontUnicode.properties92
-rw-r--r--layout/mathml/mathml.css338
-rw-r--r--layout/mathml/moz.build72
-rw-r--r--layout/mathml/nsIMathMLFrame.h387
-rw-r--r--layout/mathml/nsMathMLAtoms.h10
-rw-r--r--layout/mathml/nsMathMLChar.cpp2485
-rw-r--r--layout/mathml/nsMathMLChar.h269
-rw-r--r--layout/mathml/nsMathMLContainerFrame.cpp1607
-rw-r--r--layout/mathml/nsMathMLContainerFrame.h565
-rw-r--r--layout/mathml/nsMathMLFrame.cpp427
-rw-r--r--layout/mathml/nsMathMLFrame.h381
-rw-r--r--layout/mathml/nsMathMLOperators.cpp467
-rw-r--r--layout/mathml/nsMathMLOperators.h181
-rw-r--r--layout/mathml/nsMathMLParts.h41
-rw-r--r--layout/mathml/nsMathMLSelectedFrame.cpp189
-rw-r--r--layout/mathml/nsMathMLSelectedFrame.h69
-rw-r--r--layout/mathml/nsMathMLTokenFrame.cpp205
-rw-r--r--layout/mathml/nsMathMLTokenFrame.h68
-rw-r--r--layout/mathml/nsMathMLmactionFrame.cpp338
-rw-r--r--layout/mathml/nsMathMLmactionFrame.h76
-rw-r--r--layout/mathml/nsMathMLmencloseFrame.cpp868
-rw-r--r--layout/mathml/nsMathMLmencloseFrame.h126
-rw-r--r--layout/mathml/nsMathMLmfencedFrame.cpp743
-rw-r--r--layout/mathml/nsMathMLmfencedFrame.h116
-rw-r--r--layout/mathml/nsMathMLmfracFrame.cpp663
-rw-r--r--layout/mathml/nsMathMLmfracFrame.h118
-rw-r--r--layout/mathml/nsMathMLmmultiscriptsFrame.cpp704
-rw-r--r--layout/mathml/nsMathMLmmultiscriptsFrame.h53
-rw-r--r--layout/mathml/nsMathMLmoFrame.cpp1121
-rw-r--r--layout/mathml/nsMathMLmoFrame.h107
-rw-r--r--layout/mathml/nsMathMLmpaddedFrame.cpp449
-rw-r--r--layout/mathml/nsMathMLmpaddedFrame.h93
-rw-r--r--layout/mathml/nsMathMLmrootFrame.cpp419
-rw-r--r--layout/mathml/nsMathMLmrootFrame.h71
-rw-r--r--layout/mathml/nsMathMLmrowFrame.cpp69
-rw-r--r--layout/mathml/nsMathMLmrowFrame.h54
-rw-r--r--layout/mathml/nsMathMLmspaceFrame.cpp132
-rw-r--r--layout/mathml/nsMathMLmspaceFrame.h56
-rw-r--r--layout/mathml/nsMathMLmsqrtFrame.cpp57
-rw-r--r--layout/mathml/nsMathMLmsqrtFrame.h68
-rw-r--r--layout/mathml/nsMathMLmtableFrame.cpp1363
-rw-r--r--layout/mathml/nsMathMLmtableFrame.h334
-rw-r--r--layout/mathml/nsMathMLmunderoverFrame.cpp686
-rw-r--r--layout/mathml/nsMathMLmunderoverFrame.h57
-rw-r--r--layout/mathml/nsMathMLsemanticsFrame.cpp117
-rw-r--r--layout/mathml/nsMathMLsemanticsFrame.h31
-rw-r--r--layout/mathml/operatorDictionary.xsl76
-rw-r--r--layout/mathml/tests/chrome.ini6
-rw-r--r--layout/mathml/tests/mathml_example_test.html28
-rw-r--r--layout/mathml/tests/mochitest.ini17
-rw-r--r--layout/mathml/tests/stretchy-and-large-operators.html88
-rw-r--r--layout/mathml/tests/stretchy-and-large-operators.js2
-rw-r--r--layout/mathml/tests/test_bug330964.html98
-rw-r--r--layout/mathml/tests/test_bug553917.html171
-rw-r--r--layout/mathml/tests/test_bug706406.html71
-rw-r--r--layout/mathml/tests/test_bug827713-2.html152
-rw-r--r--layout/mathml/tests/test_bug827713.html61
-rw-r--r--layout/mathml/tests/test_bug975681.html118
-rw-r--r--layout/mathml/tests/test_disabled.html47
-rw-r--r--layout/mathml/tests/test_disabled_chrome.html55
-rw-r--r--layout/mathml/tests/test_opentype-axis-height.html60
-rw-r--r--layout/mathml/tests/test_opentype-fraction.html187
-rw-r--r--layout/mathml/tests/test_opentype-limits.html172
-rw-r--r--layout/mathml/tests/test_opentype-radical.html196
-rw-r--r--layout/mathml/tests/test_opentype-scripts.html292
-rw-r--r--layout/mathml/tests/test_opentype-stack.html137
-rwxr-xr-xlayout/mathml/updateOperatorDictionary.pl499
163 files changed, 22702 insertions, 0 deletions
diff --git a/layout/mathml/crashtests/1028521-1.xhtml b/layout/mathml/crashtests/1028521-1.xhtml
new file mode 100644
index 000000000..b8d094774
--- /dev/null
+++ b/layout/mathml/crashtests/1028521-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<mstyle xmlns="http://www.w3.org/1998/Math/MathML"><li xmlns="http://www.w3.org/1999/xhtml" dir="rtl"></li></mstyle>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/1061027.html b/layout/mathml/crashtests/1061027.html
new file mode 100644
index 000000000..ee69c8ed0
--- /dev/null
+++ b/layout/mathml/crashtests/1061027.html
@@ -0,0 +1,12 @@
+<style>
+.c1 { transform-style: preserve-3d; display: table-row-group;</style><script>
+var docElement = document.documentElement;
+function init() {
+test1 = document.createElementNS("http://www.w3.org/1999/xhtml", "defs");
+test1.setAttribute("class", "c1");
+docElement.appendChild(test1);
+test2 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "msub");
+test1.appendChild(test2);
+}
+document.addEventListener("DOMContentLoaded", init, false);
+</script> \ No newline at end of file
diff --git a/layout/mathml/crashtests/151054-1.xml b/layout/mathml/crashtests/151054-1.xml
new file mode 100644
index 000000000..619345c9b
--- /dev/null
+++ b/layout/mathml/crashtests/151054-1.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Type" content="text/html" />
+ <title>Math with position: absolute</title>
+ <style>
+ math { position: absolute; }
+ </style>
+</head>
+<body>
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <infinity/>
+ </math>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/289180-1.xml b/layout/mathml/crashtests/289180-1.xml
new file mode 100644
index 000000000..7ad9f54ff
--- /dev/null
+++ b/layout/mathml/crashtests/289180-1.xml
@@ -0,0 +1,16 @@
+
+
+ <math xmlns="http://www.w3.org/1998/Math/MathML" mode="inline">
+ <mfrac>
+ <mi>x</mi>
+ <mi>y</mi>
+ </mfrac>
+ </math>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" mode="display">
+ <mfrac>
+
+ <mi>x</mi>
+ <mi>y</mi>
+ </mfrac>
+ </math>
+
diff --git a/layout/mathml/crashtests/307826-1.xhtml b/layout/mathml/crashtests/307826-1.xhtml
new file mode 100644
index 000000000..879b426e1
--- /dev/null
+++ b/layout/mathml/crashtests/307826-1.xhtml
@@ -0,0 +1,30 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+
+<title>Testcase for MathML crash</title>
+
+<script><![CDATA[
+
+function boom() {
+ var div = document.getElementById("div");
+ var mi = document.getElementById("mi");
+ mi.appendChild(div);
+ mi.removeChild(div);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]></script>
+
+</head>
+
+
+<body onload="setTimeout(boom, 30);">
+
+<div style="float: right;" id="div">Floated div</div>
+
+<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><mi id="mi">mi</mi></math>
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/307839-1.xhtml b/layout/mathml/crashtests/307839-1.xhtml
new file mode 100644
index 000000000..7275f0859
--- /dev/null
+++ b/layout/mathml/crashtests/307839-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Testcase</title>
+ </head>
+ <body onload="document.getElementById('B').appendChild(document.getElementById('A'));">
+ <div style="position: relative;">
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mover>
+ <mrow id="A"/>
+ <mn id="B"/>
+ </mover>
+ </math>
+ </div>
+ </body>
+</html>
diff --git a/layout/mathml/crashtests/307839-2.xhtml b/layout/mathml/crashtests/307839-2.xhtml
new file mode 100644
index 000000000..77c13fbd6
--- /dev/null
+++ b/layout/mathml/crashtests/307839-2.xhtml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN" "http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd" [
+ <!ENTITY mathml "http://www.w3.org/1998/Math/MathML">
+]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>Testcase bug - Evil mrow:hover testcase crashes Mozilla</title>
+<style type="text/css">
+#h:hover{display:block;}
+</style>
+</head>
+<body onload="document.getElementById('mrow').setAttribute('id', 'h');">
+<math mode="display" xmlns="http://www.w3.org/1998/Math/MathML">
+<mover>
+ <mrow id="mrow">hovering over this should not crash Mozilla</mrow>
+ <mover>
+ <mo>10</mo>
+ <mrow>times</mrow>
+ </mover>
+</mover>
+</math>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/323733-1.xml b/layout/mathml/crashtests/323733-1.xml
new file mode 100644
index 000000000..5551d8588
--- /dev/null
+++ b/layout/mathml/crashtests/323733-1.xml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">
+ <ms />
+ </math>
+ </div>
+</html>
diff --git a/layout/mathml/crashtests/323737-1.xml b/layout/mathml/crashtests/323737-1.xml
new file mode 100644
index 000000000..ad673f946
--- /dev/null
+++ b/layout/mathml/crashtests/323737-1.xml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">
+ <mtable>
+ <mroot />
+ </mtable>
+ </math>
+ </div>
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/323738-1.xml b/layout/mathml/crashtests/323738-1.xml
new file mode 100644
index 000000000..318aa93b4
--- /dev/null
+++ b/layout/mathml/crashtests/323738-1.xml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">
+ <msub>
+ <mo>
+ <factorial />
+ </mo>
+ </msub>
+ </math>
+ </div>
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/323741-1.xml b/layout/mathml/crashtests/323741-1.xml
new file mode 100644
index 000000000..8527c1a78
--- /dev/null
+++ b/layout/mathml/crashtests/323741-1.xml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">
+ <notin>
+ <mspace>
+ <mfenced />
+ </mspace>
+
+ f
+ </notin>
+ </math>
+ </div>
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/323742-1.xml b/layout/mathml/crashtests/323742-1.xml
new file mode 100644
index 000000000..2cd225990
--- /dev/null
+++ b/layout/mathml/crashtests/323742-1.xml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">
+
+ <eulergamma>
+ <arcsech>
+ <mrow>
+ <mtable>
+ <maction/>
+ </mtable>
+ </mrow>
+ </arcsech>
+ </eulergamma>
+ </math>
+ </div>
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/336074-1.xhtml b/layout/mathml/crashtests/336074-1.xhtml
new file mode 100644
index 000000000..b8d3d9225
--- /dev/null
+++ b/layout/mathml/crashtests/336074-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+
+<script>
+<![CDATA[
+
+function init()
+{
+ var k = document.getElementById("k");
+ k.parentNode.removeChild(k);
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+
+</head>
+
+<body onload="setTimeout(init, 30);">
+
+<mo xmlns='http://www.w3.org/1998/Math/MathML'><mi id="k">z</mi></mo>
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/347355-1-inner.xhtml b/layout/mathml/crashtests/347355-1-inner.xhtml
new file mode 100644
index 000000000..9c0b46ff5
--- /dev/null
+++ b/layout/mathml/crashtests/347355-1-inner.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function foo()
+{
+ var mrow = document.getElementById("mrow")
+
+ mrow.parentNode.removeChild(mrow);
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(foo, 100)">
+
+<div>
+<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mrow id="mrow">
+ <mtd><img xmlns="http://www.w3.org/1999/xhtml" src="347355-1.gif" /></mtd>
+ <mtable> <mtr> <mtd><mi>t</mi></mtd> </mtr> </mtable>
+ </mrow>
+</math>
+</div>
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/347355-1.gif b/layout/mathml/crashtests/347355-1.gif
new file mode 100644
index 000000000..475ea8c16
--- /dev/null
+++ b/layout/mathml/crashtests/347355-1.gif
Binary files differ
diff --git a/layout/mathml/crashtests/347355-1.html b/layout/mathml/crashtests/347355-1.html
new file mode 100644
index 000000000..7e3c6c7c5
--- /dev/null
+++ b/layout/mathml/crashtests/347355-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="347355-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/347495-1.xhtml b/layout/mathml/crashtests/347495-1.xhtml
new file mode 100644
index 000000000..9cff7bdcf
--- /dev/null
+++ b/layout/mathml/crashtests/347495-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <munderover> <mo>&#x0220F;</mo> <mn>1</mn> </munderover>
+</math></div>
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/347507-1.xhtml b/layout/mathml/crashtests/347507-1.xhtml
new file mode 100644
index 000000000..6231950c7
--- /dev/null
+++ b/layout/mathml/crashtests/347507-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function foo()
+{
+ var div = document.getElementById("div");
+ div.appendChild(div.firstChild);
+
+ var td = document.getElementById("td");
+ td.setAttribute('rowspan', -2);
+}
+
+</script>
+
+</head>
+
+<body onload="foo()">
+
+
+<div id="div"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+
+ <mtable> <mtr> <mtd id="td"><mi>y</mi></mtd> </mtr> </mtable>
+
+</math></div>
+
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/348492-1.xhtml b/layout/mathml/crashtests/348492-1.xhtml
new file mode 100644
index 000000000..18edae35d
--- /dev/null
+++ b/layout/mathml/crashtests/348492-1.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<script>
+var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+
+function foo()
+{
+ var mmultiscripts = document.createElementNS(MATHML_NS, 'mmultiscripts');
+ var mtd = document.createElementNS(MATHML_NS, 'mtd');
+
+
+ document.body.appendChild(mmultiscripts);
+ aC(mmultiscripts, mtd);
+ aC(mmultiscripts, document.createTextNode('foo'));
+
+ document.documentElement.removeAttribute("class");
+}
+
+function aC(q1, q2) { q1.appendChild(q2); }
+
+</script>
+</head>
+
+<body onload="setTimeout(foo, 30)">
+
+
+
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/348709-1.xhtml b/layout/mathml/crashtests/348709-1.xhtml
new file mode 100644
index 000000000..907ede6ab
--- /dev/null
+++ b/layout/mathml/crashtests/348709-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+function foo()
+{
+ document.getElementById("mtr").appendChild(document.createTextNode("a"));
+}
+</script>
+</head>
+
+<body onload="foo()">
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+<mrow> <mfenced open="[" close="]"> <mtable> <mtr id="mtr"> <mtd><mi>x</mi></mtd> <mtd><mi>y</mi></mtd> </mtr> </mtable> </mfenced> </mrow>
+</math></div>
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/348811-1.xhtml b/layout/mathml/crashtests/348811-1.xhtml
new file mode 100644
index 000000000..367cc32be
--- /dev/null
+++ b/layout/mathml/crashtests/348811-1.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function foo()
+{
+ var newTable = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mtable")
+
+ document.getElementById("mtr").appendChild(newTable);
+}
+</script>
+</head>
+
+<body onload="foo()">
+
+
+<div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mtable> <mtr id="mtr"> <mtd>
+ <mi>x</mi>
+ </mtd> </mtr> </mtable> </math>
+</div>
+
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/348811-2.xhtml b/layout/mathml/crashtests/348811-2.xhtml
new file mode 100644
index 000000000..30f7943a9
--- /dev/null
+++ b/layout/mathml/crashtests/348811-2.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function foo()
+{
+ var m = "http://www.w3.org/1998/Math/MathML";
+ var newTable = document.createElementNS(m, "mtable")
+ document.getElementById("mtable").appendChild(newTable);
+}
+</script>
+</head>
+
+<body onload="foo()">
+
+
+<div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mtable id="mtable">
+ <mtr id="mtr">
+ <mtd>
+ <mi>x</mi>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+</div>
+
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/353612-1.xhtml b/layout/mathml/crashtests/353612-1.xhtml
new file mode 100644
index 000000000..386e66f93
--- /dev/null
+++ b/layout/mathml/crashtests/353612-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function foo()
+{
+ document.getElementById("ms").setAttribute("lquote", "'");
+}
+</script>
+</head>
+
+<body onload="foo()">
+
+<mtr xmlns="http://www.w3.org/1998/Math/MathML" />
+<mstyle xmlns="http://www.w3.org/1998/Math/MathML" id="ms" />
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/355986-1.xhtml b/layout/mathml/crashtests/355986-1.xhtml
new file mode 100644
index 000000000..d2736cf73
--- /dev/null
+++ b/layout/mathml/crashtests/355986-1.xhtml
@@ -0,0 +1,34 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+
+<head>
+<style>
+
+body * {
+ display: table-footer-group;
+ overflow: -moz-scrollbars-vertical;
+}
+
+</style>
+</head>
+
+
+
+<body onload="document.getElementById('eee').style.display = 'inline';">
+
+
+
+
+<div id="eee">
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mtable>
+ <mtr>
+ <mtd><mi>x</mi></mtd>
+ </mtr>
+ </mtable>
+ </math>
+</div>
+
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/364685-1.xhtml b/layout/mathml/crashtests/364685-1.xhtml
new file mode 100644
index 000000000..25a2499f5
--- /dev/null
+++ b/layout/mathml/crashtests/364685-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:math="http://www.w3.org/1998/Math/MathML">
+
+<body onload="boom();">
+
+<script>
+function boom()
+{
+ document.getElementById("merror").appendChild(document.getElementById("s"));
+}
+</script>
+
+<math:merror id="merror"></math:merror>
+
+<span id="s" />
+
+</body>
+</html>
+
diff --git a/layout/mathml/crashtests/366012-1.xhtml b/layout/mathml/crashtests/366012-1.xhtml
new file mode 100644
index 000000000..306a84738
--- /dev/null
+++ b/layout/mathml/crashtests/366012-1.xhtml
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ var doom = document.getElementById("doom");
+ doom.parentNode.removeChild(doom);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+ <div>
+ <p id="doom">This paragraph disappears.</p>
+ <math xmlns='http://www.w3.org/1998/Math/MathML' display='block'><msub><mi>y</mi><mn>0</mn><frameset xmlns="http://www.w3.org/1999/xhtml" /></msub></math>
+ </div>
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/366564-1.xhtml b/layout/mathml/crashtests/366564-1.xhtml
new file mode 100644
index 000000000..a83e5a8fe
--- /dev/null
+++ b/layout/mathml/crashtests/366564-1.xhtml
@@ -0,0 +1,44 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN" "http://www.w3.org/Math/DTD/mathml2/xhtml-math11-f.dtd" >
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+function boom1()
+{
+ document.getElementById("mfenced3").appendChild(document.getElementById("div4"));
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ document.getElementById("sup1").appendChild(document.getElementById("mo2"));
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom1, 30);">
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <msup id="sup1">
+ <mi>b</mi>
+ <mn>2</mn>
+ </msup>
+</math></div>
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mi>j</mi>
+ <mo id="mo2">=</mo>
+ <mfenced id="mfenced3" open="[" close="]">
+ <mn>55</mn>
+ </mfenced>
+</math></div>
+
+<div id="div4"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mo> &int; </mo>
+</math></div>
+
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/367107-1.html b/layout/mathml/crashtests/367107-1.html
new file mode 100644
index 000000000..775ace87e
--- /dev/null
+++ b/layout/mathml/crashtests/367107-1.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+
+ var mtr = document.createElementNS(MATHML_NS, 'mtr');
+ var mtable = document.createElementNS(MATHML_NS, 'mtable');
+
+ document.body.appendChild(mtr);
+ mtr.appendChild(mtable);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/368430-1.xhtml b/layout/mathml/crashtests/368430-1.xhtml
new file mode 100644
index 000000000..79e10c46a
--- /dev/null
+++ b/layout/mathml/crashtests/368430-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<maction xmlns="http://www.w3.org/1998/Math/MathML">Foo Bar</maction>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/370791-1.xhtml b/layout/mathml/crashtests/370791-1.xhtml
new file mode 100644
index 000000000..13e2a0b54
--- /dev/null
+++ b/layout/mathml/crashtests/370791-1.xhtml
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML" class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var mi = document.getElementById("mi");
+ var textA = mi.firstChild;
+ var textB = document.createTextNode("b");
+ var textSpace = document.createTextNode(" ");
+
+ var floater = document.getElementById("floater");
+
+ mi.appendChild(textB);
+ mi.appendChild(textSpace);
+ mi.removeChild(textA);
+ mi.removeChild(textSpace);
+
+ floater.parentNode.removeChild(floater);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<math:mi id="mi">a<p><span style="float: right;" id="floater" /></p></math:mi>
+
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/370862-1.xhtml b/layout/mathml/crashtests/370862-1.xhtml
new file mode 100644
index 000000000..42511880f
--- /dev/null
+++ b/layout/mathml/crashtests/370862-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+function boom()
+{
+ var math = document.getElementById("math");
+ var div = document.getElementById("div")
+
+ math.setAttribute("color", "g");
+ document.body.appendChild(div);
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<div id="div">
+
+<mo xmlns='http://www.w3.org/1998/Math/MathML'>+<math id="math"><mn>1</mn></math></mo>
+
+</div>
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/372483-1.xhtml b/layout/mathml/crashtests/372483-1.xhtml
new file mode 100644
index 000000000..41edd7d9b
--- /dev/null
+++ b/layout/mathml/crashtests/372483-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<div><munder xmlns="http://www.w3.org/1998/Math/MathML"/></div>
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/373472-1.xhtml b/layout/mathml/crashtests/373472-1.xhtml
new file mode 100644
index 000000000..6c6a304a7
--- /dev/null
+++ b/layout/mathml/crashtests/373472-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("mo").appendChild(document.getElementById("mo2"));
+ document.body.offsetWidth;
+ document.getElementById("span").appendChild(document.createTextNode("baz"));
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<p>Foo <math xmlns="http://www.w3.org/1998/Math/MathML"><mo id="mo2">-</mo></math></p>
+
+<p><math xmlns="http://www.w3.org/1998/Math/MathML"><msub id="msub"><mo id="mo">+</mo></msub></math> <span id="span">bar</span></p>
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/373472-2.xhtml b/layout/mathml/crashtests/373472-2.xhtml
new file mode 100644
index 000000000..1c7e1c7ad
--- /dev/null
+++ b/layout/mathml/crashtests/373472-2.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<p><math xmlns="http://www.w3.org/1998/Math/MathML"><mroot><mo>k</mo></mroot></math></p>
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/375562-1.xhtml b/layout/mathml/crashtests/375562-1.xhtml
new file mode 100644
index 000000000..491b31359
--- /dev/null
+++ b/layout/mathml/crashtests/375562-1.xhtml
@@ -0,0 +1,38 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function boom()
+{
+ var a = document.getElementById("a");
+ var plus = document.getElementById("plus");
+ var frameset = document.createElementNS(HTML_NS, "frameset");
+
+ document.body.style.width = "300px";
+
+ a.parentNode.removeChild(a);
+
+ plus.appendChild(frameset);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <msup>
+ <mi id="a">a</mi>
+ <mo id="plus">+</mo>
+ <mi>b</mi>
+ <mi>c</mi>
+ </msup>
+</math></div>
+
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/377824-1.xhtml b/layout/mathml/crashtests/377824-1.xhtml
new file mode 100644
index 000000000..ddbe7da66
--- /dev/null
+++ b/layout/mathml/crashtests/377824-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<body>
+<math:mtable />
+</body>
+</html>
diff --git a/layout/mathml/crashtests/379418-1.xhtml b/layout/mathml/crashtests/379418-1.xhtml
new file mode 100644
index 000000000..dc6e9e571
--- /dev/null
+++ b/layout/mathml/crashtests/379418-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+×¢</math>
+</body>
+</html>
+
diff --git a/layout/mathml/crashtests/385226-1.xhtml b/layout/mathml/crashtests/385226-1.xhtml
new file mode 100644
index 000000000..b1d261eb3
--- /dev/null
+++ b/layout/mathml/crashtests/385226-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<head>
+<style>
+[class="capleft"] { caption-side: left; }
+[class="collapse"] { visibility: collapse; }
+</style>
+</head>
+
+<body>
+
+<div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="block" class="collapse">
+ <mtable>
+ <mtr>
+ <mtd><mi>x</mi></mtd>
+ <mtd><mi>y</mi></mtd>
+ </mtr>
+ <html:caption class="capleft">
+ <mtd><mi>z</mi></mtd>
+ <mtd><mi>w</mi></mtd>
+ </html:caption>
+ </mtable>
+ </math>
+</div>
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/mathml/crashtests/393760-1.xhtml b/layout/mathml/crashtests/393760-1.xhtml
new file mode 100644
index 000000000..cb5c2d7a1
--- /dev/null
+++ b/layout/mathml/crashtests/393760-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("mfenced").setAttribute("mathbackground", "yellow");
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<math:mfenced id="mfenced" />
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/397518-1.xhtml b/layout/mathml/crashtests/397518-1.xhtml
new file mode 100644
index 000000000..bc460ead1
--- /dev/null
+++ b/layout/mathml/crashtests/397518-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+function boom()
+{
+ var span = document.getElementById("s");
+ var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ span.appendChild(newSpan);
+}
+</script>
+</head>
+<body onload="boom();">
+<math:math><span id="s"><div/></span></math:math>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/398038-1.html b/layout/mathml/crashtests/398038-1.html
new file mode 100644
index 000000000..b383141c4
--- /dev/null
+++ b/layout/mathml/crashtests/398038-1.html
@@ -0,0 +1,8 @@
+<html>
+<head><title>Crashtest for bug 398038</title></head>
+<body>
+ <math><mpadded height="20 10"/></math>
+ <math><mpadded height="10 0000000000em"/></math>
+ <math><mpadded height="10 1.2em"/></math>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/400157.xhtml b/layout/mathml/crashtests/400157.xhtml
new file mode 100644
index 000000000..78754875d
--- /dev/null
+++ b/layout/mathml/crashtests/400157.xhtml
@@ -0,0 +1,34 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML" class="reftest-wait">
+<mathml:mfenced/>
+
+
+<script><![CDATA[
+var docviewer;
+function do_onload() {
+ var navigator1 = SpecialPowers.wrap(parent).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).getInterface(SpecialPowers.Ci.nsIWebNavigation);
+ var docShell = navigator1.QueryInterface(SpecialPowers.Ci.nsIDocShell);
+ docviewer = docShell.contentViewer;
+
+ setTimeout(function() {
+ clearTimeout(timer);
+ docviewer.textZoom = 1;
+ document.documentElement.removeAttribute("class");
+ }, 500);
+ setTimeout(doe,50, 0.2);
+}
+do_onload();
+
+var timer;
+function doe(i) {
+ docviewer.textZoom += i;
+
+ if (docviewer.textZoom >=4)
+ i = -0.2;
+
+ if (docviewer.textZoom <=0)
+ i = 0.2;
+ window.status = docviewer.textZoom;
+ timer = setTimeout(doe, 50, i);
+}
+]]></script>
+</html>
diff --git a/layout/mathml/crashtests/400475-1.xhtml b/layout/mathml/crashtests/400475-1.xhtml
new file mode 100644
index 000000000..13ff2fcb6
--- /dev/null
+++ b/layout/mathml/crashtests/400475-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function foo()
+{
+ document.getElementById("ms").setAttribute("foo", "x");
+}
+</script>
+</head>
+
+<body onload="foo()">
+
+<mstyle xmlns="http://www.w3.org/1998/Math/MathML" id="ms" />
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/402400-1.xhtml b/layout/mathml/crashtests/402400-1.xhtml
new file mode 100644
index 000000000..8a2a92753
--- /dev/null
+++ b/layout/mathml/crashtests/402400-1.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var textB = document.createTextNode("b");
+ var textC = document.createTextNode("c");
+ var textN = document.createTextNode("n");
+ var textP = document.createTextNode("p");
+
+ var blv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ var odj = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ var tr = document.createElementNS("http://www.w3.org/1999/xhtml", "tr");
+ var td = document.createElementNS("http://www.w3.org/1999/xhtml", "td");
+ var mathMO = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo");
+
+ td.appendChild(textB);
+ document.body.appendChild(blv);
+ blv.appendChild(tr);
+ blv.appendChild(mathMO);
+ mathMO.appendChild(textP);
+ odj.appendChild(td);
+ odj.appendChild(textN);
+
+ blv.removeChild(tr);
+ mathMO.appendChild(odj);
+ td.removeChild(textB);
+ blv.appendChild(textC);
+ document.body.appendChild(odj);
+ odj.insertBefore(mathMO, textN);
+ document.body.appendChild(mathMO);
+ mathMO.appendChild(odj);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/mathml/crashtests/403156-1.xhtml b/layout/mathml/crashtests/403156-1.xhtml
new file mode 100644
index 000000000..b49bff7d0
--- /dev/null
+++ b/layout/mathml/crashtests/403156-1.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+
+<head>
+<style>
+maction { height: 12em; }
+</style>
+</head>
+
+<body>
+<m:maction>&#x499a;<span>c<div>t&#x499a;</div></span>&#x499a;x&#x499a;&#x499a;2</m:maction>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/404485-1.xhtml b/layout/mathml/crashtests/404485-1.xhtml
new file mode 100644
index 000000000..fea18cbd5
--- /dev/null
+++ b/layout/mathml/crashtests/404485-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+
+<body onload="document.getElementById('s').firstChild.splitText(1);">
+
+<math:msqrt><span xmlns="http://www.w3.org/1999/xhtml" id="s">x <math:mo/></span></math:msqrt>
+
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/405187-1.xhtml b/layout/mathml/crashtests/405187-1.xhtml
new file mode 100644
index 000000000..537512e62
--- /dev/null
+++ b/layout/mathml/crashtests/405187-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+<style id="s">
+[class="fx"] { position: fixed; }
+</style>
+</head>
+<body onload="document.getElementById('s').textContent = '* { color: magenta; }'">
+<m:mrow><m:mrow class="fx" /><span class="fx" /></m:mrow>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/405271-1.xml b/layout/mathml/crashtests/405271-1.xml
new file mode 100644
index 000000000..b7b9e7a60
--- /dev/null
+++ b/layout/mathml/crashtests/405271-1.xml
@@ -0,0 +1,36 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+
+<style type="text/css">
+#q { width: 16px; height: 16px; }
+#a, #b { counter-increment: chicken; }
+</style>
+
+<style id="s" type="text/css">
+#a { content: 'x'; }
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ remove(document.getElementById("s"));
+ remove(document.getElementById("b"));
+}
+
+function remove(n)
+{
+ n.parentNode.removeChild(n);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<m:mrow id="a"/><m:mo id="q"><m:malignmark><m:msubsup/><m:munderover/><m:munder id="b"/></m:malignmark></m:mo>
+
+</body>
+
+</html>
diff --git a/layout/mathml/crashtests/412237-1.xml b/layout/mathml/crashtests/412237-1.xml
new file mode 100644
index 000000000..2e8f13b73
--- /dev/null
+++ b/layout/mathml/crashtests/412237-1.xml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <ms fontsize="-2%"><mfrac><mi>x</mi><mi>y</mi></mfrac></ms>
+</math>
+
+</body>
+</html>
diff --git a/layout/mathml/crashtests/413063-1.xhtml b/layout/mathml/crashtests/413063-1.xhtml
new file mode 100644
index 000000000..425db9a81
--- /dev/null
+++ b/layout/mathml/crashtests/413063-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var mtd = document.getElementById("mtd");
+ var ns = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var nt = document.createTextNode("foo");
+ ns.appendChild(nt);
+ mtd.appendChild(ns);
+ mtd.setAttribute("rowspan", "0");
+}
+
+
+</script>
+</head>
+
+<body onload="boom();"><m:mtd id="mtd" style="display: table-footer-group"><m:mi /></m:mtd></body>
+
+</html>
diff --git a/layout/mathml/crashtests/416907-1.xhtml b/layout/mathml/crashtests/416907-1.xhtml
new file mode 100644
index 000000000..94a9abde2
--- /dev/null
+++ b/layout/mathml/crashtests/416907-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML">
+<mathml:mroot>
+<frameset/>
+</mathml:mroot>
+</html>
diff --git a/layout/mathml/crashtests/420420-1.xhtml b/layout/mathml/crashtests/420420-1.xhtml
new file mode 100644
index 000000000..db17b277a
--- /dev/null
+++ b/layout/mathml/crashtests/420420-1.xhtml
@@ -0,0 +1,7 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN" "http://www.w3.org/Math/DTD/mathml2/xhtml-math11-f.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<body>
+<m:math><span><m:mfenced><m:mo>&ApplyFunction;</m:mo></m:mfenced></span></m:math>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/431072-1.xhtml b/layout/mathml/crashtests/431072-1.xhtml
new file mode 100644
index 000000000..f25ce28f1
--- /dev/null
+++ b/layout/mathml/crashtests/431072-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("math").setAttribute("class", "li");
+ document.documentElement.offsetHeight;
+ document.documentElement.appendChild(document.createTextNode("\n"));
+}
+
+</script>
+
+<style type="text/css">
+
+[class="li"] { display: list-item; }
+
+</style>
+
+</head>
+
+<body onload="boom();"><math xmlns="http://www.w3.org/1998/Math/MathML" id="math"/></body>
+
+</html>
diff --git a/layout/mathml/crashtests/443089-1.xhtml b/layout/mathml/crashtests/443089-1.xhtml
new file mode 100644
index 000000000..2630cea13
--- /dev/null
+++ b/layout/mathml/crashtests/443089-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+<mtd xmlns="http://www.w3.org/1998/Math/MathML" id="mtd" rowspan="1073741825"/>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/463763-1.xhtml b/layout/mathml/crashtests/463763-1.xhtml
new file mode 100644
index 000000000..61df8ff0c
--- /dev/null
+++ b/layout/mathml/crashtests/463763-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<math xmlns="http://www.w3.org/1998/Math/MathML"
+ xmlns:h="http://www.w3.org/1999/xhtml">
+<msup style="display:block"><mo><h:frameset/></mo></msup>
+<msub style="display:block"><mo><h:frameset/></mo></msub>
+<msubsup style="display:block"><mo><h:frameset/></mo></msubsup>
+</math>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/463763-2.xhtml b/layout/mathml/crashtests/463763-2.xhtml
new file mode 100644
index 000000000..8d41abade
--- /dev/null
+++ b/layout/mathml/crashtests/463763-2.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<math xmlns="http://www.w3.org/1998/Math/MathML"
+ xmlns:h="http://www.w3.org/1999/xhtml">
+<mstyle displaystyle="false">
+<munder style="display:block"><mo movablelimits="true"><h:frameset/></mo></munder>
+<mover style="display:block"><mo movablelimits="true"><h:frameset/></mo></mover>
+<munderover style="display:block"><mo movablelimits="true"><h:frameset/></mo></munderover>
+</mstyle>
+</math>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/476547-1.xhtml b/layout/mathml/crashtests/476547-1.xhtml
new file mode 100644
index 000000000..0cece35ea
--- /dev/null
+++ b/layout/mathml/crashtests/476547-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="quotes: '&lt;1&gt;' '';">
+<body onload="document.getElementById('ms').setAttribute('lquote', '');" style="direction: rtl;">
+<span><ms id="ms" xmlns="http://www.w3.org/1998/Math/MathML"><mstyle/></ms></span>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/477740-1.xhtml b/layout/mathml/crashtests/477740-1.xhtml
new file mode 100644
index 000000000..b3e9c133c
--- /dev/null
+++ b/layout/mathml/crashtests/477740-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xbl="http://www.mozilla.org/xbl" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+
+<xbl:bindings><xbl:binding id="res"><xbl:content><xul:resizer><xbl:children/></xul:resizer></xbl:content></xbl:binding></xbl:bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var x = document.getElementById("x");
+
+ var r = document.createRange();
+ r.setEnd(x, 1);
+ r.extractContents();
+
+ x.appendChild(document.createElement('span'));
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<xul:rows id="x" style="-moz-binding: url(#res);"><math:ms/></xul:rows>
+</body>
+</html>
diff --git a/layout/mathml/crashtests/541620-1.xhtml b/layout/mathml/crashtests/541620-1.xhtml
new file mode 100644
index 000000000..ccef762e4
--- /dev/null
+++ b/layout/mathml/crashtests/541620-1.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<body>
+<m:ms><m:mfenced/></m:ms>
+</body>
+</html>
+
diff --git a/layout/mathml/crashtests/557474-1.html b/layout/mathml/crashtests/557474-1.html
new file mode 100644
index 000000000..1bf8d534c
--- /dev/null
+++ b/layout/mathml/crashtests/557474-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mpadded</title>
+ </head>
+ <body>
+ <math>
+ <mpadded width="-100px" height="-100px" depth="-100px"></mpadded>
+ </math>
+ </body>
+</html>
diff --git a/layout/mathml/crashtests/654928-1.html b/layout/mathml/crashtests/654928-1.html
new file mode 100644
index 000000000..da52f34b4
--- /dev/null
+++ b/layout/mathml/crashtests/654928-1.html
@@ -0,0 +1,3 @@
+<html>
+ <math><maction selection="2">A<mo>B</mo></maction></math>
+</html>
diff --git a/layout/mathml/crashtests/655451-1.xhtml b/layout/mathml/crashtests/655451-1.xhtml
new file mode 100644
index 000000000..619a1c271
--- /dev/null
+++ b/layout/mathml/crashtests/655451-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.style.fontStyle = "oblique";
+ var c = document.getElementById("c");
+ c.parentNode.removeChild(c);
+}
+
+</script>
+</head>
+<body onload="boom();"><math xmlns="http://www.w3.org/1998/Math/MathML"><frameset xmlns="http://www.w3.org/1999/xhtml"></frameset><msubsup id="c"/><mo><frameset xmlns="http://www.w3.org/1999/xhtml"></frameset></mo></math>x</body>
+</html>
diff --git a/layout/mathml/crashtests/713606-1.html b/layout/mathml/crashtests/713606-1.html
new file mode 100644
index 000000000..a0d4939a8
--- /dev/null
+++ b/layout/mathml/crashtests/713606-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Crashtest bug 713606</title>
+ </head>
+ <body onload="document.querySelector('mstyle').setAttribute('scriptminsize', '0%');">
+ <math>
+ <msubsup>
+ <mtext>X</mtext>
+ <mtable displaystyle="true"></mstyle>
+ </mtext>Y</mtext>
+ </msubsup>
+ </math>
+ <math>
+ <msubsup>
+ <mtext>X</mtext>
+ <mstyle displaystyle="true"></mstyle>
+ </mtext>Y</mtext>
+ </msubsup>
+ </math>
+ <math><mfrac><mstyle displaystyle="true"></mstyle></mfrac></math>
+ </body>
+</html>
+
diff --git a/layout/mathml/crashtests/716349-1.html b/layout/mathml/crashtests/716349-1.html
new file mode 100644
index 000000000..9b5895ad6
--- /dev/null
+++ b/layout/mathml/crashtests/716349-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Crashtest bug 716349</title>
+ </head>
+ <body>
+ <math>
+ <mspace width="-10px"/>
+ <mspace height="-10px"/>
+ </math>
+ </body>
+</html>
+
diff --git a/layout/mathml/crashtests/848725-1.html b/layout/mathml/crashtests/848725-1.html
new file mode 100644
index 000000000..7ce9c434c
--- /dev/null
+++ b/layout/mathml/crashtests/848725-1.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Bug 848725</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <div>
+ <math>
+ <mover>
+ <mo>&#x21A0;</mo>
+ <mspace width="500px"></mspace>
+ </mover>
+ </math>
+ </div>
+ </body>
+</html>
diff --git a/layout/mathml/crashtests/848725-2.html b/layout/mathml/crashtests/848725-2.html
new file mode 100644
index 000000000..b6362f9bf
--- /dev/null
+++ b/layout/mathml/crashtests/848725-2.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Bug 848725</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <div>
+ <math>
+ <mover>
+ <mo>&#x21A1;</mo>
+ <mspace height="500px"></mspace>
+ </mover>
+ </math>
+ </div>
+ </body>
+</html>
diff --git a/layout/mathml/crashtests/947557-1.html b/layout/mathml/crashtests/947557-1.html
new file mode 100644
index 000000000..3ddfb7b98
--- /dev/null
+++ b/layout/mathml/crashtests/947557-1.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<body>
+ <math>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mprescripts/>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mprescripts/>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mprescripts/>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </math>
+</body>
diff --git a/layout/mathml/crashtests/973322-1.xhtml b/layout/mathml/crashtests/973322-1.xhtml
new file mode 100644
index 000000000..dfac127fb
--- /dev/null
+++ b/layout/mathml/crashtests/973322-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body onload="document.getElementById('f').setAttribute('class', 'e');">
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mfenced id="f"/>
+ </math>
+ <p style="overflow: scroll; position: sticky;"></p>
+ </body>
+</html>
diff --git a/layout/mathml/crashtests/crashtests.list b/layout/mathml/crashtests/crashtests.list
new file mode 100644
index 000000000..6322f9eaa
--- /dev/null
+++ b/layout/mathml/crashtests/crashtests.list
@@ -0,0 +1,66 @@
+load 151054-1.xml
+load 289180-1.xml
+load 307826-1.xhtml
+load 307839-1.xhtml
+load 307839-2.xhtml
+load 323733-1.xml
+load 323737-1.xml
+load 323738-1.xml
+load 323741-1.xml
+load 323742-1.xml
+load 336074-1.xhtml
+load 347355-1.html
+load 347495-1.xhtml
+load 347507-1.xhtml
+load 348492-1.xhtml
+load 348709-1.xhtml
+load 348811-1.xhtml
+load 348811-2.xhtml
+load 353612-1.xhtml
+load 355986-1.xhtml
+load 364685-1.xhtml
+load 366012-1.xhtml
+load 366564-1.xhtml
+load 367107-1.html
+load 368430-1.xhtml
+load 370791-1.xhtml
+load 370862-1.xhtml
+load 372483-1.xhtml
+load 373472-1.xhtml
+load 373472-2.xhtml
+load 375562-1.xhtml
+load 377824-1.xhtml
+load 379418-1.xhtml
+load 385226-1.xhtml
+load 393760-1.xhtml
+load 397518-1.xhtml
+load 398038-1.html
+load 400157.xhtml
+load 400475-1.xhtml
+load 402400-1.xhtml
+load 403156-1.xhtml
+load 404485-1.xhtml
+load 405187-1.xhtml
+load 405271-1.xml
+load 412237-1.xml
+load 413063-1.xhtml
+load 416907-1.xhtml
+load 420420-1.xhtml
+load 431072-1.xhtml
+load 443089-1.xhtml
+load 463763-1.xhtml
+load 463763-2.xhtml
+load 476547-1.xhtml
+load 477740-1.xhtml
+load 541620-1.xhtml
+load 557474-1.html
+load 654928-1.html
+load 655451-1.xhtml
+load 713606-1.html
+load 716349-1.html
+load 848725-1.html
+load 848725-2.html
+load 947557-1.html
+load 973322-1.xhtml
+load 1028521-1.xhtml
+load 1061027.html
diff --git a/layout/mathml/imptests/LICENSE b/layout/mathml/imptests/LICENSE
new file mode 100644
index 000000000..4047b5439
--- /dev/null
+++ b/layout/mathml/imptests/LICENSE
@@ -0,0 +1,32 @@
+Tests in this directory have been imported from
+https://github.com/MathML/MathMLinHTML5-tests
+
+
+Copyright (c) 2016 MathML Association
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/layout/mathml/imptests/fonts/fraction-axisheight7000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-axisheight7000-rulethickness1000.woff
new file mode 100644
index 000000000..0f2ae3bc1
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-axisheight7000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff
new file mode 100644
index 000000000..03e9b3688
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woff
new file mode 100644
index 000000000..df6aab016
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-denominatorgapmin4000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-denominatorgapmin4000-rulethickness1000.woff
new file mode 100644
index 000000000..c5ba7a6a7
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-denominatorgapmin4000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-denominatorshiftdown3000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-denominatorshiftdown3000-rulethickness1000.woff
new file mode 100644
index 000000000..2c2b3d81b
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-denominatorshiftdown3000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff
new file mode 100644
index 000000000..a899cc9d2
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woff
new file mode 100644
index 000000000..d3fa259f9
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-numeratorgapmin9000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-numeratorgapmin9000-rulethickness1000.woff
new file mode 100644
index 000000000..cceffbb5f
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-numeratorgapmin9000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-numeratorshiftup11000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-numeratorshiftup11000-rulethickness1000.woff
new file mode 100644
index 000000000..20bd5f7d5
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-numeratorshiftup11000-rulethickness1000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/fraction-rulethickness10000.woff b/layout/mathml/imptests/fonts/fraction-rulethickness10000.woff
new file mode 100644
index 000000000..8ac982834
--- /dev/null
+++ b/layout/mathml/imptests/fonts/fraction-rulethickness10000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/stack-axisheight7000.woff b/layout/mathml/imptests/fonts/stack-axisheight7000.woff
new file mode 100644
index 000000000..fa2e6f3aa
--- /dev/null
+++ b/layout/mathml/imptests/fonts/stack-axisheight7000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/stack-bottomdisplaystyleshiftdown5000.woff b/layout/mathml/imptests/fonts/stack-bottomdisplaystyleshiftdown5000.woff
new file mode 100644
index 000000000..2addfc4b2
--- /dev/null
+++ b/layout/mathml/imptests/fonts/stack-bottomdisplaystyleshiftdown5000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/stack-bottomshiftdown6000.woff b/layout/mathml/imptests/fonts/stack-bottomshiftdown6000.woff
new file mode 100644
index 000000000..246154e2e
--- /dev/null
+++ b/layout/mathml/imptests/fonts/stack-bottomshiftdown6000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/stack-displaystylegapmin4000.woff b/layout/mathml/imptests/fonts/stack-displaystylegapmin4000.woff
new file mode 100644
index 000000000..02dd51531
--- /dev/null
+++ b/layout/mathml/imptests/fonts/stack-displaystylegapmin4000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/stack-gapmin8000.woff b/layout/mathml/imptests/fonts/stack-gapmin8000.woff
new file mode 100644
index 000000000..173907405
--- /dev/null
+++ b/layout/mathml/imptests/fonts/stack-gapmin8000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/stack-topdisplaystyleshiftup3000.woff b/layout/mathml/imptests/fonts/stack-topdisplaystyleshiftup3000.woff
new file mode 100644
index 000000000..c8db7da1f
--- /dev/null
+++ b/layout/mathml/imptests/fonts/stack-topdisplaystyleshiftup3000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/stack-topshiftup9000.woff b/layout/mathml/imptests/fonts/stack-topshiftup9000.woff
new file mode 100644
index 000000000..858790659
--- /dev/null
+++ b/layout/mathml/imptests/fonts/stack-topshiftup9000.woff
Binary files differ
diff --git a/layout/mathml/imptests/fonts/xheight500.woff b/layout/mathml/imptests/fonts/xheight500.woff
new file mode 100644
index 000000000..fe9f88893
--- /dev/null
+++ b/layout/mathml/imptests/fonts/xheight500.woff
Binary files differ
diff --git a/layout/mathml/imptests/mochitest.ini b/layout/mathml/imptests/mochitest.ini
new file mode 100644
index 000000000..ad92b4993
--- /dev/null
+++ b/layout/mathml/imptests/mochitest.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+
+[test_fraction-parameters.html]
+support-files =
+ fonts/fraction-axisheight7000-rulethickness1000.woff
+ fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff
+ fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woff
+ fonts/fraction-denominatorgapmin4000-rulethickness1000.woff
+ fonts/fraction-denominatorshiftdown3000-rulethickness1000.woff
+ fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff
+ fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woff
+ fonts/fraction-numeratorgapmin9000-rulethickness1000.woff
+ fonts/fraction-numeratorshiftup11000-rulethickness1000.woff
+ fonts/fraction-rulethickness10000.woff
+[test_lengths-3.html]
+support-files =
+ fonts/xheight500.woff
+[test_stack-parameters.html]
+support-files =
+ fonts/stack-axisheight7000.woff
+ fonts/stack-bottomdisplaystyleshiftdown5000.woff
+ fonts/stack-bottomshiftdown6000.woff
+ fonts/stack-displaystylegapmin4000.woff
+ fonts/stack-gapmin8000.woff
+ fonts/stack-topdisplaystyleshiftup3000.woff
+ fonts/stack-topshiftup9000.woff
diff --git a/layout/mathml/imptests/test_fraction-parameters.html b/layout/mathml/imptests/test_fraction-parameters.html
new file mode 100644
index 000000000..d067d0184
--- /dev/null
+++ b/layout/mathml/imptests/test_fraction-parameters.html
@@ -0,0 +1,244 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Fraction parameters</title>
+<link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2">
+<meta name="assert" content="Element mfrac correctly uses the fraction parameters from the MATH table.">
+<!-- Copyright 2016 MathML Association
+ Licensed under 3-Clause BSD-License -->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ math, mspace {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: axisheight7000-rulethickness1000;
+ src: url("fonts/fraction-axisheight7000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: denominatordisplaystylegapmin5000-rulethickness1000;
+ src: url("fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: denominatordisplaystyleshiftdown6000-rulethickness1000;
+ src: url("fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: denominatorgapmin4000-rulethickness1000;
+ src: url("fonts/fraction-denominatorgapmin4000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: denominatorshiftdown3000-rulethickness1000;
+ src: url("fonts/fraction-denominatorshiftdown3000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: numeratordisplaystylegapmin8000-rulethickness1000;
+ src: url("fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: numeratordisplaystyleshiftup2000-rulethickness1000;
+ src: url("fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: numeratorgapmin9000-rulethickness1000;
+ src: url("fonts/fraction-numeratorgapmin9000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: numeratorshiftup11000-rulethickness1000;
+ src: url("fonts/fraction-numeratorshiftup11000-rulethickness1000.woff");
+ }
+ @font-face {
+ font-family: rulethickness10000;
+ src: url("fonts/fraction-rulethickness10000.woff");
+ }
+</style>
+<script>
+ var emToPx = 10 / 1000; // font-size: 10px, font.em = 1000
+ var epsilon = 1;
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ var test_loaded = async_test("Page Loaded");
+ window.addEventListener("load", function() {
+ // FIXME: Use an API to wait for the Web fonts to arrive.
+ window.setTimeout(runTests, 250);
+ });
+
+ function runTests() {
+ test(function() {
+ var v1 = 7000 * emToPx;
+ var v2 = 1000 * emToPx;
+ assert_approx_equals(getBox("ref0001").top - getBox("num0001").bottom,
+ v1 + v2 / 2, epsilon, "mfrac: axis height");
+ }, "AxisHeight");
+
+ test(function() {
+ var v1 = 5000 * emToPx;
+ assert_approx_equals(getBox("den0002").top - getBox("ref0002").bottom,
+ v1, epsilon, "mfrac: denominator gap");
+ }, "DenominatorDisplayStyleGapMin");
+
+ test(function() {
+ var v1 = 6000 * emToPx;
+ assert_approx_equals(getBox("den0003").top - getBox("ref0003").bottom,
+ v1, epsilon, "mfrac: denominator shift");
+ }, "DenominatorDisplayStyleShiftDown");
+
+ test(function() {
+ var v1 = 4000 * emToPx;
+ assert_approx_equals(getBox("den0004").top - getBox("ref0004").bottom,
+ v1, epsilon, "mfrac: denominator gap");
+ }, "DenominatorGapMin");
+
+ test(function() {
+ var v1 = 3000 * emToPx;
+ assert_approx_equals(getBox("den0005").top - getBox("ref0005").bottom,
+ v1, epsilon, "mfrac: denominator shift");
+ }, "DenominatorShiftDown");
+
+ test(function() {
+ var v1 = 8000 * emToPx;
+ assert_approx_equals(getBox("ref0006").top - getBox("num0006").bottom,
+ v1, epsilon, "mfrac: numerator gap");
+ }, "NumeratorDisplayStyleGapMin");
+
+ test(function() {
+ var v1 = 2000 * emToPx;
+ assert_approx_equals(getBox("ref0007").top - getBox("num0007").bottom,
+ v1, epsilon, "mfrac: numerator shift");
+ }, "NumeratorDisplayStyleShiftDown");
+
+ test(function() {
+ var v1 = 9000 * emToPx;
+ assert_approx_equals(getBox("ref0008").top - getBox("num0008").bottom,
+ v1, epsilon, "mfrac: numerator gap");
+ }, "NumeratorGapMin");
+
+ test(function() {
+ var v1 = 11000 * emToPx;
+ assert_approx_equals(getBox("ref0009").top - getBox("num0009").bottom,
+ v1, epsilon, "mfrac: numerator shift");
+ }, "NumeratorShiftDown");
+
+ test(function() {
+ var v1 = 10000 * emToPx;
+ assert_approx_equals(getBox("den0010").top - getBox("num0010").bottom,
+ v1, epsilon, "mfrac: rule thickness");
+ }, "FractionRuleThickness");
+
+ test_loaded.done();
+ }
+</script>
+</head>
+<body>
+ <p>
+ <math style="font-family: axisheight7000-rulethickness1000;">
+ <mspace id="ref0001" depth="1em" width="3em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em" height="1em" id="num0001" mathbackground="blue"/>
+ <mspace width="3em"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math display="block" style="font-family: denominatordisplaystylegapmin5000-rulethickness1000;">
+ <mspace id="ref0002" width="3em"
+ height=".5em" depth=".5em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em"/>
+ <mspace width="3em" height="1em" id="den0002" mathbackground="blue"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math display="block" style="font-family: denominatordisplaystyleshiftdown6000-rulethickness1000;">
+ <mspace id="ref0003" width="3em" height="1em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em"/>
+ <mspace width="3em" depth="1em" id="den0003" mathbackground="blue"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math style="font-family: denominatorgapmin4000-rulethickness1000;">
+ <mspace id="ref0004" width="3em"
+ height=".5em" depth=".5em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em"/>
+ <mspace width="3em" height="1em" id="den0004" mathbackground="blue"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math style="font-family: denominatorshiftdown3000-rulethickness1000;">
+ <mspace id="ref0005" width="3em" height="1em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em"/>
+ <mspace width="3em" depth="1em" id="den0005" mathbackground="blue"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math display="block" style="font-family: numeratordisplaystylegapmin8000-rulethickness1000;">
+ <mspace id="ref0006" width="3em"
+ height=".5em" depth=".5em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em" depth="1em" id="num0006" mathbackground="blue"/>
+ <mspace width="3em"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math display="block" style="font-family: numeratordisplaystyleshiftup2000-rulethickness1000;">
+ <mspace id="ref0007" width="3em"
+ depth="1em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em" height="1em" id="num0007" mathbackground="blue"/>
+ <mspace width="3em"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math style="font-family: numeratorgapmin9000-rulethickness1000;">
+ <mspace id="ref0008" width="3em"
+ height=".5em" depth=".5em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em" depth="1em" id="num0008" mathbackground="blue"/>
+ <mspace width="3em"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math style="font-family: numeratorshiftup11000-rulethickness1000;">
+ <mspace id="ref0009" width="3em"
+ depth="1em" mathbackground="green"/>
+ <mfrac>
+ <mspace width="3em" height="1em" id="num0009" mathbackground="blue"/>
+ <mspace width="3em"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math style="font-family: rulethickness10000">
+ <mfrac>
+ <mspace width="3em" height="1em" id="num0010" mathbackground="blue"/>
+ <mspace width="3em" depth="1em" id="den0010" mathbackground="green"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+</body>
+</html>
diff --git a/layout/mathml/imptests/test_lengths-3.html b/layout/mathml/imptests/test_lengths-3.html
new file mode 100644
index 000000000..03a249a92
--- /dev/null
+++ b/layout/mathml/imptests/test_lengths-3.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>MathML lengths</title>
+<link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S2.html#SS3.SSS1"/>
+<meta name="assert" content="Verify various cases of the MathML length syntax.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("fonts/xheight500.woff");
+ }
+ math {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<script>
+ var epsilon = 2;
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ setup({ explicit_done: true });
+ window.addEventListener("load", function() {
+ document.fonts.ready.then(runTests);
+ });
+
+ function runTests() {
+ test(function() {
+ assert_approx_equals(getBox("unitCm").width, 96, epsilon, "cm");
+ assert_approx_equals(getBox("unitEm").width, 120, epsilon, "em");
+ assert_approx_equals(getBox("unitEx").width, 500, epsilon, "ex");
+ assert_approx_equals(getBox("unitIn").width, 288, epsilon, "in");
+ assert_approx_equals(getBox("unitNamed").width, 389, epsilon, "namedspace");
+ assert_approx_equals(getBox("unitMm").width, 576, epsilon, "mm");
+ assert_approx_equals(getBox("unitPc").width, 96, epsilon, "pc");
+ assert_approx_equals(getBox("unitPercentage").width, 60, epsilon, "%");
+ assert_approx_equals(getBox("unitPt").width, 96, epsilon, "pt");
+ assert_approx_equals(getBox("unitPx").width, 123, epsilon, "px");
+ assert_approx_equals(getBox("unitNone").width, 150, epsilon, "Unitless");
+ }, "Units");
+
+ test(function() {
+ assert_approx_equals(getBox("spaceCm").width, 96, epsilon, "cm");
+ assert_approx_equals(getBox("spaceEm").width, 120, epsilon, "em");
+ assert_approx_equals(getBox("spaceEx").width, 500, epsilon, "ex");
+ assert_approx_equals(getBox("spaceIn").width, 288, epsilon, "in");
+ assert_approx_equals(getBox("spaceNamed").width, 389, epsilon, "namedspace");
+ assert_approx_equals(getBox("spaceMm").width, 576, epsilon, "mm");
+ assert_approx_equals(getBox("spacePc").width, 96, epsilon, "pc");
+ assert_approx_equals(getBox("spacePercentage").width, 60, epsilon, "%");
+ assert_approx_equals(getBox("spacePt").width, 96, epsilon, "pt");
+ assert_approx_equals(getBox("spacePx").width, 123, epsilon, "px");
+ assert_approx_equals(getBox("spaceNone").width, 150, epsilon, "Unitless");
+ }, "Trimming of space");
+
+ test(function() {
+ assert_approx_equals(getBox("n0").width, 0, epsilon, "n0");
+ assert_approx_equals(getBox("n1").width, 90, epsilon, "n1");
+ assert_approx_equals(getBox("n2").width, 8, epsilon, "n2");
+ assert_approx_equals(getBox("n3").width, 70, epsilon, "n3");
+ assert_approx_equals(getBox("n4").width, 650, epsilon, "n4");
+ assert_approx_equals(getBox("n5").width, 4320, epsilon, "n5");
+ assert_approx_equals(getBox("n6").width, 1, epsilon, "n6");
+ assert_approx_equals(getBox("n7").width, 8, epsilon, "n7");
+ assert_approx_equals(getBox("n8").width, 65, epsilon, "n8");
+ assert_approx_equals(getBox("n9").width, 432, epsilon, "n9");
+ assert_approx_equals(getBox("n10").width, 123, epsilon, "n10");
+ }, "Non-negative numbers");
+
+ test(function() {
+ var topRef = getBox("ref").top;
+ assert_approx_equals(getBox("N0").top - topRef, -0, epsilon, "N0");
+ assert_approx_equals(topRef - getBox("N1").top, -90, epsilon, "N1");
+ assert_approx_equals(topRef - getBox("N2").top, -8, epsilon, "N2");
+ assert_approx_equals(topRef - getBox("N3").top, -70, epsilon, "N3");
+ assert_approx_equals(topRef - getBox("N4").top, -650, epsilon, "N4");
+ assert_approx_equals(topRef - getBox("N5").top, -4320, epsilon, "N5");
+ assert_approx_equals(topRef - getBox("N6").top, -1, epsilon, "N6");
+ assert_approx_equals(topRef - getBox("N7").top, -8, epsilon, "N7");
+ assert_approx_equals(topRef - getBox("N8").top, -65, epsilon, "N8");
+ assert_approx_equals(topRef - getBox("N9").top, -432, epsilon, "N9");
+ assert_approx_equals(topRef - getBox("N10").top, -123, epsilon, "N10");
+ }, "Non-positive numbers");
+
+ done();
+ }
+</script>
+</head>
+<body>
+ <p>
+ <math>
+ <mspace id="unitCm" width="2.54cm"/>
+ <mspace id="unitEm" width="12em"/>
+ <mspace id="unitEx" width="100ex"/>
+ <mspace id="unitIn" width="3in"/>
+ <mspace style="font-size: 1000px" id="unitNamed" width="veryverythickmathspace"/>
+ <mspace id="unitMm" width="152.4mm"/>
+ <mspace id="unitPc" width="6pc"/>
+ <mstyle mathsize="200%"><mspace id="unitPercentage" width="3em"/></mstyle>
+ <mspace id="unitPt" width="72pt"/>
+ <mspace id="unitPx" width="123px"/>
+ <mstyle mathsize="5"><mspace id="unitNone" width="3em"/></mstyle>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mspace id="spaceCm" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;2.54cm&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mspace id="spaceEm" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;12em&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mspace id="spaceEx" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;100ex&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mspace id="spaceIn" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;3in&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mspace style="font-size: 1000px" id="spaceNamed" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;veryverythickmathspace&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mspace id="spaceMm" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;152.4mm&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mspace id="spacePc" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;6pc&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mstyle mathsize="200%"><mspace id="spacePercentage" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;3em&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/></mstyle>
+ <mspace id="spacePt" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;72pt&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mspace id="spacePx" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;123px&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/>
+ <mstyle mathsize="5"><mspace id="spaceNone" width="&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;3em&#x20;&#x9;&#xA;&#xD;&#x20;&#x9;&#xA;&#xD;"/></mstyle>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mspace id="n0" width="0em"/>
+ <mspace id="n1" width="9em"/>
+ <mspace id="n2" width=".8em"/>
+ <mspace id="n3" width="7.em"/>
+ <mspace id="n4" width="65em"/>
+ <mspace id="n5" width="432em"/>
+ <mspace id="n6" width=".10em"/>
+ <mspace id="n7" width=".789em"/>
+ <mspace id="n8" width="6.5em"/>
+ <mspace id="n9" width="43.21em"/>
+ <mspace id="n10" width="012.345em"/>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mspace id="ref"></mspace>
+ <mpadded voffset="-0em"><mspace id="N0"/></mpadded>
+ <mpadded voffset="-9em"><mspace id="N1"/></mpadded>
+ <mpadded voffset="-.8em"><mspace id="N2"/></mpadded>
+ <mpadded voffset="-7.em"><mspace id="N3"/></mpadded>
+ <mpadded voffset="-65em"><mspace id="N4"/></mpadded>
+ <mpadded voffset="-432em"><mspace id="N5"/></mpadded>
+ <mpadded voffset="-.10em"><mspace id="N6"/></mpadded>
+ <mpadded voffset="-.789em"><mspace id="N7"/></mpadded>
+ <mpadded voffset="-6.5em"><mspace id="N8"/></mpadded>
+ <mpadded voffset="-43.21em"><mspace id="N9"/></mpadded>
+ <mpadded voffset="-012.345em"><mspace id="N10"/></mpadded>
+ </math>
+ </p>
+ <hr/>
+</body>
+</html>
diff --git a/layout/mathml/imptests/test_stack-parameters.html b/layout/mathml/imptests/test_stack-parameters.html
new file mode 100644
index 000000000..784e7dfe8
--- /dev/null
+++ b/layout/mathml/imptests/test_stack-parameters.html
@@ -0,0 +1,176 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Stack parameters</title>
+<link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2">
+<meta name="assert" content="Element mfrac correctly uses the stack parameters from the MATH table.">
+<!-- Copyright 2016 MathML Association
+ Licensed under 3-Clause BSD-License -->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ math, mspace {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: axisheight7000;
+ src: url("fonts/stack-axisheight7000.woff");
+ }
+ @font-face {
+ font-family: bottomdisplaystyleshiftdown5000;
+ src: url("fonts/stack-bottomdisplaystyleshiftdown5000.woff");
+ }
+ @font-face {
+ font-family: bottomshiftdown6000;
+ src: url("fonts/stack-bottomshiftdown6000.woff");
+ }
+ @font-face {
+ font-family: displaystylegapmin4000;
+ src: url("fonts/stack-displaystylegapmin4000.woff");
+ }
+ @font-face {
+ font-family: gapmin8000;
+ src: url("fonts/stack-gapmin8000.woff");
+ }
+ @font-face {
+ font-family: topdisplaystyleshiftup3000;
+ src: url("fonts/stack-topdisplaystyleshiftup3000.woff");
+ }
+ @font-face {
+ font-family: topshiftup9000;
+ src: url("fonts/stack-topshiftup9000.woff");
+ }
+</style>
+<script>
+ var emToPx = 10 / 1000; // font-size: 10px, font.em = 1000
+ var epsilon = 1;
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ var test_loaded = async_test("Page Loaded");
+ window.addEventListener("load", function() {
+ // FIXME: Use an API to wait for the Web fonts to arrive.
+ window.setTimeout(runTests, 250);
+ });
+
+ function runTests() {
+ test(function() {
+ var v = 7000 * emToPx;
+ assert_approx_equals(getBox("ref0001").top - getBox("num0001").bottom,
+ v, epsilon, "mfrac: axis height");
+ }, "AxisHeight");
+
+ test(function() {
+ var v = 5000 * emToPx;
+ assert_approx_equals(getBox("den0002").top - getBox("ref0002").bottom,
+ v, epsilon, "mfrac: denominator shift");
+ }, "BottomDisplayStyleShiftDown");
+
+ test(function() {
+ var v = 6000 * emToPx;
+ assert_approx_equals(getBox("den0003").top - getBox("ref0003").bottom,
+ v, epsilon, "mfrac: denominator shift");
+ }, "BottomShiftDown");
+
+ test(function() {
+ var v = 4000 * emToPx;
+ assert_approx_equals(getBox("den0004").top - getBox("num0004").bottom,
+ v, epsilon, "mfrac: gap");
+ }, "DisplayStyleGapMin");
+
+ test(function() {
+ var v = 8000 * emToPx;
+ assert_approx_equals(getBox("den0005").top - getBox("num0005").bottom,
+ v, epsilon, "mfrac: gap");
+ }, "GapMin");
+
+ test(function() {
+ var v = 3000 * emToPx;
+ assert_approx_equals(getBox("ref0006").top - getBox("num0006").bottom,
+ v, epsilon, "mfrac: numerator shift");
+ }, "TopDisplayStyleShiftUp");
+
+ test(function() {
+ var v = 9000 * emToPx;
+ assert_approx_equals(getBox("ref0007").top - getBox("num0007").bottom,
+ v, epsilon, "mfrac: numerator shift");
+ }, "ToShiftUp");
+
+ test_loaded.done();
+ }
+</script>
+</head>
+<body>
+ <p>
+ <math style="font-family: axisheight7000;">
+ <mspace id="ref0001" depth="1em" width="3em" mathbackground="green"/>
+ <mfrac linethickness="0px">
+ <mspace width="3em" height="1em" id="num0001" mathbackground="blue"/>
+ <mspace width="3em"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math display="block" style="font-family: bottomdisplaystyleshiftdown5000;">
+ <mspace id="ref0002" width="3em" height="1em" mathbackground="green"/>
+ <mfrac linethickness="0px">
+ <mspace width="3em"/>
+ <mspace width="3em" depth="1em" id="den0002" mathbackground="blue"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math style="font-family: bottomshiftdown6000;">
+ <mspace id="ref0003" width="3em" height="1em" mathbackground="green"/>
+ <mfrac linethickness="0px">
+ <mspace width="3em"/>
+ <mspace width="3em" depth="1em" id="den0003" mathbackground="blue"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math display="block" style="font-family: displaystylegapmin4000;">
+ <mfrac linethickness="0px">
+ <mspace width="3em" height="1em" id="num0004" mathbackground="blue"/>
+ <mspace width="3em" depth="1em" id="den0004" mathbackground="green"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math style="font-family: gapmin8000;">
+ <mfrac linethickness="0px">
+ <mspace width="3em" height="1em" id="num0005" mathbackground="blue"/>
+ <mspace width="3em" depth="1em" id="den0005" mathbackground="green"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math display="block" style="font-family: topdisplaystyleshiftup3000;">
+ <mspace id="ref0006" width="3em" depth="1em" mathbackground="green"/>
+ <mfrac linethickness="0px">
+ <mspace width="3em" height="1em" id="num0006" mathbackground="blue"/>
+ <mspace width="3em"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+ <p>
+ <math style="font-family: topshiftup9000;">
+ <mspace id="ref0007" width="3em" depth="1em" mathbackground="green"/>
+ <mfrac linethickness="0px">
+ <mspace width="3em" height="1em" id="num0007" mathbackground="blue"/>
+ <mspace width="3em"/>
+ </mfrac>
+ </math>
+ </p>
+ <hr/>
+</body>
+</html>
diff --git a/layout/mathml/jar.mn b/layout/mathml/jar.mn
new file mode 100644
index 000000000..d3aeb44e9
--- /dev/null
+++ b/layout/mathml/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+ res/mathml.css (mathml.css)
diff --git a/layout/mathml/mathfont.properties b/layout/mathml/mathfont.properties
new file mode 100644
index 000000000..eb958a6cc
--- /dev/null
+++ b/layout/mathml/mathfont.properties
@@ -0,0 +1,1164 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE: FILE
+# Do not translate anything in this file
+
+# The ordered list of fonts with which to attempt to stretch MathML
+# characters is controlled by setting pref("font.mathfont-family",
+# "CMSY10, CMEX10, ...") for example, or by setting the font-family list in
+# :-moz-math-stretchy in mathml.css.
+#
+# Note: setting base fonts for non-stretchy characters only works
+# for operators that are ultimately handled by nsMathMLChar.
+# @see how |useMathMLChar| is set in nsMathMLmoFrame::Stretch() & Paint().
+
+# Operator Dictionary indexed on the "form" (i.e., infix, prefix, or suffix).
+# Each entry lists the attributes of the operator, using its Unicode format.
+
+operator.\u0021.postfix = lspace:1 rspace:0 # !
+operator.\u0021\u0021.postfix = lspace:1 rspace:0 # !!
+operator.\u0021\u003D.infix = lspace:4 rspace:4 # !=
+operator.\u0025.infix = lspace:3 rspace:3 # percent sign
+operator.\u0026.postfix = lspace:0 rspace:0 # &amp;
+operator.\u0026\u0026.infix = lspace:4 rspace:4 # &amp;&amp;
+operator.\u0027.postfix = lspace:0 rspace:0 accent # '
+operator.\u0028.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # (
+operator.\u0029.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # )
+operator.\u002A.infix = lspace:3 rspace:3 # *
+operator.\u002A\u002A.infix = lspace:1 rspace:1 # **
+operator.\u002A\u003D.infix = lspace:4 rspace:4 # *=
+operator.\u002B.infix = lspace:4 rspace:4 # +
+operator.\u002B.prefix = lspace:0 rspace:1 # +
+operator.\u002B\u002B.postfix = lspace:0 rspace:0 # ++
+operator.\u002B\u003D.infix = lspace:4 rspace:4 # +=
+operator.\u002C.infix = lspace:0 rspace:3 separator # ,
+operator.\u002D.infix = lspace:4 rspace:4 # -
+operator.\u002D.prefix = lspace:0 rspace:1 # -
+operator.\u002D\u002D.postfix = lspace:0 rspace:0 # --
+operator.\u002D\u003D.infix = lspace:4 rspace:4 # -=
+operator.\u002D\u003E.infix = lspace:5 rspace:5 # ->
+operator.\u002E.infix = lspace:3 rspace:3 # .
+operator.\u002E\u002E.postfix = lspace:0 rspace:0 # ..
+operator.\u002E\u002E\u002E.postfix = lspace:0 rspace:0 # ...
+operator.\u002F.infix = lspace:1 rspace:1 direction:vertical # solidus
+operator.\u002F\u002F.infix = lspace:1 rspace:1 # //
+operator.\u002F\u003D.infix = lspace:4 rspace:4 # /=
+operator.\u003A.infix = lspace:1 rspace:2 # :
+operator.\u003A\u003D.infix = lspace:4 rspace:4 # :=
+operator.\u003B.infix = lspace:0 rspace:3 separator # ;
+operator.\u003C.infix = lspace:5 rspace:5 # &lt;
+operator.\u003C\u003D.infix = lspace:5 rspace:5 # &lt;=
+operator.\u003C\u003E.infix = lspace:1 rspace:1 # &lt;>
+operator.\u003D.infix = lspace:5 rspace:5 direction:horizontal # =
+operator.\u003D\u003D.infix = lspace:4 rspace:4 # ==
+operator.\u003E.infix = lspace:5 rspace:5 # >
+operator.\u003E\u003D.infix = lspace:5 rspace:5 # >=
+operator.\u003F.infix = lspace:1 rspace:1 # ?
+operator.\u0040.infix = lspace:1 rspace:1 # @
+operator.\u005B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # [
+operator.\u005C.infix = lspace:0 rspace:0 # reverse solidus
+operator.\u005D.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # ]
+operator.\u005E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &Hat; circumflex accent
+operator.\u005E.infix = lspace:1 rspace:1 direction:horizontal # ^
+operator.\u005F.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # _ low line
+operator.\u005F.infix = lspace:1 rspace:1 stretchy direction:horizontal # _ low line
+operator.\u0060.postfix = lspace:0 rspace:0 accent # &DiacriticalGrave;
+operator.\u007B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # {
+operator.\u007C.infix = lspace:2 rspace:2 stretchy fence symmetric direction:vertical # &VerticalLine; |
+operator.\u007C.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # |
+operator.\u007C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # |
+operator.\u007C\u007C.infix = lspace:2 rspace:2 stretchy fence symmetric direction:vertical # ||
+operator.\u007C\u007C.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # multiple character operator: ||
+operator.\u007C\u007C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # multiple character operator: ||
+operator.\u007C\u007C\u007C.infix = lspace:2 rspace:2 stretchy fence symmetric direction:vertical # multiple character operator: |||
+operator.\u007C\u007C\u007C.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # multiple character operator: |||
+operator.\u007C\u007C\u007C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # multiple character operator: |||
+operator.\u007D.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # }
+operator.\u007E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ~ tilde
+operator.\u00A8.postfix = lspace:0 rspace:0 accent # &DoubleDot;
+operator.\u00AC.prefix = lspace:2 rspace:1 # not sign
+operator.\u00AF.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBar;
+operator.\u00B0.postfix = lspace:0 rspace:0 # degree sign
+operator.\u00B1.infix = lspace:4 rspace:4 # &PlusMinus;
+operator.\u00B1.prefix = lspace:0 rspace:1 # &PlusMinus;
+operator.\u00B4.postfix = lspace:0 rspace:0 accent # &DiacriticalAcute;
+operator.\u00B7.infix = lspace:4 rspace:4 # &CenterDot;
+operator.\u00B8.postfix = lspace:0 rspace:0 accent # &Cedilla;
+operator.\u00D7.infix = lspace:4 rspace:4 # multiplication sign
+operator.\u00F7.infix = lspace:4 rspace:4 # division sign
+operator.\u02C6.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter circumflex accent
+operator.\u02C7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &Hacek; caron
+operator.\u02C9.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter macron
+operator.\u02CA.postfix = lspace:0 rspace:0 accent # modifier letter acute accent
+operator.\u02CB.postfix = lspace:0 rspace:0 accent # modifier letter grave accent
+operator.\u02CD.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter low macron
+operator.\u02D8.postfix = lspace:0 rspace:0 accent # &Breve;
+operator.\u02D9.postfix = lspace:0 rspace:0 accent # &DiacriticalDot;
+operator.\u02DA.postfix = lspace:0 rspace:0 accent # ring above
+operator.\u02DC.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &DiacriticalTilde; small tilde
+operator.\u02DD.postfix = lspace:0 rspace:0 accent # &DiacriticalDoubleAcute;
+operator.\u02F7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter low tilde
+operator.\u0302.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # combining circumflex accent
+operator.\u0311.postfix = lspace:0 rspace:0 accent # &DownBreve;
+operator.\u03F6.infix = lspace:5 rspace:5 # greek reversed lunate epsilon symbol
+operator.\u2016.prefix = lspace:0 rspace:0 stretchy fence direction:vertical # &Vert; &Verbar;
+operator.\u2016.postfix = lspace:0 rspace:0 stretchy fence direction:vertical # &Vert; &Verbar;
+operator.\u2018.prefix = lspace:0 rspace:0 fence mirrorable # &OpenCurlyQuote;
+operator.\u2019.postfix = lspace:0 rspace:0 fence mirrorable # &CloseCurlyQuote;
+operator.\u201C.prefix = lspace:0 rspace:0 fence mirrorable # &OpenCurlyDoubleQuote;
+operator.\u201D.postfix = lspace:0 rspace:0 fence mirrorable # &CloseCurlyDoubleQuote;
+operator.\u2022.infix = lspace:4 rspace:4 # bullet
+operator.\u2026.infix = lspace:0 rspace:0 # horizontal ellipsis
+operator.\u2032.postfix = lspace:0 rspace:2 # prime
+operator.\u203E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # overline
+operator.\u2044.infix = lspace:4 rspace:4 stretchy direction:vertical # fraction slash
+operator.\u2061.infix = lspace:0 rspace:0 # &ApplyFunction;
+operator.\u2062.infix = lspace:0 rspace:0 # &InvisibleTimes;
+operator.\u2063.infix = lspace:0 rspace:0 separator # &InvisibleComma;
+operator.\u2064.infix = lspace:0 rspace:0 # invisible plus
+operator.\u20DB.postfix = lspace:0 rspace:0 accent # &TripleDot;
+operator.\u20DC.postfix = lspace:0 rspace:0 accent # combining four dots above
+operator.\u2145.prefix = lspace:2 rspace:1 # &CapitalDifferentialD;
+operator.\u2146.prefix = lspace:2 rspace:0 # &DifferentialD;
+operator.\u2190.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrow;
+operator.\u2191.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrow;
+operator.\u2192.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrow;
+operator.\u2193.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrow;
+operator.\u2194.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftRightArrow;
+operator.\u2195.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpDownArrow;
+operator.\u2196.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpperLeftArrow;
+operator.\u2197.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpperRightArrow;
+operator.\u2198.infix = lspace:5 rspace:5 stretchy direction:horizontal # &LowerRightArrow;
+operator.\u2199.infix = lspace:5 rspace:5 stretchy direction:horizontal # &LowerLeftArrow;
+operator.\u219A.infix = lspace:5 rspace:5 accent # leftwards arrow with stroke
+operator.\u219B.infix = lspace:5 rspace:5 accent # rightwards arrow with stroke
+operator.\u219C.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards wave arrow
+operator.\u219D.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards wave arrow
+operator.\u219E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards two headed arrow
+operator.\u219F.infix = lspace:5 rspace:5 stretchy accent direction:vertical # upwards two headed arrow
+operator.\u21A0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards two headed arrow
+operator.\u21A1.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards two headed arrow
+operator.\u21A2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow with tail
+operator.\u21A3.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards arrow with tail
+operator.\u21A4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftTeeArrow;
+operator.\u21A5.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpTeeArrow;
+operator.\u21A6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightTeeArrow;
+operator.\u21A7.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownTeeArrow;
+operator.\u21A8.infix = lspace:5 rspace:5 stretchy direction:vertical # up down arrow with base
+operator.\u21A9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &hookleftarrow; &larrhk;
+operator.\u21AA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &hookrightarrow; &rarrhk;
+operator.\u21AB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow with loop
+operator.\u21AC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards arrow with loop
+operator.\u21AD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # left right wave arrow
+operator.\u21AE.infix = lspace:5 rspace:5 accent # left right arrow with stroke
+operator.\u21AF.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards zigzag arrow
+operator.\u21B0.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards arrow with tip leftwards
+operator.\u21B1.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards arrow with tip rightwards
+operator.\u21B2.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with tip leftwards
+operator.\u21B3.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with tip rightwards
+operator.\u21B4.infix = lspace:5 rspace:5 stretchy direction:horizontal # rightwards arrow with corner downwards
+operator.\u21B5.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with corner leftwards
+operator.\u21B6.infix = lspace:5 rspace:5 accent # anticlockwise top semicircle arrow
+operator.\u21B7.infix = lspace:5 rspace:5 accent # clockwise top semicircle arrow
+operator.\u21B8.infix = lspace:5 rspace:5 # north west arrow to long bar
+operator.\u21B9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow to bar over rightwards arrow to bar
+operator.\u21BA.infix = lspace:5 rspace:5 # anticlockwise open circle arrow
+operator.\u21BB.infix = lspace:5 rspace:5 # clockwise open circle arrow
+operator.\u21BC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftVector;
+operator.\u21BD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftVector;
+operator.\u21BE.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpVector;
+operator.\u21BF.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpVector;
+operator.\u21C0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightVector;
+operator.\u21C1.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownRightVector;
+operator.\u21C2.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownVector;
+operator.\u21C3.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownVector;
+operator.\u21C4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrowLeftArrow;
+operator.\u21C5.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrowDownArrow;
+operator.\u21C6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrowRightArrow;
+operator.\u21C7.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards paired arrows
+operator.\u21C8.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards paired arrows
+operator.\u21C9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards paired arrows
+operator.\u21CA.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards paired arrows
+operator.\u21CB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &ReverseEquilibrium;
+operator.\u21CC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &Equilibrium;
+operator.\u21CD.infix = lspace:5 rspace:5 accent # leftwards double arrow with stroke
+operator.\u21CE.infix = lspace:5 rspace:5 accent # left right double arrow with stroke
+operator.\u21CF.infix = lspace:5 rspace:5 accent # rightwards double arrow with stroke
+operator.\u21D0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLeftArrow;
+operator.\u21D1.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleUpArrow;
+operator.\u21D2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &Implies; &DoubleRightArrow;
+operator.\u21D3.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleDownArrow;
+operator.\u21D4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLeftRightArrow;
+operator.\u21D5.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleUpDownArrow;
+operator.\u21DA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards triple arrow
+operator.\u21DB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards triple arrow
+operator.\u21DC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards squiggle arrow
+operator.\u21DD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards squiggle arrow
+operator.\u21DE.infix = lspace:5 rspace:5 # upwards arrow with double stroke
+operator.\u21DF.infix = lspace:5 rspace:5 # downwards arrow with double stroke
+operator.\u21E0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards dashed arrow
+operator.\u21E1.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards dashed arrow
+operator.\u21E2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards dashed arrow
+operator.\u21E3.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards dashed arrow
+operator.\u21E4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrowBar;
+operator.\u21E5.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrowBar;
+operator.\u21E6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards white arrow
+operator.\u21E7.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow
+operator.\u21E8.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards white arrow
+operator.\u21E9.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards white arrow
+operator.\u21EA.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow from bar
+operator.\u21EB.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal
+operator.\u21EC.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal with horizontal bar
+operator.\u21ED.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal with vertical bar
+operator.\u21EE.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white double arrow
+operator.\u21EF.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white double arrow on pedestal
+operator.\u21F0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards white arrow from wall
+operator.\u21F1.infix = lspace:5 rspace:5 # north west arrow to corner
+operator.\u21F2.infix = lspace:5 rspace:5 # south east arrow to corner
+operator.\u21F3.infix = lspace:5 rspace:5 stretchy direction:vertical # up down white arrow
+operator.\u21F4.infix = lspace:5 rspace:5 accent # right arrow with small circle
+operator.\u21F5.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrowUpArrow;
+operator.\u21F6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # three rightwards arrows
+operator.\u21F7.infix = lspace:5 rspace:5 accent # leftwards arrow with vertical stroke
+operator.\u21F8.infix = lspace:5 rspace:5 accent # rightwards arrow with vertical stroke
+operator.\u21F9.infix = lspace:5 rspace:5 accent # left right arrow with vertical stroke
+operator.\u21FA.infix = lspace:5 rspace:5 accent # leftwards arrow with double vertical stroke
+operator.\u21FB.infix = lspace:5 rspace:5 accent # rightwards arrow with double vertical stroke
+operator.\u21FC.infix = lspace:5 rspace:5 accent # left right arrow with double vertical stroke
+operator.\u21FD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards open-headed arrow
+operator.\u21FE.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards open-headed arrow
+operator.\u21FF.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # left right open-headed arrow
+operator.\u2200.prefix = lspace:2 rspace:1 # &ForAll;
+operator.\u2201.infix = lspace:1 rspace:2 # complement
+operator.\u2202.prefix = lspace:2 rspace:1 # &PartialD;
+operator.\u2203.prefix = lspace:2 rspace:1 # &Exists;
+operator.\u2204.prefix = lspace:2 rspace:1 # &NotExists;
+operator.\u2206.infix = lspace:3 rspace:3 # increment
+operator.\u2207.prefix = lspace:2 rspace:1 # &Del;
+operator.\u2208.infix = lspace:5 rspace:5 # &Element;
+operator.\u2209.infix = lspace:5 rspace:5 # &NotElement;
+operator.\u220A.infix = lspace:5 rspace:5 # small element of
+operator.\u220B.infix = lspace:5 rspace:5 # &SuchThat; &ReverseElement;
+operator.\u220C.infix = lspace:5 rspace:5 # &NotReverseElement;
+operator.\u220D.infix = lspace:5 rspace:5 # small contains as member
+operator.\u220E.infix = lspace:3 rspace:3 # end of proof
+operator.\u220F.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Product;
+operator.\u2210.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Coproduct;
+operator.\u2211.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical mirrorable # &Sum;
+operator.\u2212.infix = lspace:4 rspace:4 # official Unicode minus sign
+operator.\u2212.prefix = lspace:0 rspace:1 # official Unicode minus sign
+operator.\u2213.infix = lspace:4 rspace:4 # &MinusPlus;
+operator.\u2213.prefix = lspace:0 rspace:1 # &MinusPlus;
+operator.\u2214.infix = lspace:4 rspace:4 # dot plus
+operator.\u2215.infix = lspace:4 rspace:4 stretchy mirrorable direction:vertical # division slash
+operator.\u2216.infix = lspace:4 rspace:4 direction:vertical # set minus
+operator.\u2217.infix = lspace:4 rspace:4 # asterisk operator
+operator.\u2218.infix = lspace:4 rspace:4 # &SmallCircle;
+operator.\u2219.infix = lspace:4 rspace:4 # bullet operator
+operator.\u221A.prefix = lspace:1 rspace:1 stretchy direction:vertical mirrorable # &Sqrt;
+operator.\u221B.prefix = lspace:1 rspace:1 # cube root
+operator.\u221C.prefix = lspace:1 rspace:1 # fourth root
+operator.\u221D.infix = lspace:5 rspace:5 # &Proportional;
+operator.\u221F.infix = lspace:5 rspace:5 # right angle
+operator.\u2220.prefix = lspace:0 rspace:0 # angle
+operator.\u2221.prefix = lspace:0 rspace:0 # measured angle
+operator.\u2222.prefix = lspace:0 rspace:0 # spherical angle
+operator.\u2223.infix = lspace:5 rspace:5 direction:vertical # divides
+operator.\u2224.infix = lspace:5 rspace:5 # &NotVerticalBar;
+operator.\u2225.infix = lspace:5 rspace:5 direction:vertical # parallel to
+operator.\u2226.infix = lspace:5 rspace:5 # &NotDoubleVerticalBar;
+operator.\u2227.infix = lspace:4 rspace:4 # &wedge;
+operator.\u2228.infix = lspace:4 rspace:4 # &vee;
+operator.\u2229.infix = lspace:4 rspace:4 # &cap;
+operator.\u222A.infix = lspace:4 rspace:4 # &cup;
+operator.\u222B.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # &Integral;
+operator.\u222C.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # double integral
+operator.\u222D.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # triple integral
+operator.\u222E.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # &ContourIntegral;
+operator.\u222F.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # &DoubleContourIntegral;
+operator.\u2230.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # volume integral
+operator.\u2231.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral # clockwise integral
+operator.\u2232.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral # &ClockwiseContourIntegral;
+operator.\u2233.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral # &CounterClockwiseContourIntegral;
+operator.\u2234.infix = lspace:5 rspace:5 # &Therefore;
+operator.\u2235.infix = lspace:5 rspace:5 # &Because;
+operator.\u2236.infix = lspace:5 rspace:5 # ratio
+operator.\u2237.infix = lspace:5 rspace:5 # &Colon; &Proportion;
+operator.\u2238.infix = lspace:4 rspace:4 # dot minus
+operator.\u2239.infix = lspace:5 rspace:5 # excess
+operator.\u223A.infix = lspace:4 rspace:4 # geometric proportion
+operator.\u223B.infix = lspace:5 rspace:5 # homothetic
+operator.\u223C.infix = lspace:5 rspace:5 # &Tilde;
+operator.\u223D.infix = lspace:5 rspace:5 # reversed tilde
+operator.\u223D\u0331.infix = lspace:3 rspace:3 # reversed tilde with underline
+operator.\u223E.infix = lspace:5 rspace:5 # inverted lazy s
+operator.\u223F.infix = lspace:3 rspace:3 # sine wave
+operator.\u2240.infix = lspace:4 rspace:4 # &VerticalTilde;
+operator.\u2241.infix = lspace:5 rspace:5 # &NotTilde;
+operator.\u2242.infix = lspace:5 rspace:5 # &EqualTilde;
+operator.\u2242\u0338.infix = lspace:5 rspace:5 # &NotEqualTilde;
+operator.\u2243.infix = lspace:5 rspace:5 # &TildeEqual;
+operator.\u2244.infix = lspace:5 rspace:5 # &NotTildeEqual;
+operator.\u2245.infix = lspace:5 rspace:5 # &TildeFullEqual;
+operator.\u2246.infix = lspace:5 rspace:5 # approximately but not actually equal to
+operator.\u2247.infix = lspace:5 rspace:5 # &NotTildeFullEqual;
+operator.\u2248.infix = lspace:5 rspace:5 # &TildeTilde;
+operator.\u2249.infix = lspace:5 rspace:5 # &NotTildeTilde;
+operator.\u224A.infix = lspace:5 rspace:5 # almost equal or equal to
+operator.\u224B.infix = lspace:5 rspace:5 # triple tilde
+operator.\u224C.infix = lspace:5 rspace:5 # all equal to
+operator.\u224D.infix = lspace:5 rspace:5 # &CupCap;
+operator.\u224E.infix = lspace:5 rspace:5 # &HumpDownHump;
+operator.\u224E\u0338.infix = lspace:5 rspace:5 # &NotHumpDownHump;
+operator.\u224F.infix = lspace:5 rspace:5 # &HumpEqual;
+operator.\u224F\u0338.infix = lspace:5 rspace:5 # &NotHumpEqual;
+operator.\u2250.infix = lspace:5 rspace:5 # &DotEqual;
+operator.\u2251.infix = lspace:5 rspace:5 # geometrically equal to
+operator.\u2252.infix = lspace:5 rspace:5 # approximately equal to or the image of
+operator.\u2253.infix = lspace:5 rspace:5 # image of or approximately equal to
+operator.\u2254.infix = lspace:5 rspace:5 # &Assign;
+operator.\u2255.infix = lspace:5 rspace:5 # equals colon
+operator.\u2256.infix = lspace:5 rspace:5 # ring in equal to
+operator.\u2257.infix = lspace:5 rspace:5 # ring equal to
+operator.\u2258.infix = lspace:5 rspace:5 # corresponds to
+operator.\u2259.infix = lspace:5 rspace:5 # estimates
+operator.\u225A.infix = lspace:5 rspace:5 # equiangular to
+operator.\u225C.infix = lspace:5 rspace:5 # delta equal to
+operator.\u225D.infix = lspace:5 rspace:5 # equal to by definition
+operator.\u225E.infix = lspace:5 rspace:5 # measured by
+operator.\u225F.infix = lspace:5 rspace:5 # questioned equal to
+operator.\u2260.infix = lspace:5 rspace:5 # &NotEqual;
+operator.\u2261.infix = lspace:5 rspace:5 # &Congruent;
+operator.\u2262.infix = lspace:5 rspace:5 # &NotCongruent;
+operator.\u2263.infix = lspace:5 rspace:5 # strictly equivalent to
+operator.\u2264.infix = lspace:5 rspace:5 # &le;
+operator.\u2265.infix = lspace:5 rspace:5 # &GreaterEqual;
+operator.\u2266.infix = lspace:5 rspace:5 # &LessFullEqual;
+operator.\u2266\u0338.infix = lspace:5 rspace:5 # &NotGreaterFullEqual;
+operator.\u2267.infix = lspace:5 rspace:5 # &GreaterFullEqual;
+operator.\u2268.infix = lspace:5 rspace:5 # less-than but not equal to
+operator.\u2269.infix = lspace:5 rspace:5 # greater-than but not equal to
+operator.\u226A.infix = lspace:5 rspace:5 # &NestedLessLess;
+operator.\u226A\u0338.infix = lspace:5 rspace:5 # &NotLessLess;
+operator.\u226B.infix = lspace:5 rspace:5 # &NestedGreaterGreater;
+operator.\u226B\u0338.infix = lspace:5 rspace:5 # &NotGreaterGreater;
+operator.\u226C.infix = lspace:5 rspace:5 # between
+operator.\u226D.infix = lspace:5 rspace:5 # &NotCupCap;
+operator.\u226E.infix = lspace:5 rspace:5 # &NotLess;
+operator.\u226F.infix = lspace:5 rspace:5 # &NotGreater;
+operator.\u2270.infix = lspace:5 rspace:5 # &NotLessEqual;
+operator.\u2271.infix = lspace:5 rspace:5 # &NotGreaterEqual;
+operator.\u2272.infix = lspace:5 rspace:5 # &LessTilde;
+operator.\u2273.infix = lspace:5 rspace:5 # &GreaterTilde;
+operator.\u2274.infix = lspace:5 rspace:5 # &NotLessTilde;
+operator.\u2275.infix = lspace:5 rspace:5 # &NotGreaterTilde;
+operator.\u2276.infix = lspace:5 rspace:5 # &LessGreater;
+operator.\u2277.infix = lspace:5 rspace:5 # &GreaterLess;
+operator.\u2278.infix = lspace:5 rspace:5 # &NotLessGreater;
+operator.\u2279.infix = lspace:5 rspace:5 # &NotGreaterLess;
+operator.\u227A.infix = lspace:5 rspace:5 # &Precedes;
+operator.\u227B.infix = lspace:5 rspace:5 # &Succeeds;
+operator.\u227C.infix = lspace:5 rspace:5 # &PrecedesSlantEqual;
+operator.\u227D.infix = lspace:5 rspace:5 # &SucceedsSlantEqual;
+operator.\u227E.infix = lspace:5 rspace:5 # &PrecedesTilde;
+operator.\u227F.infix = lspace:5 rspace:5 # &SucceedsTilde;
+operator.\u227F\u0338.infix = lspace:5 rspace:5 # &NotSucceedsTilde;
+operator.\u2280.infix = lspace:5 rspace:5 # &NotPrecedes;
+operator.\u2281.infix = lspace:5 rspace:5 # &NotSucceeds;
+operator.\u2282.infix = lspace:5 rspace:5 # &subset;
+operator.\u2282\u20D2.infix = lspace:5 rspace:5 # subset of with vertical line
+operator.\u2283.infix = lspace:5 rspace:5 # &Superset;
+operator.\u2283\u20D2.infix = lspace:5 rspace:5 # superset of with vertical line
+operator.\u2284.infix = lspace:5 rspace:5 # &nsub;
+operator.\u2285.infix = lspace:5 rspace:5 # &nsup;
+operator.\u2286.infix = lspace:5 rspace:5 # &SubsetEqual;
+operator.\u2287.infix = lspace:5 rspace:5 # &SupersetEqual;
+operator.\u2288.infix = lspace:5 rspace:5 # &NotSubsetEqual;
+operator.\u2289.infix = lspace:5 rspace:5 # &NotSupersetEqual;
+operator.\u228A.infix = lspace:5 rspace:5 # &subsetneq; &subne;
+operator.\u228B.infix = lspace:5 rspace:5 # superset of with not equal to
+operator.\u228C.infix = lspace:4 rspace:4 # multiset
+operator.\u228D.infix = lspace:4 rspace:4 # multiset multiplication
+operator.\u228E.infix = lspace:4 rspace:4 direction:vertical # &UnionPlus;
+operator.\u228F.infix = lspace:5 rspace:5 # &SquareSubset;
+operator.\u228F\u0338.infix = lspace:5 rspace:5 # &NotSquareSubset;
+operator.\u2290.infix = lspace:5 rspace:5 # &SquareSuperset;
+operator.\u2290\u0338.infix = lspace:5 rspace:5 # &NotSquareSuperset;
+operator.\u2291.infix = lspace:5 rspace:5 # &SquareSubsetEqual;
+operator.\u2292.infix = lspace:5 rspace:5 # &SquareSupersetEqual;
+operator.\u2293.infix = lspace:4 rspace:4 direction:vertical # &SquareIntersection;
+operator.\u2294.infix = lspace:4 rspace:4 direction:vertical # &SquareUnion;
+operator.\u2295.infix = lspace:4 rspace:4 # &CirclePlus;
+operator.\u2296.infix = lspace:4 rspace:4 # &CircleMinus;
+operator.\u2297.infix = lspace:4 rspace:4 # &CircleTimes;
+operator.\u2298.infix = lspace:4 rspace:4 # circled division slash
+operator.\u2299.infix = lspace:4 rspace:4 # &CircleDot;
+operator.\u229A.infix = lspace:4 rspace:4 # circled ring operator
+operator.\u229B.infix = lspace:4 rspace:4 # circled asterisk operator
+operator.\u229C.infix = lspace:4 rspace:4 # circled equals
+operator.\u229D.infix = lspace:4 rspace:4 # circled dash
+operator.\u229E.infix = lspace:4 rspace:4 # squared plus
+operator.\u229F.infix = lspace:4 rspace:4 # squared minus
+operator.\u22A0.infix = lspace:4 rspace:4 # squared times
+operator.\u22A1.infix = lspace:4 rspace:4 # squared dot operator
+operator.\u22A2.infix = lspace:5 rspace:5 # &RightTee;
+operator.\u22A3.infix = lspace:5 rspace:5 # &LeftTee;
+operator.\u22A4.infix = lspace:5 rspace:5 # &DownTee;
+operator.\u22A5.infix = lspace:5 rspace:5 # &UpTee;
+operator.\u22A6.infix = lspace:5 rspace:5 # assertion
+operator.\u22A7.infix = lspace:5 rspace:5 # models
+operator.\u22A8.infix = lspace:5 rspace:5 # &DoubleRightTee;
+operator.\u22A9.infix = lspace:5 rspace:5 # forces
+operator.\u22AA.infix = lspace:5 rspace:5 # triple vertical bar right turnstile
+operator.\u22AB.infix = lspace:5 rspace:5 # double vertical bar double right turnstile
+operator.\u22AC.infix = lspace:5 rspace:5 # does not prove
+operator.\u22AD.infix = lspace:5 rspace:5 # not true
+operator.\u22AE.infix = lspace:5 rspace:5 # does not force
+operator.\u22AF.infix = lspace:5 rspace:5 # negated double vertical bar double right turnstile
+operator.\u22B0.infix = lspace:5 rspace:5 # precedes under relation
+operator.\u22B1.infix = lspace:5 rspace:5 # succeeds under relation
+operator.\u22B2.infix = lspace:5 rspace:5 # &LeftTriangle;
+operator.\u22B3.infix = lspace:5 rspace:5 # &RightTriangle;
+operator.\u22B4.infix = lspace:5 rspace:5 # &LeftTriangleEqual;
+operator.\u22B5.infix = lspace:5 rspace:5 # &RightTriangleEqual;
+operator.\u22B6.infix = lspace:5 rspace:5 # original of
+operator.\u22B7.infix = lspace:5 rspace:5 # image of
+operator.\u22B8.infix = lspace:5 rspace:5 # multimap
+operator.\u22B9.infix = lspace:5 rspace:5 # hermitian conjugate matrix
+operator.\u22BA.infix = lspace:4 rspace:4 # intercalate
+operator.\u22BB.infix = lspace:4 rspace:4 # xor
+operator.\u22BC.infix = lspace:4 rspace:4 # nand
+operator.\u22BD.infix = lspace:4 rspace:4 # nor
+operator.\u22BE.infix = lspace:3 rspace:3 # right angle with arc
+operator.\u22BF.infix = lspace:3 rspace:3 # right triangle
+operator.\u22C0.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Wedge;
+operator.\u22C1.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Vee;
+operator.\u22C2.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Intersection;
+operator.\u22C3.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Union;
+operator.\u22C4.infix = lspace:4 rspace:4 # &Diamond;
+operator.\u22C5.infix = lspace:4 rspace:4 # &cdot;
+operator.\u22C6.infix = lspace:4 rspace:4 # &Star;
+operator.\u22C7.infix = lspace:4 rspace:4 # division times
+operator.\u22C8.infix = lspace:5 rspace:5 # bowtie
+operator.\u22C9.infix = lspace:4 rspace:4 # left normal factor semidirect product
+operator.\u22CA.infix = lspace:4 rspace:4 # right normal factor semidirect product
+operator.\u22CB.infix = lspace:4 rspace:4 # left semidirect product
+operator.\u22CC.infix = lspace:4 rspace:4 # right semidirect product
+operator.\u22CD.infix = lspace:5 rspace:5 # reversed tilde equals
+operator.\u22CE.infix = lspace:4 rspace:4 # curly logical or
+operator.\u22CF.infix = lspace:4 rspace:4 # curly logical and
+operator.\u22D0.infix = lspace:5 rspace:5 # &Subset;
+operator.\u22D1.infix = lspace:5 rspace:5 # double superset
+operator.\u22D2.infix = lspace:4 rspace:4 # &Cap;
+operator.\u22D3.infix = lspace:4 rspace:4 # &Cup;
+operator.\u22D4.infix = lspace:5 rspace:5 # pitchfork
+operator.\u22D5.infix = lspace:5 rspace:5 # equal and parallel to
+operator.\u22D6.infix = lspace:5 rspace:5 # less-than with dot
+operator.\u22D7.infix = lspace:5 rspace:5 # greater-than with dot
+operator.\u22D8.infix = lspace:5 rspace:5 # very much less-than
+operator.\u22D9.infix = lspace:5 rspace:5 # very much greater-than
+operator.\u22DA.infix = lspace:5 rspace:5 # &LessEqualGreater;
+operator.\u22DB.infix = lspace:5 rspace:5 # &GreaterEqualLess;
+operator.\u22DC.infix = lspace:5 rspace:5 # equal to or less-than
+operator.\u22DD.infix = lspace:5 rspace:5 # equal to or greater-than
+operator.\u22DE.infix = lspace:5 rspace:5 # equal to or precedes
+operator.\u22DF.infix = lspace:5 rspace:5 # equal to or succeeds
+operator.\u22E0.infix = lspace:5 rspace:5 # &NotPrecedesSlantEqual;
+operator.\u22E1.infix = lspace:5 rspace:5 # &NotSucceedsSlantEqual;
+operator.\u22E2.infix = lspace:5 rspace:5 # &NotSquareSubsetEqual;
+operator.\u22E3.infix = lspace:5 rspace:5 # &NotSquareSupersetEqual;
+operator.\u22E4.infix = lspace:5 rspace:5 # square image of or not equal to
+operator.\u22E5.infix = lspace:5 rspace:5 # square original of or not equal to
+operator.\u22E6.infix = lspace:5 rspace:5 # less-than but not equivalent to
+operator.\u22E7.infix = lspace:5 rspace:5 # greater-than but not equivalent to
+operator.\u22E8.infix = lspace:5 rspace:5 # precedes but not equivalent to
+operator.\u22E9.infix = lspace:5 rspace:5 # succeeds but not equivalent to
+operator.\u22EA.infix = lspace:5 rspace:5 # &NotLeftTriangle;
+operator.\u22EB.infix = lspace:5 rspace:5 # &NotRightTriangle;
+operator.\u22EC.infix = lspace:5 rspace:5 # &NotLeftTriangleEqual;
+operator.\u22ED.infix = lspace:5 rspace:5 # &NotRightTriangleEqual;
+operator.\u22EE.infix = lspace:5 rspace:5 # vertical ellipsis
+operator.\u22EF.infix = lspace:0 rspace:0 # midline horizontal ellipsis
+operator.\u22F0.infix = lspace:5 rspace:5 # up right diagonal ellipsis
+operator.\u22F1.infix = lspace:5 rspace:5 # down right diagonal ellipsis
+operator.\u22F2.infix = lspace:5 rspace:5 # element of with long horizontal stroke
+operator.\u22F3.infix = lspace:5 rspace:5 # element of with vertical bar at end of horizontal stroke
+operator.\u22F4.infix = lspace:5 rspace:5 # small element of with vertical bar at end of horizontal stroke
+operator.\u22F5.infix = lspace:5 rspace:5 # element of with dot above
+operator.\u22F6.infix = lspace:5 rspace:5 # element of with overbar
+operator.\u22F7.infix = lspace:5 rspace:5 # small element of with overbar
+operator.\u22F8.infix = lspace:5 rspace:5 # element of with underbar
+operator.\u22F9.infix = lspace:5 rspace:5 # element of with two horizontal strokes
+operator.\u22FA.infix = lspace:5 rspace:5 # contains with long horizontal stroke
+operator.\u22FB.infix = lspace:5 rspace:5 # contains with vertical bar at end of horizontal stroke
+operator.\u22FC.infix = lspace:5 rspace:5 # small contains with vertical bar at end of horizontal stroke
+operator.\u22FD.infix = lspace:5 rspace:5 # contains with overbar
+operator.\u22FE.infix = lspace:5 rspace:5 # small contains with overbar
+operator.\u22FF.infix = lspace:5 rspace:5 # z notation bag membership
+operator.\u2308.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &LeftCeiling;
+operator.\u2309.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &RightCeiling;
+operator.\u230A.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &LeftFloor;
+operator.\u230B.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &RightFloor;
+operator.\u23B4.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBracket;
+operator.\u23B5.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBracket;
+operator.\u23DC.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverParenthesis; (Unicode)
+operator.\u23DD.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderParenthesis; (Unicode)
+operator.\u23DE.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBrace; (Unicode)
+operator.\u23DF.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBrace; (Unicode)
+operator.\u23E0.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # top tortoise shell bracket
+operator.\u23E1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # bottom tortoise shell bracket
+operator.\u25A0.infix = lspace:3 rspace:3 # black square
+operator.\u25A1.infix = lspace:3 rspace:3 # white square
+operator.\u25AA.infix = lspace:3 rspace:3 # black small square
+operator.\u25AB.infix = lspace:3 rspace:3 # white small square
+operator.\u25AD.infix = lspace:3 rspace:3 # white rectangle
+operator.\u25AE.infix = lspace:3 rspace:3 # black vertical rectangle
+operator.\u25AF.infix = lspace:3 rspace:3 # white vertical rectangle
+operator.\u25B0.infix = lspace:3 rspace:3 # black parallelogram
+operator.\u25B1.infix = lspace:3 rspace:3 # white parallelogram
+operator.\u25B2.infix = lspace:4 rspace:4 # black up-pointing triangle
+operator.\u25B3.infix = lspace:4 rspace:4 # white up-pointing triangle
+operator.\u25B4.infix = lspace:4 rspace:4 # black up-pointing small triangle
+operator.\u25B5.infix = lspace:4 rspace:4 # white up-pointing small triangle
+operator.\u25B6.infix = lspace:4 rspace:4 # black right-pointing triangle
+operator.\u25B7.infix = lspace:4 rspace:4 # white right-pointing triangle
+operator.\u25B8.infix = lspace:4 rspace:4 # black right-pointing small triangle
+operator.\u25B9.infix = lspace:4 rspace:4 # white right-pointing small triangle
+operator.\u25BC.infix = lspace:4 rspace:4 # black down-pointing triangle
+operator.\u25BD.infix = lspace:4 rspace:4 # white down-pointing triangle
+operator.\u25BE.infix = lspace:4 rspace:4 # black down-pointing small triangle
+operator.\u25BF.infix = lspace:4 rspace:4 # white down-pointing small triangle
+operator.\u25C0.infix = lspace:4 rspace:4 # black left-pointing triangle
+operator.\u25C1.infix = lspace:4 rspace:4 # white left-pointing triangle
+operator.\u25C2.infix = lspace:4 rspace:4 # black left-pointing small triangle
+operator.\u25C3.infix = lspace:4 rspace:4 # white left-pointing small triangle
+operator.\u25C4.infix = lspace:4 rspace:4 # black left-pointing pointer
+operator.\u25C5.infix = lspace:4 rspace:4 # white left-pointing pointer
+operator.\u25C6.infix = lspace:4 rspace:4 # black diamond
+operator.\u25C7.infix = lspace:4 rspace:4 # white diamond
+operator.\u25C8.infix = lspace:4 rspace:4 # white diamond containing black small diamond
+operator.\u25C9.infix = lspace:4 rspace:4 # fisheye
+operator.\u25CC.infix = lspace:4 rspace:4 # dotted circle
+operator.\u25CD.infix = lspace:4 rspace:4 # circle with vertical fill
+operator.\u25CE.infix = lspace:4 rspace:4 # bullseye
+operator.\u25CF.infix = lspace:4 rspace:4 # black circle
+operator.\u25D6.infix = lspace:4 rspace:4 # left half black circle
+operator.\u25D7.infix = lspace:4 rspace:4 # right half black circle
+operator.\u25E6.infix = lspace:4 rspace:4 # white bullet
+operator.\u266D.postfix = lspace:0 rspace:2 # music flat sign
+operator.\u266E.postfix = lspace:0 rspace:2 # music natural sign
+operator.\u266F.postfix = lspace:0 rspace:2 # music sharp sign
+operator.\u2758.infix = lspace:5 rspace:5 direction:vertical # light vertical bar
+operator.\u2772.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # light left tortoise shell bracket ornament
+operator.\u2773.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # light right tortoise shell bracket ornament
+operator.\u27E6.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &LeftDoubleBracket;
+operator.\u27E7.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &RightDoubleBracket;
+operator.\u27E8.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &LeftAngleBracket;
+operator.\u27E9.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &RightAngleBracket;
+operator.\u27EA.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical left double angle bracket
+operator.\u27EB.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical right double angle bracket
+operator.\u27EC.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical left white tortoise shell bracket
+operator.\u27ED.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical right white tortoise shell bracket
+operator.\u27EE.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical left flattened parenthesis
+operator.\u27EF.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical right flattened parenthesis
+operator.\u27F0.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards quadruple arrow
+operator.\u27F1.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards quadruple arrow
+operator.\u27F5.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongLeftArrow;
+operator.\u27F6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongRightArrow;
+operator.\u27F7.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongLeftRightArrow;
+operator.\u27F8.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongLeftArrow;
+operator.\u27F9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongRightArrow;
+operator.\u27FA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongLeftRightArrow;
+operator.\u27FB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long leftwards arrow from bar
+operator.\u27FC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards arrow from bar
+operator.\u27FD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long leftwards double arrow from bar
+operator.\u27FE.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards double arrow from bar
+operator.\u27FF.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards squiggle arrow
+operator.\u2900.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with vertical stroke
+operator.\u2901.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with double vertical stroke
+operator.\u2902.infix = lspace:5 rspace:5 accent # leftwards double arrow with vertical stroke
+operator.\u2903.infix = lspace:5 rspace:5 accent # rightwards double arrow with vertical stroke
+operator.\u2904.infix = lspace:5 rspace:5 accent # left right double arrow with vertical stroke
+operator.\u2905.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow from bar
+operator.\u2906.infix = lspace:5 rspace:5 accent # leftwards double arrow from bar
+operator.\u2907.infix = lspace:5 rspace:5 accent # rightwards double arrow from bar
+operator.\u2908.infix = lspace:5 rspace:5 # downwards arrow with horizontal stroke
+operator.\u2909.infix = lspace:5 rspace:5 # upwards arrow with horizontal stroke
+operator.\u290A.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards triple arrow
+operator.\u290B.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards triple arrow
+operator.\u290C.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards double dash arrow
+operator.\u290D.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards double dash arrow
+operator.\u290E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards triple dash arrow
+operator.\u290F.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards triple dash arrow
+operator.\u2910.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards two-headed triple dash arrow
+operator.\u2911.infix = lspace:5 rspace:5 accent # rightwards arrow with dotted stem
+operator.\u2912.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrowBar;
+operator.\u2913.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrowBar;
+operator.\u2914.infix = lspace:5 rspace:5 accent # rightwards arrow with tail with vertical stroke
+operator.\u2915.infix = lspace:5 rspace:5 accent # rightwards arrow with tail with double vertical stroke
+operator.\u2916.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with tail
+operator.\u2917.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with tail with vertical stroke
+operator.\u2918.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with tail with double vertical stroke
+operator.\u2919.infix = lspace:5 rspace:5 accent # leftwards arrow-tail
+operator.\u291A.infix = lspace:5 rspace:5 accent # rightwards arrow-tail
+operator.\u291B.infix = lspace:5 rspace:5 accent # leftwards double arrow-tail
+operator.\u291C.infix = lspace:5 rspace:5 accent # rightwards double arrow-tail
+operator.\u291D.infix = lspace:5 rspace:5 accent # leftwards arrow to black diamond
+operator.\u291E.infix = lspace:5 rspace:5 accent # rightwards arrow to black diamond
+operator.\u291F.infix = lspace:5 rspace:5 accent # leftwards arrow from bar to black diamond
+operator.\u2920.infix = lspace:5 rspace:5 accent # rightwards arrow from bar to black diamond
+operator.\u2923.infix = lspace:5 rspace:5 # north west arrow with hook
+operator.\u2924.infix = lspace:5 rspace:5 # north east arrow with hook
+operator.\u2925.infix = lspace:5 rspace:5 # south east arrow with hook
+operator.\u2926.infix = lspace:5 rspace:5 # south west arrow with hook
+operator.\u2927.infix = lspace:5 rspace:5 # north west arrow and north east arrow
+operator.\u2928.infix = lspace:5 rspace:5 # north east arrow and south east arrow
+operator.\u2929.infix = lspace:5 rspace:5 # south east arrow and south west arrow
+operator.\u292A.infix = lspace:5 rspace:5 # south west arrow and north west arrow
+operator.\u292B.infix = lspace:5 rspace:5 # rising diagonal crossing falling diagonal
+operator.\u292C.infix = lspace:5 rspace:5 # falling diagonal crossing rising diagonal
+operator.\u292D.infix = lspace:5 rspace:5 # south east arrow crossing north east arrow
+operator.\u292E.infix = lspace:5 rspace:5 # north east arrow crossing south east arrow
+operator.\u292F.infix = lspace:5 rspace:5 # falling diagonal crossing north east arrow
+operator.\u2930.infix = lspace:5 rspace:5 # rising diagonal crossing south east arrow
+operator.\u2931.infix = lspace:5 rspace:5 # north east arrow crossing north west arrow
+operator.\u2932.infix = lspace:5 rspace:5 # north west arrow crossing north east arrow
+operator.\u2933.infix = lspace:5 rspace:5 accent # wave arrow pointing directly right
+operator.\u2934.infix = lspace:5 rspace:5 # arrow pointing rightwards then curving upwards
+operator.\u2935.infix = lspace:5 rspace:5 # arrow pointing rightwards then curving downwards
+operator.\u2936.infix = lspace:5 rspace:5 # arrow pointing downwards then curving leftwards
+operator.\u2937.infix = lspace:5 rspace:5 # arrow pointing downwards then curving rightwards
+operator.\u2938.infix = lspace:5 rspace:5 # right-side arc clockwise arrow
+operator.\u2939.infix = lspace:5 rspace:5 # left-side arc anticlockwise arrow
+operator.\u293A.infix = lspace:5 rspace:5 accent # top arc anticlockwise arrow
+operator.\u293B.infix = lspace:5 rspace:5 accent # bottom arc anticlockwise arrow
+operator.\u293C.infix = lspace:5 rspace:5 accent # top arc clockwise arrow with minus
+operator.\u293D.infix = lspace:5 rspace:5 accent # top arc anticlockwise arrow with plus
+operator.\u293E.infix = lspace:5 rspace:5 # lower right semicircular clockwise arrow
+operator.\u293F.infix = lspace:5 rspace:5 # lower left semicircular anticlockwise arrow
+operator.\u2940.infix = lspace:5 rspace:5 # anticlockwise closed circle arrow
+operator.\u2941.infix = lspace:5 rspace:5 # clockwise closed circle arrow
+operator.\u2942.infix = lspace:5 rspace:5 accent # rightwards arrow above short leftwards arrow
+operator.\u2943.infix = lspace:5 rspace:5 accent # leftwards arrow above short rightwards arrow
+operator.\u2944.infix = lspace:5 rspace:5 accent # short rightwards arrow above leftwards arrow
+operator.\u2945.infix = lspace:5 rspace:5 accent # rightwards arrow with plus below
+operator.\u2946.infix = lspace:5 rspace:5 accent # leftwards arrow with plus below
+operator.\u2947.infix = lspace:5 rspace:5 accent # rightwards arrow through x
+operator.\u2948.infix = lspace:5 rspace:5 accent # left right arrow through small circle
+operator.\u2949.infix = lspace:5 rspace:5 # upwards two-headed arrow from small circle
+operator.\u294A.infix = lspace:5 rspace:5 accent # left barb up right barb down harpoon
+operator.\u294B.infix = lspace:5 rspace:5 accent # left barb down right barb up harpoon
+operator.\u294C.infix = lspace:5 rspace:5 # up barb right down barb left harpoon
+operator.\u294D.infix = lspace:5 rspace:5 # up barb left down barb right harpoon
+operator.\u294E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftRightVector;
+operator.\u294F.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpDownVector;
+operator.\u2950.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftRightVector;
+operator.\u2951.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpDownVector;
+operator.\u2952.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftVectorBar;
+operator.\u2953.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightVectorBar;
+operator.\u2954.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpVectorBar;
+operator.\u2955.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownVectorBar;
+operator.\u2956.infix = lspace:5 rspace:5 stretchy direction:horizontal # &DownLeftVectorBar;
+operator.\u2957.infix = lspace:5 rspace:5 stretchy direction:horizontal # &DownRightVectorBar;
+operator.\u2958.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpVectorBar;
+operator.\u2959.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownVectorBar;
+operator.\u295A.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftTeeVector;
+operator.\u295B.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightTeeVector;
+operator.\u295C.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpTeeVector;
+operator.\u295D.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownTeeVector;
+operator.\u295E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftTeeVector;
+operator.\u295F.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownRightTeeVector;
+operator.\u2960.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpTeeVector;
+operator.\u2961.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownTeeVector;
+operator.\u2962.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb up above leftwards harpoon with barb down
+operator.\u2963.infix = lspace:5 rspace:5 # upwards harpoon with barb left beside upwards harpoon with barb right
+operator.\u2964.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb up above rightwards harpoon with barb down
+operator.\u2965.infix = lspace:5 rspace:5 # downwards harpoon with barb left beside downwards harpoon with barb right
+operator.\u2966.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb up above rightwards harpoon with barb up
+operator.\u2967.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb down above rightwards harpoon with barb down
+operator.\u2968.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb up above leftwards harpoon with barb up
+operator.\u2969.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb down above leftwards harpoon with barb down
+operator.\u296A.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb up above long dash
+operator.\u296B.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb down below long dash
+operator.\u296C.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb up above long dash
+operator.\u296D.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb down below long dash
+operator.\u296E.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpEquilibrium;
+operator.\u296F.infix = lspace:5 rspace:5 stretchy direction:vertical # &ReverseUpEquilibrium;
+operator.\u2970.infix = lspace:5 rspace:5 accent # &RoundImplies;
+operator.\u2971.infix = lspace:5 rspace:5 accent # equals sign above rightwards arrow
+operator.\u2972.infix = lspace:5 rspace:5 accent # tilde operator above rightwards arrow
+operator.\u2973.infix = lspace:5 rspace:5 accent # leftwards arrow above tilde operator
+operator.\u2974.infix = lspace:5 rspace:5 accent # rightwards arrow above tilde operator
+operator.\u2975.infix = lspace:5 rspace:5 accent # rightwards arrow above almost equal to
+operator.\u2976.infix = lspace:5 rspace:5 accent # less-than above leftwards arrow
+operator.\u2977.infix = lspace:5 rspace:5 accent # leftwards arrow through less-than
+operator.\u2978.infix = lspace:5 rspace:5 accent # greater-than above rightwards arrow
+operator.\u2979.infix = lspace:5 rspace:5 accent # subset above rightwards arrow
+operator.\u297A.infix = lspace:5 rspace:5 accent # leftwards arrow through subset
+operator.\u297B.infix = lspace:5 rspace:5 accent # superset above leftwards arrow
+operator.\u297C.infix = lspace:5 rspace:5 accent # left fish tail
+operator.\u297D.infix = lspace:5 rspace:5 accent # right fish tail
+operator.\u297E.infix = lspace:5 rspace:5 # up fish tail
+operator.\u297F.infix = lspace:5 rspace:5 # down fish tail
+operator.\u2980.prefix = lspace:0 rspace:0 stretchy fence direction:vertical # triple direction:vertical bar delimiter
+operator.\u2980.postfix = lspace:0 rspace:0 stretchy fence direction:vertical # triple direction:vertical bar delimiter
+operator.\u2981.infix = lspace:3 rspace:3 # z notation spot
+operator.\u2982.infix = lspace:3 rspace:3 # z notation type colon
+operator.\u2983.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left white curly bracket
+operator.\u2984.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right white curly bracket
+operator.\u2985.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left white parenthesis
+operator.\u2986.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right white parenthesis
+operator.\u2987.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # z notation left image bracket
+operator.\u2988.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # z notation right image bracket
+operator.\u2989.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # z notation left binding bracket
+operator.\u298A.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # z notation right binding bracket
+operator.\u298B.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left square bracket with underbar
+operator.\u298C.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right square bracket with underbar
+operator.\u298D.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left square bracket with tick in top corner
+operator.\u298E.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right square bracket with tick in bottom corner
+operator.\u298F.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left square bracket with tick in bottom corner
+operator.\u2990.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right square bracket with tick in top corner
+operator.\u2991.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left angle bracket with dot
+operator.\u2992.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right angle bracket with dot
+operator.\u2993.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left arc less-than bracket
+operator.\u2994.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right arc greater-than bracket
+operator.\u2995.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # double left arc greater-than bracket
+operator.\u2996.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # double right arc less-than bracket
+operator.\u2997.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left black tortoise shell bracket
+operator.\u2998.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right black tortoise shell bracket
+operator.\u2999.infix = lspace:3 rspace:3 # dotted fence
+operator.\u299A.infix = lspace:3 rspace:3 # vertical zigzag line
+operator.\u299B.infix = lspace:3 rspace:3 # measured angle opening left
+operator.\u299C.infix = lspace:3 rspace:3 # right angle variant with square
+operator.\u299D.infix = lspace:3 rspace:3 # measured right angle with dot
+operator.\u299E.infix = lspace:3 rspace:3 # angle with s inside
+operator.\u299F.infix = lspace:3 rspace:3 # acute angle
+operator.\u29A0.infix = lspace:3 rspace:3 # spherical angle opening left
+operator.\u29A1.infix = lspace:3 rspace:3 # spherical angle opening up
+operator.\u29A2.infix = lspace:3 rspace:3 # turned angle
+operator.\u29A3.infix = lspace:3 rspace:3 # reversed angle
+operator.\u29A4.infix = lspace:3 rspace:3 # angle with underbar
+operator.\u29A5.infix = lspace:3 rspace:3 # reversed angle with underbar
+operator.\u29A6.infix = lspace:3 rspace:3 # oblique angle opening up
+operator.\u29A7.infix = lspace:3 rspace:3 # oblique angle opening down
+operator.\u29A8.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing up and right
+operator.\u29A9.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing up and left
+operator.\u29AA.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing down and right
+operator.\u29AB.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing down and left
+operator.\u29AC.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing right and up
+operator.\u29AD.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing left and up
+operator.\u29AE.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing right and down
+operator.\u29AF.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing left and down
+operator.\u29B0.infix = lspace:3 rspace:3 # reversed empty set
+operator.\u29B1.infix = lspace:3 rspace:3 # empty set with overbar
+operator.\u29B2.infix = lspace:3 rspace:3 # empty set with small circle above
+operator.\u29B3.infix = lspace:3 rspace:3 # empty set with right arrow above
+operator.\u29B4.infix = lspace:3 rspace:3 # empty set with left arrow above
+operator.\u29B5.infix = lspace:3 rspace:3 # circle with horizontal bar
+operator.\u29B6.infix = lspace:4 rspace:4 # circled vertical bar
+operator.\u29B7.infix = lspace:4 rspace:4 # circled parallel
+operator.\u29B8.infix = lspace:4 rspace:4 # circled reverse solidus
+operator.\u29B9.infix = lspace:4 rspace:4 # circled perpendicular
+operator.\u29BA.infix = lspace:4 rspace:4 # circle divided by horizontal bar and top half divided by vertical bar
+operator.\u29BB.infix = lspace:4 rspace:4 # circle with superimposed x
+operator.\u29BC.infix = lspace:4 rspace:4 # circled anticlockwise-rotated division sign
+operator.\u29BD.infix = lspace:4 rspace:4 # up arrow through circle
+operator.\u29BE.infix = lspace:4 rspace:4 # circled white bullet
+operator.\u29BF.infix = lspace:4 rspace:4 # circled bullet
+operator.\u29C0.infix = lspace:5 rspace:5 # circled less-than
+operator.\u29C1.infix = lspace:5 rspace:5 # circled greater-than
+operator.\u29C2.infix = lspace:3 rspace:3 # circle with small circle to the right
+operator.\u29C3.infix = lspace:3 rspace:3 # circle with two horizontal strokes to the right
+operator.\u29C4.infix = lspace:4 rspace:4 # squared rising diagonal slash
+operator.\u29C5.infix = lspace:4 rspace:4 # squared falling diagonal slash
+operator.\u29C6.infix = lspace:4 rspace:4 # squared asterisk
+operator.\u29C7.infix = lspace:4 rspace:4 # squared small circle
+operator.\u29C8.infix = lspace:4 rspace:4 # squared square
+operator.\u29C9.infix = lspace:3 rspace:3 # two joined squares
+operator.\u29CA.infix = lspace:3 rspace:3 # triangle with dot above
+operator.\u29CB.infix = lspace:3 rspace:3 # triangle with underbar
+operator.\u29CC.infix = lspace:3 rspace:3 # s in triangle
+operator.\u29CD.infix = lspace:3 rspace:3 # triangle with serifs at bottom
+operator.\u29CE.infix = lspace:5 rspace:5 # right triangle above left triangle
+operator.\u29CF.infix = lspace:5 rspace:5 # &LeftTriangleBar;
+operator.\u29CF\u0338.infix = lspace:5 rspace:5 # &NotLeftTriangleBar;
+operator.\u29D0.infix = lspace:5 rspace:5 # &RightTriangleBar;
+operator.\u29D0\u0338.infix = lspace:5 rspace:5 # &NotRightTriangleBar;
+operator.\u29D1.infix = lspace:5 rspace:5 # bowtie with left half black
+operator.\u29D2.infix = lspace:5 rspace:5 # bowtie with right half black
+operator.\u29D3.infix = lspace:5 rspace:5 # black bowtie
+operator.\u29D4.infix = lspace:5 rspace:5 # times with left half black
+operator.\u29D5.infix = lspace:5 rspace:5 # times with right half black
+operator.\u29D6.infix = lspace:4 rspace:4 # white hourglass
+operator.\u29D7.infix = lspace:4 rspace:4 # black hourglass
+operator.\u29D8.infix = lspace:3 rspace:3 # left wiggly fence
+operator.\u29D9.infix = lspace:3 rspace:3 # right wiggly fence
+operator.\u29DB.infix = lspace:3 rspace:3 # right double wiggly fence
+operator.\u29DC.infix = lspace:3 rspace:3 # incomplete infinity
+operator.\u29DD.infix = lspace:3 rspace:3 # tie over infinity
+operator.\u29DE.infix = lspace:5 rspace:5 # infinity negated with vertical bar
+operator.\u29DF.infix = lspace:3 rspace:3 # double-ended multimap
+operator.\u29E0.infix = lspace:3 rspace:3 # square with contoured outline
+operator.\u29E1.infix = lspace:5 rspace:5 # increases as
+operator.\u29E2.infix = lspace:4 rspace:4 # shuffle product
+operator.\u29E3.infix = lspace:5 rspace:5 # equals sign and slanted parallel
+operator.\u29E4.infix = lspace:5 rspace:5 # equals sign and slanted parallel with tilde above
+operator.\u29E5.infix = lspace:5 rspace:5 # identical to and slanted parallel
+operator.\u29E6.infix = lspace:5 rspace:5 # gleich stark
+operator.\u29E7.infix = lspace:3 rspace:3 # thermodynamic
+operator.\u29E8.infix = lspace:3 rspace:3 # down-pointing triangle with left half black
+operator.\u29E9.infix = lspace:3 rspace:3 # down-pointing triangle with right half black
+operator.\u29EA.infix = lspace:3 rspace:3 # black diamond with down arrow
+operator.\u29EB.infix = lspace:3 rspace:3 # black lozenge
+operator.\u29EC.infix = lspace:3 rspace:3 # white circle with down arrow
+operator.\u29ED.infix = lspace:3 rspace:3 # black circle with down arrow
+operator.\u29EE.infix = lspace:3 rspace:3 # error-barred white square
+operator.\u29EF.infix = lspace:3 rspace:3 # error-barred black square
+operator.\u29F0.infix = lspace:3 rspace:3 # error-barred white diamond
+operator.\u29F1.infix = lspace:3 rspace:3 # error-barred black diamond
+operator.\u29F2.infix = lspace:3 rspace:3 # error-barred white circle
+operator.\u29F3.infix = lspace:3 rspace:3 # error-barred black circle
+operator.\u29F4.infix = lspace:5 rspace:5 # rule-delayed
+operator.\u29F5.infix = lspace:4 rspace:4 # reverse solidus operator
+operator.\u29F6.infix = lspace:4 rspace:4 # solidus with overbar
+operator.\u29F7.infix = lspace:4 rspace:4 # reverse solidus with horizontal stroke
+operator.\u29F8.infix = lspace:3 rspace:3 # big solidus
+operator.\u29F9.infix = lspace:3 rspace:3 # big reverse solidus
+operator.\u29FA.infix = lspace:3 rspace:3 # double plus
+operator.\u29FB.infix = lspace:3 rspace:3 # triple plus
+operator.\u29FC.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left-pointing curved angle bracket
+operator.\u29FD.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right-pointing curved angle bracket
+operator.\u29FE.infix = lspace:4 rspace:4 # tiny
+operator.\u29FF.infix = lspace:4 rspace:4 # miny
+operator.\u2A00.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &bigodot;
+operator.\u2A01.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &bigoplus;
+operator.\u2A02.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &bigotimes;
+operator.\u2A03.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # n-ary union operator with dot
+operator.\u2A04.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &biguplus;
+operator.\u2A05.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # n-ary square intersection operator
+operator.\u2A06.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &bigsqcup;
+operator.\u2A07.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # two logical and operator
+operator.\u2A08.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # two logical or operator
+operator.\u2A09.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # n-ary times operator
+operator.\u2A0A.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical mirrorable # modulo two sum
+operator.\u2A0B.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # summation with integral
+operator.\u2A0C.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # quadruple integral operator
+operator.\u2A0D.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # finite part integral
+operator.\u2A0E.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with double stroke
+operator.\u2A0F.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral average with slash
+operator.\u2A10.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral mirrorable # circulation function
+operator.\u2A11.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral # anticlockwise integration
+operator.\u2A12.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral mirrorable # line integration with rectangular path around pole
+operator.\u2A13.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral mirrorable # line integration with semicircular path around pole
+operator.\u2A14.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral mirrorable # line integration not including the pole
+operator.\u2A15.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral around a point operator
+operator.\u2A16.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # quaternion integral operator
+operator.\u2A17.prefix = lspace:1 rspace:2 largeop symmetric integral mirrorable direction:vertical # integral with leftwards arrow with hook
+operator.\u2A18.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with times sign
+operator.\u2A19.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with intersection
+operator.\u2A1A.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with union
+operator.\u2A1B.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with overbar
+operator.\u2A1C.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with underbar
+operator.\u2A1D.infix = lspace:3 rspace:3 # join
+operator.\u2A1E.infix = lspace:3 rspace:3 # large left triangle operator
+operator.\u2A1F.infix = lspace:3 rspace:3 # z notation schema composition
+operator.\u2A20.infix = lspace:3 rspace:3 # z notation schema piping
+operator.\u2A21.infix = lspace:3 rspace:3 # z notation schema projection
+operator.\u2A22.infix = lspace:4 rspace:4 # plus sign with small circle above
+operator.\u2A23.infix = lspace:4 rspace:4 # plus sign with circumflex accent above
+operator.\u2A24.infix = lspace:4 rspace:4 # plus sign with tilde above
+operator.\u2A25.infix = lspace:4 rspace:4 # plus sign with dot below
+operator.\u2A26.infix = lspace:4 rspace:4 # plus sign with tilde below
+operator.\u2A27.infix = lspace:4 rspace:4 # plus sign with subscript two
+operator.\u2A28.infix = lspace:4 rspace:4 # plus sign with black triangle
+operator.\u2A29.infix = lspace:4 rspace:4 # minus sign with comma above
+operator.\u2A2A.infix = lspace:4 rspace:4 # minus sign with dot below
+operator.\u2A2B.infix = lspace:4 rspace:4 # minus sign with falling dots
+operator.\u2A2C.infix = lspace:4 rspace:4 # minus sign with rising dots
+operator.\u2A2D.infix = lspace:4 rspace:4 # plus sign in left half circle
+operator.\u2A2E.infix = lspace:4 rspace:4 # plus sign in right half circle
+operator.\u2A2F.infix = lspace:4 rspace:4 # &Cross;
+operator.\u2A30.infix = lspace:4 rspace:4 # multiplication sign with dot above
+operator.\u2A31.infix = lspace:4 rspace:4 # multiplication sign with underbar
+operator.\u2A32.infix = lspace:4 rspace:4 # semidirect product with bottom closed
+operator.\u2A33.infix = lspace:4 rspace:4 # smash product
+operator.\u2A34.infix = lspace:4 rspace:4 # multiplication sign in left half circle
+operator.\u2A35.infix = lspace:4 rspace:4 # multiplication sign in right half circle
+operator.\u2A36.infix = lspace:4 rspace:4 # circled multiplication sign with circumflex accent
+operator.\u2A37.infix = lspace:4 rspace:4 # multiplication sign in double circle
+operator.\u2A38.infix = lspace:4 rspace:4 # circled division sign
+operator.\u2A39.infix = lspace:4 rspace:4 # plus sign in triangle
+operator.\u2A3A.infix = lspace:4 rspace:4 # minus sign in triangle
+operator.\u2A3B.infix = lspace:4 rspace:4 # multiplication sign in triangle
+operator.\u2A3C.infix = lspace:4 rspace:4 # interior product
+operator.\u2A3D.infix = lspace:4 rspace:4 # righthand interior product
+operator.\u2A3E.infix = lspace:4 rspace:4 # z notation relational composition
+operator.\u2A3F.infix = lspace:4 rspace:4 # amalgamation or coproduct
+operator.\u2A40.infix = lspace:4 rspace:4 # intersection with dot
+operator.\u2A41.infix = lspace:4 rspace:4 # union with minus sign
+operator.\u2A42.infix = lspace:4 rspace:4 # union with overbar
+operator.\u2A43.infix = lspace:4 rspace:4 # intersection with overbar
+operator.\u2A44.infix = lspace:4 rspace:4 # intersection with logical and
+operator.\u2A45.infix = lspace:4 rspace:4 # union with logical or
+operator.\u2A46.infix = lspace:4 rspace:4 # union above intersection
+operator.\u2A47.infix = lspace:4 rspace:4 # intersection above union
+operator.\u2A48.infix = lspace:4 rspace:4 # union above bar above intersection
+operator.\u2A49.infix = lspace:4 rspace:4 # intersection above bar above union
+operator.\u2A4A.infix = lspace:4 rspace:4 # union beside and joined with union
+operator.\u2A4B.infix = lspace:4 rspace:4 # intersection beside and joined with intersection
+operator.\u2A4C.infix = lspace:4 rspace:4 # closed union with serifs
+operator.\u2A4D.infix = lspace:4 rspace:4 # closed intersection with serifs
+operator.\u2A4E.infix = lspace:4 rspace:4 # double square intersection
+operator.\u2A4F.infix = lspace:4 rspace:4 # double square union
+operator.\u2A50.infix = lspace:4 rspace:4 # closed union with serifs and smash product
+operator.\u2A51.infix = lspace:4 rspace:4 # logical and with dot above
+operator.\u2A52.infix = lspace:4 rspace:4 # logical or with dot above
+operator.\u2A53.infix = lspace:4 rspace:4 direction:vertical # &And;
+operator.\u2A54.infix = lspace:4 rspace:4 direction:vertical # &Or;
+operator.\u2A55.infix = lspace:4 rspace:4 # two intersecting logical and
+operator.\u2A56.infix = lspace:4 rspace:4 # two intersecting logical or
+operator.\u2A57.infix = lspace:4 rspace:4 # sloping large or
+operator.\u2A58.infix = lspace:4 rspace:4 # sloping large and
+operator.\u2A59.infix = lspace:5 rspace:5 # logical or overlapping logical and
+operator.\u2A5A.infix = lspace:4 rspace:4 # logical and with middle stem
+operator.\u2A5B.infix = lspace:4 rspace:4 # logical or with middle stem
+operator.\u2A5C.infix = lspace:4 rspace:4 # logical and with horizontal dash
+operator.\u2A5D.infix = lspace:4 rspace:4 # logical or with horizontal dash
+operator.\u2A5E.infix = lspace:4 rspace:4 # logical and with double overbar
+operator.\u2A5F.infix = lspace:4 rspace:4 # logical and with underbar
+operator.\u2A60.infix = lspace:4 rspace:4 # logical and with double underbar
+operator.\u2A61.infix = lspace:4 rspace:4 # small vee with underbar
+operator.\u2A62.infix = lspace:4 rspace:4 # logical or with double overbar
+operator.\u2A63.infix = lspace:4 rspace:4 # logical or with double underbar
+operator.\u2A64.infix = lspace:4 rspace:4 # z notation domain antirestriction
+operator.\u2A65.infix = lspace:4 rspace:4 # z notation range antirestriction
+operator.\u2A66.infix = lspace:5 rspace:5 # equals sign with dot below
+operator.\u2A67.infix = lspace:5 rspace:5 # identical with dot above
+operator.\u2A68.infix = lspace:5 rspace:5 # triple horizontal bar with double vertical stroke
+operator.\u2A69.infix = lspace:5 rspace:5 # triple horizontal bar with triple vertical stroke
+operator.\u2A6A.infix = lspace:5 rspace:5 # tilde operator with dot above
+operator.\u2A6B.infix = lspace:5 rspace:5 # tilde operator with rising dots
+operator.\u2A6C.infix = lspace:5 rspace:5 # similar minus similar
+operator.\u2A6D.infix = lspace:5 rspace:5 # congruent with dot above
+operator.\u2A6E.infix = lspace:5 rspace:5 # equals with asterisk
+operator.\u2A6F.infix = lspace:5 rspace:5 # almost equal to with circumflex accent
+operator.\u2A70.infix = lspace:5 rspace:5 # approximately equal or equal to
+operator.\u2A71.infix = lspace:4 rspace:4 # equals sign above plus sign
+operator.\u2A72.infix = lspace:4 rspace:4 # plus sign above equals sign
+operator.\u2A73.infix = lspace:5 rspace:5 # equals sign above tilde operator
+operator.\u2A74.infix = lspace:5 rspace:5 # double colon equal
+operator.\u2A75.infix = lspace:5 rspace:5 # &Equal;
+operator.\u2A76.infix = lspace:5 rspace:5 # three consecutive equals signs
+operator.\u2A77.infix = lspace:5 rspace:5 # equals sign with two dots above and two dots below
+operator.\u2A78.infix = lspace:5 rspace:5 # equivalent with four dots above
+operator.\u2A79.infix = lspace:5 rspace:5 # less-than with circle inside
+operator.\u2A7A.infix = lspace:5 rspace:5 # greater-than with circle inside
+operator.\u2A7B.infix = lspace:5 rspace:5 # less-than with question mark above
+operator.\u2A7C.infix = lspace:5 rspace:5 # greater-than with question mark above
+operator.\u2A7D.infix = lspace:5 rspace:5 # &LessSlantEqual;
+operator.\u2A7D\u0338.infix = lspace:5 rspace:5 # &NotLessSlantEqual;
+operator.\u2A7E.infix = lspace:5 rspace:5 # &GreaterSlantEqual;
+operator.\u2A7E\u0338.infix = lspace:5 rspace:5 # &NotGreaterSlantEqual;
+operator.\u2A7F.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot inside
+operator.\u2A80.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot inside
+operator.\u2A81.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot above
+operator.\u2A82.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot above
+operator.\u2A83.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot above right
+operator.\u2A84.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot above left
+operator.\u2A85.infix = lspace:5 rspace:5 # &lessapprox;
+operator.\u2A86.infix = lspace:5 rspace:5 # &gtrapprox;
+operator.\u2A87.infix = lspace:5 rspace:5 # less-than and single-line not equal to
+operator.\u2A88.infix = lspace:5 rspace:5 # greater-than and single-line not equal to
+operator.\u2A89.infix = lspace:5 rspace:5 # less-than and not approximate
+operator.\u2A8A.infix = lspace:5 rspace:5 # greater-than and not approximate
+operator.\u2A8B.infix = lspace:5 rspace:5 # &lesseqqgtr;
+operator.\u2A8C.infix = lspace:5 rspace:5 # &gtreqqless;
+operator.\u2A8D.infix = lspace:5 rspace:5 # less-than above similar or equal
+operator.\u2A8E.infix = lspace:5 rspace:5 # greater-than above similar or equal
+operator.\u2A8F.infix = lspace:5 rspace:5 # less-than above similar above greater-than
+operator.\u2A90.infix = lspace:5 rspace:5 # greater-than above similar above less-than
+operator.\u2A91.infix = lspace:5 rspace:5 # less-than above greater-than above double-line equal
+operator.\u2A92.infix = lspace:5 rspace:5 # greater-than above less-than above double-line equal
+operator.\u2A93.infix = lspace:5 rspace:5 # less-than above slanted equal above greater-than above slanted equal
+operator.\u2A94.infix = lspace:5 rspace:5 # greater-than above slanted equal above less-than above slanted equal
+operator.\u2A95.infix = lspace:5 rspace:5 # slanted equal to or less-than
+operator.\u2A96.infix = lspace:5 rspace:5 # slanted equal to or greater-than
+operator.\u2A97.infix = lspace:5 rspace:5 # slanted equal to or less-than with dot inside
+operator.\u2A98.infix = lspace:5 rspace:5 # slanted equal to or greater-than with dot inside
+operator.\u2A99.infix = lspace:5 rspace:5 # double-line equal to or less-than
+operator.\u2A9A.infix = lspace:5 rspace:5 # double-line equal to or greater-than
+operator.\u2A9B.infix = lspace:5 rspace:5 # double-line slanted equal to or less-than
+operator.\u2A9C.infix = lspace:5 rspace:5 # double-line slanted equal to or greater-than
+operator.\u2A9D.infix = lspace:5 rspace:5 # similar or less-than
+operator.\u2A9E.infix = lspace:5 rspace:5 # similar or greater-than
+operator.\u2A9F.infix = lspace:5 rspace:5 # similar above less-than above equals sign
+operator.\u2AA0.infix = lspace:5 rspace:5 # similar above greater-than above equals sign
+operator.\u2AA1.infix = lspace:5 rspace:5 # &LessLess;
+operator.\u2AA1\u0338.infix = lspace:5 rspace:5 # &NotNestedLessLess;
+operator.\u2AA2.infix = lspace:5 rspace:5 # &GreaterGreater;
+operator.\u2AA2\u0338.infix = lspace:5 rspace:5 # &NotNestedGreaterGreater;
+operator.\u2AA3.infix = lspace:5 rspace:5 # double nested less-than with underbar
+operator.\u2AA4.infix = lspace:5 rspace:5 # greater-than overlapping less-than
+operator.\u2AA5.infix = lspace:5 rspace:5 # greater-than beside less-than
+operator.\u2AA6.infix = lspace:5 rspace:5 # less-than closed by curve
+operator.\u2AA7.infix = lspace:5 rspace:5 # greater-than closed by curve
+operator.\u2AA8.infix = lspace:5 rspace:5 # less-than closed by curve above slanted equal
+operator.\u2AA9.infix = lspace:5 rspace:5 # greater-than closed by curve above slanted equal
+operator.\u2AAA.infix = lspace:5 rspace:5 # smaller than
+operator.\u2AAB.infix = lspace:5 rspace:5 # larger than
+operator.\u2AAC.infix = lspace:5 rspace:5 # smaller than or equal to
+operator.\u2AAD.infix = lspace:5 rspace:5 # larger than or equal to
+operator.\u2AAE.infix = lspace:5 rspace:5 # equals sign with bumpy above
+operator.\u2AAF.infix = lspace:5 rspace:5 # &PrecedesEqual;
+operator.\u2AAF\u0338.infix = lspace:5 rspace:5 # &NotPrecedesEqual;
+operator.\u2AB0.infix = lspace:5 rspace:5 # &SucceedsEqual;
+operator.\u2AB0\u0338.infix = lspace:5 rspace:5 # &NotSucceedsEqual;
+operator.\u2AB1.infix = lspace:5 rspace:5 # precedes above single-line not equal to
+operator.\u2AB2.infix = lspace:5 rspace:5 # succeeds above single-line not equal to
+operator.\u2AB3.infix = lspace:5 rspace:5 # &prE;
+operator.\u2AB4.infix = lspace:5 rspace:5 # &scE;
+operator.\u2AB5.infix = lspace:5 rspace:5 # precedes above not equal to
+operator.\u2AB6.infix = lspace:5 rspace:5 # succeeds above not equal to
+operator.\u2AB7.infix = lspace:5 rspace:5 # &precapprox;
+operator.\u2AB8.infix = lspace:5 rspace:5 # &succapprox;
+operator.\u2AB9.infix = lspace:5 rspace:5 # precedes above not almost equal to
+operator.\u2ABA.infix = lspace:5 rspace:5 # succeeds above not almost equal to
+operator.\u2ABB.infix = lspace:5 rspace:5 # double precedes
+operator.\u2ABC.infix = lspace:5 rspace:5 # double succeeds
+operator.\u2ABD.infix = lspace:5 rspace:5 # subset with dot
+operator.\u2ABE.infix = lspace:5 rspace:5 # superset with dot
+operator.\u2ABF.infix = lspace:5 rspace:5 # subset with plus sign below
+operator.\u2AC0.infix = lspace:5 rspace:5 # superset with plus sign below
+operator.\u2AC1.infix = lspace:5 rspace:5 # subset with multiplication sign below
+operator.\u2AC2.infix = lspace:5 rspace:5 # superset with multiplication sign below
+operator.\u2AC3.infix = lspace:5 rspace:5 # subset of or equal to with dot above
+operator.\u2AC4.infix = lspace:5 rspace:5 # superset of or equal to with dot above
+operator.\u2AC5.infix = lspace:5 rspace:5 # &subseteqq;
+operator.\u2AC6.infix = lspace:5 rspace:5 # &supseteqq;
+operator.\u2AC7.infix = lspace:5 rspace:5 # subset of above tilde operator
+operator.\u2AC8.infix = lspace:5 rspace:5 # superset of above tilde operator
+operator.\u2AC9.infix = lspace:5 rspace:5 # subset of above almost equal to
+operator.\u2ACA.infix = lspace:5 rspace:5 # superset of above almost equal to
+operator.\u2ACB.infix = lspace:5 rspace:5 # subset of above not equal to
+operator.\u2ACC.infix = lspace:5 rspace:5 # superset of above not equal to
+operator.\u2ACD.infix = lspace:5 rspace:5 # square left open box operator
+operator.\u2ACE.infix = lspace:5 rspace:5 # square right open box operator
+operator.\u2ACF.infix = lspace:5 rspace:5 # closed subset
+operator.\u2AD0.infix = lspace:5 rspace:5 # closed superset
+operator.\u2AD1.infix = lspace:5 rspace:5 # closed subset or equal to
+operator.\u2AD2.infix = lspace:5 rspace:5 # closed superset or equal to
+operator.\u2AD3.infix = lspace:5 rspace:5 # subset above superset
+operator.\u2AD4.infix = lspace:5 rspace:5 # superset above subset
+operator.\u2AD5.infix = lspace:5 rspace:5 # subset above subset
+operator.\u2AD6.infix = lspace:5 rspace:5 # superset above superset
+operator.\u2AD7.infix = lspace:5 rspace:5 # superset beside subset
+operator.\u2AD8.infix = lspace:5 rspace:5 # superset beside and joined by dash with subset
+operator.\u2AD9.infix = lspace:5 rspace:5 # element of opening downwards
+operator.\u2ADA.infix = lspace:5 rspace:5 # pitchfork with tee top
+operator.\u2ADB.infix = lspace:5 rspace:5 # transversal intersection
+operator.\u2ADC.infix = lspace:5 rspace:5 # forking
+operator.\u2ADD.infix = lspace:5 rspace:5 # nonforking
+operator.\u2ADD\u0338.infix = lspace:5 rspace:5 # nonforking with slash
+operator.\u2ADE.infix = lspace:5 rspace:5 # short left tack
+operator.\u2ADF.infix = lspace:5 rspace:5 # short down tack
+operator.\u2AE0.infix = lspace:5 rspace:5 # short up tack
+operator.\u2AE1.infix = lspace:5 rspace:5 # perpendicular with s
+operator.\u2AE2.infix = lspace:5 rspace:5 # vertical bar triple right turnstile
+operator.\u2AE3.infix = lspace:5 rspace:5 # double vertical bar left turnstile
+operator.\u2AE4.infix = lspace:5 rspace:5 # &DoubleLeftTee;
+operator.\u2AE5.infix = lspace:5 rspace:5 # double vertical bar double left turnstile
+operator.\u2AE6.infix = lspace:5 rspace:5 # long dash from left member of double vertical
+operator.\u2AE7.infix = lspace:5 rspace:5 # short down tack with overbar
+operator.\u2AE8.infix = lspace:5 rspace:5 # short up tack with underbar
+operator.\u2AE9.infix = lspace:5 rspace:5 # short up tack above short down tack
+operator.\u2AEA.infix = lspace:5 rspace:5 # double down tack
+operator.\u2AEB.infix = lspace:5 rspace:5 # double up tack
+operator.\u2AEC.infix = lspace:5 rspace:5 # double stroke not sign
+operator.\u2AED.infix = lspace:5 rspace:5 # reversed double stroke not sign
+operator.\u2AEE.infix = lspace:5 rspace:5 # does not divide with reversed negation slash
+operator.\u2AEF.infix = lspace:5 rspace:5 # vertical line with circle above
+operator.\u2AF0.infix = lspace:5 rspace:5 # vertical line with circle below
+operator.\u2AF1.infix = lspace:5 rspace:5 # down tack with circle below
+operator.\u2AF2.infix = lspace:5 rspace:5 # parallel with horizontal stroke
+operator.\u2AF3.infix = lspace:5 rspace:5 # parallel with tilde operator
+operator.\u2AF4.infix = lspace:4 rspace:4 # triple vertical bar binary relation
+operator.\u2AF5.infix = lspace:4 rspace:4 # triple vertical bar with horizontal stroke
+operator.\u2AF6.infix = lspace:4 rspace:4 # triple colon operator
+operator.\u2AF7.infix = lspace:5 rspace:5 # triple nested less-than
+operator.\u2AF8.infix = lspace:5 rspace:5 # triple nested greater-than
+operator.\u2AF9.infix = lspace:5 rspace:5 # double-line slanted less-than or equal to
+operator.\u2AFA.infix = lspace:5 rspace:5 # double-line slanted greater-than or equal to
+operator.\u2AFB.infix = lspace:4 rspace:4 # triple solidus binary relation
+operator.\u2AFC.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # large triple vertical bar operator
+operator.\u2AFD.infix = lspace:4 rspace:4 # double solidus operator
+operator.\u2AFE.infix = lspace:3 rspace:3 # white vertical bar
+operator.\u2AFF.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # n-ary white vertical bar
+operator.\u2B45.infix = lspace:5 rspace:5 stretchy direction:horizontal # leftwards quadruple arrow
+operator.\u2B46.infix = lspace:5 rspace:5 stretchy direction:horizontal # rightwards quadruple arrow
+
+# Entries below are not part of the official MathML dictionary
+
+operator.\u0021.prefix = lspace:0 rspace:5 # !
+operator.\u0026.infix = lspace:5 rspace:5 # &amp;
+operator.\u0026.prefix = lspace:0 rspace:5 # &amp;
+operator.\u002B\u002B.prefix = lspace:0 rspace:2 # ++
+operator.\u002D\u002D.prefix = lspace:0 rspace:2 # --
+operator.\u003B.postfix = lspace:0 rspace:0 separator # ;
+operator.\u003C\u20D2.infix = lspace:5 rspace:5 # &nvlt;
+operator.\u003E\u20D2.infix = lspace:5 rspace:5 # &nvgt;
+operator.\u006C\u0069\u006D.prefix = lspace:0 rspace:3 movablelimits # lim
+operator.\u006D\u0061\u0078.prefix = lspace:0 rspace:3 movablelimits # max
+operator.\u006D\u0069\u006E.prefix = lspace:0 rspace:3 movablelimits # min
+operator.\u007E.infix = lspace:2 rspace:2 stretchy direction:horizontal # ~
+operator.\u0332.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBar;
+operator.\u2016.infix = lspace:5 rspace:5 stretchy direction:vertical # &Vert; &Verbar;
+operator.\u20D0.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D0;
+operator.\u20D1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D1;
+operator.\u20D6.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D6;
+operator.\u20D7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D7;
+operator.\u20E1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20E1;
+operator.\u2190\u200B.infix = lspace:5 rspace:5 # &ShortLeftArrow;
+operator.\u2191\u200B.infix = lspace:2 rspace:2 # &ShortUpArrow;
+operator.\u2192\u200B.infix = lspace:5 rspace:5 # &ShortRightArrow;
+operator.\u2193\u200B.infix = lspace:2 rspace:2 # &ShortDownArrow;
+operator.\u2223.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &VerticalBar;
+operator.\u2223.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &VerticalBar;
+operator.\u2225.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &DoubleVerticalBar;
+operator.\u2225.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &DoubleVerticalBar;
+operator.\u2282\u020D2.infix = lspace:5 rspace:5 # &NotSubset;
+operator.\u2283\u020D2.infix = lspace:5 rspace:5 # &NotSuperset;
+operator.\u228E.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &UnionPlus;
+operator.\u2295.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CirclePlus;
+operator.\u2296.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleMinus;
+operator.\u2297.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleTimes;
+operator.\u2299.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleDot;
+operator.\u23B0.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &lmoustache; &lmoust;
+operator.\u23B1.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &rmoustache; &rmoust;
+operator.\u2500.infix = lspace:0 rspace:0 stretchy direction:horizontal # &HorizontalLine;
+operator.\u25A1.prefix = lspace:0 rspace:2 # &Square;
+operator.\u2606.infix = lspace:3 rspace:3 # &star;
+operator.\u2AC5\u0338.infix = lspace:5 rspace:5 # &nsubseteqq;
+operator.\u2AC6\u0338.infix = lspace:5 rspace:5 # &nsubseteqq;
+operator.\u2AEC.prefix = lspace:0 rspace:5 # &Not;
+operator.\uFE35.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverParenthesis; (MathML 2.0)
+operator.\uFE36.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderParenthesis; (MathML 2.0)
+operator.\uFE37.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBrace; (MathML 2.0)
+operator.\uFE38.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBrace; (MathML 2.0)
+
+
+##################################################################################
+# DOCUMENTATION ON HOW TO SETUP THE PROPERTY FILE ASSOCIATED TO EACH FONT
+# More fonts can be supported for stretchy characters by setting up mathfont
+# property files as described below.
+#
+# Each font should have its set of glyph data. For example, the glyph data for
+# the "Symbol" font and the "MT Extra" font are in "mathfontSymbol.properties"
+# and "mathfontMTExtra.properties", respectively. The font property file is a
+# set of all the stretchy MathML characters that can be rendered with that font
+# using larger and/or partial glyphs. Each stretchy character is associated to
+# a list in the font property file which gives, in that order, the 4 partial
+# glyphs: top (or left), middle, bottom (or right), glue; and the variants of
+# bigger sizes (if any). A position that is not relevant to a particular character
+# is indicated there with the UNICODE REPLACEMENT CHARACTER 0xFFFD.
diff --git a/layout/mathml/mathfontSTIXGeneral.properties b/layout/mathml/mathfontSTIXGeneral.properties
new file mode 100644
index 000000000..55229f90b
--- /dev/null
+++ b/layout/mathml/mathfontSTIXGeneral.properties
@@ -0,0 +1,128 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE: FILE
+# Do not translate anything in this file
+
+# This file contains the list of some stretchy MathML chars that can be
+# rendered with the STIXGeneral set.
+
+external.1 = STIXSizeOneSym
+external.2 = STIXSizeTwoSym
+external.3 = STIXSizeThreeSym
+external.4 = STIXSizeFourSym
+external.5 = STIXSizeFiveSym
+external.6 = STIXIntegralsD
+external.7 = STIXNonUnicode
+
+############
+# 1) Constructions from mathfontSTIXSizeOneSym.properties (bug 1007093) #
+
+# [ T/L | M | B/R | G | size0 ... size{N-1} ]
+\u0028 = \u239B@1\uFFFD\u239D@1\u239C@1\uFFFD(@1(@2(@3(@4 # (
+\u0029 = \u239E@1\uFFFD\u23A0@1\u239F@1\uFFFD)@1)@2)@3)@4 # )
+\u005B = \u23A1@1\uFFFD\u23A3@1\u23A2@1\u005B@1[@1[@2[@3[@4 # [
+\u005D = \u23A4@1\uFFFD\u23A6@1\u23A5@1\u005D@1]@1]@2]@3]@4 # ]
+\u007B = \u23A7@1\u23A8@1\u23A9@1\u23AA@1\u007B@1{@1{@2{@3{@4 # {
+\u007D = \u23AB@1\u23AC@1\u23AD@1\u23AA@1\u007D@1}@1}@2}@3}@4 # }
+
+# N-ARY operators
+\u2140 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2140@1 # DOUBLE-STRUCK N-ARY SUMMATION
+\u220F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u220F@1 # N-ARY PRODUCT
+\u2210 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2210@1 # N-ARY COPRODUCT
+\u2211 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2211@1 # N-ARY SUMMATION
+\u22C0 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C0@1 # N-ARY LOGICAL AND
+\u22C1 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C1@1 # N-ARY LOGICAL OR
+\u22C2 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C2@1 # N-ARY INTERSECTION
+\u22C3 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C3@1 # N-ARY UNION
+\u2A00 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A00@1 # N-ARY CIRCLED DOT OPERATOR
+\u2A01 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A01@1 # N-ARY CIRCLED PLUS OPERATOR
+\u2A02 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A02@1 # N-ARY CIRCLED TIMES OPERATOR
+\u2A03 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A03@1 # N-ARY UNION OPERATOR WITH DOT
+\u2A04 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A04@1 # N-ARY UNION OPERATOR WITH PLUS
+\u2A05 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A05@1 # N-ARY SQUARE INTERSECTION OPERATOR
+\u2A06 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A06@1 # N-ARY SQUARE UNION OPERATOR
+\u2A09 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A09@1 # N-ARY TIMES OPERATOR
+\u2AFF = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2AFF@1 # N-ARY WHITE VERTICAL BAR
+
+# E000 stix-radical symbol vertical extender
+# E001 stix-radical symbol top corner
+\u221A = \uE001@7\uFFFD\u221A@4\uE000@7\uFFFD\u221A@1\u221A@2\u221A@3 # Sqrt, radic
+
+# Integrals
+\u222B = \u2320@1\uFFFD\u2321@1\u23AE@1\uFFFD@1\u222B@6
+\u222C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222C@6
+\u222D = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222D@6
+\u222E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222E@6
+\u222F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222F@6
+\u2230 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2230@6
+\u2231 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2231@6
+\u2232 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2232@6
+\u2233 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2233@6
+\u2A0B = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0B@6
+\u2A0C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0C@6
+\u2A0D = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0D@6
+\u2A0E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0E@6
+\u2A0F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0F@6
+\u2A10 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A10@6
+\u2A11 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A11@6
+\u2A12 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A12@6
+\u2A13 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A13@6
+\u2A14 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A14@6
+\u2A15 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A15@6
+\u2A16 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A16@6
+\u2A17 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A17@6
+\u2A18 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A18@6
+\u2A19 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A19@6
+\u2A1A = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1A@6
+\u2A1B = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1B@6
+\u2A1C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1C@6
+
+\u27E8 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u27E8@1\u27E8@2\u27E8@3\u27E8@4 # LeftAngleBracket
+\u27E9 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u27E9@1\u27E9@2\u27E9@3\u27E9@4 # RightAngleBracket
+
+\u23DE = \uE13B@7\uE140@7\uE13C@7\uE14A@7\uFFFD\u23DE@1\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # &OverBrace; (Unicode)
+\uFE37 = \uE13B@7\uE140@7\uE13C@7\uE14A@7\uFFFD\u23DE@1\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # &OverBrace; (MathML 2.0)
+\u23B4 = \uE146@7\uFFFD\uE147@7\uE14A@7\uFFFD\u23B4@1\u23B4@2\u23B4@3\u23B4@4\u23B4@5 # &OverBracket;
+\u23DC = \uE142@7\uFFFD\uE143@7\uE14A@7\uFFFD\u23DC@1\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # &OverParenthesis; (Unicode)
+\uFE35 = \uE142@7\uFFFD\uE143@7\uE14A@7\uFFFD\u23DC@1\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # &OverParenthesis; (MathML 2.0)
+\u23DF = \uE13D@7\uE141@7\uE13E@7\uE13F@7\uFFFD\u23DF@1\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # &UnderBrace; (Unicode)
+\uFE38 = \uE13D@7\uE141@7\uE13E@7\uE13F@7\uFFFD\u23DF@1\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # &UnderBrace; (MathML 2.0)
+\u23B5 = \uE148@7\uFFFD\uE149@7\uE14B@7\uFFFD\u23B5@1\u23B5@2\u23B5@3\u23B5@4\u23B5@5 # &UnderBracket;
+\u23DD = \uE144@7\uFFFD\uE145@7\uE14B@7\uFFFD\u23DD@1\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # &UnderParenthesis; (Unicode)
+\uFE36 = \uE144@7\uFFFD\uE145@7\uE14B@7\uFFFD\u23DD@1\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # &UnderParenthesis; (MathML 2.0)
+
+\u005E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0302@1\u0302@2\u0302@3\u0302@4\u0302@5 # circumflex accent, COMBINING CIRCUMFLEX ACCENT
+\u02C6 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0302@1\u0302@2\u0302@3\u0302@4\u0302@5 # modifier letter circumflex accent, COMBINING CIRCUMFLEX ACCENT
+\u007E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0303@1\u0303@2\u0303@3\u0303@4\u0303@5 # ~ tilde, COMBINING TILDE
+\u02DC = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0303@1\u0303@2\u0303@3\u0303@4\u0303@5 # small tilde, COMBINING TILDE
+\u02C7 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u030C@1\u030C@2\u030C@3\u030C@4\u030C@5 # caron, COMBINING CARON
+
+############
+# 2) Constructions from mathfontSTIXNonUnicode.properties (bug 1007093) #
+
+# [ T/L | M | B/R | G | size0 ... size{N-1} ]
+# E0B4 stix-arrow hookleft
+# E0B5 stix-arrow hookright
+\u21A9 = \u2190\uFFFD\uE0B5@7\u23AF # hookleftarrow, larrhk
+\u21AA = \uE0B4@7\uFFFD\u2192\u23AF # hookrightarrow, rarrhk
+
+# 0E10E stix-stix-extender for vertical double arrow
+# 0E10F stix-extender for horizontal double arrow
+\u21D0 = \u21D0\uFFFD\uFFFD\uE10F@7\uFFFD\u27F8 # DoubleLeftArrow, Leftarrow, lArr
+\u21D1 = \u21D1\uFFFD\uFFFD\uE10E@7 # DoubleUpArrow, Uparrow, uArr
+\u21D2 = \uFFFD\uFFFD\u21D2\uE10F@7\uFFFD\u27F9 # DoubleRightArrow, Implies, Rightarrow, rArr
+\u21D3 = \uFFFD\uFFFD\u21D3\uE10E@7 # DoubleDownArrow, Downarrow, dArr
+\u21D4 = \u21D0\uFFFD\u21D2\uE10F@7\uFFFD\u27FA # DoubleLeftRightArrow, Leftrightarrow, hArr, iff
+\u21D5 = \u21D1\uFFFD\u21D3\uE10E@7 # DoubleUpDownArrow, Updownarrow, vArr
+
+# STIXGeneral U+22A2/U+22A3 RIGHT/LEFT TACK are different heights to U+23AF.
+# Could use LONG RIGHT/LEFT TACK instead, but STIXNonUnicode provides
+# E0B6 stix-maps-to-relation tail
+\u21A4 = \u2190\uFFFD\uE0B6@7\u23AF\uFFFD\u27FB # LeftTeeArrow, mapstoleft
+\u21A6 = \uE0B6@7\uFFFD\u2192\u23AF\uFFFD\u27FC # RightTeeArrow, map, mapsto
+\u295A = \u21BC\uFFFD\uE0B6@7\u23AF # LeftTeeVector
+\u295B = \uE0B6@7\uFFFD\u21C0\u23AF # RIGHTWARDS HARPOON WITH BARB UP FROM BAR, RightTeeVector
+\u295E = \u21BD\uFFFD\uE0B6@7\u23AF # DownLeftTeeVector
+\u295F = \uE0B6@7\uFFFD\u21C1\u23AF # RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR, DownRightTeeVector
diff --git a/layout/mathml/mathfontSymbol.properties b/layout/mathml/mathfontSymbol.properties
new file mode 100644
index 000000000..32984094c
--- /dev/null
+++ b/layout/mathml/mathfontSymbol.properties
@@ -0,0 +1,46 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE: FILE
+# Do not translate anything in this file
+
+# This file contains the list of all stretchy MathML chars that can be
+# rendered with Monotype's Symbol font.
+#
+# This file should normally only be installed on MS Windows systems as other
+# platforms have different fonts with the same family name but different
+# character codes.
+mathfont = Symbol
+
+# [ T/L | M | B/R | G | size0 ... size{N-1} ]
+\u0028 = \uF0E6\uFFFD\uF0E8\uF0E7\uF028 # (
+\u0029 = \uF0F6\uFFFD\uF0F8\uF0F7\uF029 # )
+\u005B = \uF0E9\uFFFD\uF0EB\uF0EA\uF05B # [
+\u005D = \uF0F9\uFFFD\uF0FB\uF0FA\uF05D # ]
+\u007B = \uF0EC\uF0ED\uF0EE\uF0EF\uF07B # {
+\u007C = \uFFFD\uFFFD\uFFFD\uF07C\uF07C # |
+\u007D = \uF0FC\uF0FD\uF0FE\uF0EF\uF07D # }
+
+\u00AF = \uFFFD\uFFFD\uFFFD\uF060\uF060 # ad-hoc: overbar is stretched with the radical extender
+\u0332 = \uFFFD\uFFFD\uFFFD\uF05F\uF05F # ad-hoc: UnderBar (0x0332) is stretched with underscore
+
+\u2190 = \uF0AC\uFFFD\uFFFD\uF0BE\uF0AC # LeftArrow, larr, leftarrow
+\u2191 = \uF0AD\uFFFD\uFFFD\uF0BD\uF0AD # UpArrow, uarr, uparrow
+\u2192 = \uFFFD\uFFFD\uF0AE\uF0BE\uF0AE # RightArrow, rarr, rightarrow
+\u2193 = \uFFFD\uFFFD\uF0AF\uF0BD\uF0AF # DownArrow, darr, downarrow
+\u2194 = \uF0AC\uFFFD\uF0AE\uF0BE\uF0B4 # LeftRightArrow, harr, leftrightarrow
+\u2195 = \uF0AD\uFFFD\uF0AF\uF0BD # UpDownArrow, updownarrow, varr
+
+\u222B = \uF0F3\uFFFD\uF0F5\uF0F4\uF0F2 # Integral, int
+
+# Using parts of [ and ]
+\u2308 = \uF0E9\uFFFD\uFFFD\uF0EA\uF0E9 # LeftCeiling, lceil
+\u2309 = \uF0F9\uFFFD\uFFFD\uF0FA\uF0F9 # RightCeiling, rceil
+\u230A = \uFFFD\uFFFD\uF0EB\uF0EA\uF0EB # LeftFloor, lfloor
+\u230B = \uFFFD\uFFFD\uF0FB\uF0FA\uF0FB # RightFloor, rfloor
+
+# same as normal arrows
+\u27F5 = \uF0AC\uFFFD\uFFFD\uF0BE # LongLeftArrow
+\u27F6 = \uFFFD\uFFFD\uF0AE\uF0BE # LongRightArrow
+\u27F7 = \uF0AC\uFFFD\uF0AE\uF0BE # LongLeftRightArrow
diff --git a/layout/mathml/mathfontUnicode.properties b/layout/mathml/mathfontUnicode.properties
new file mode 100644
index 000000000..18bf5b1c0
--- /dev/null
+++ b/layout/mathml/mathfontUnicode.properties
@@ -0,0 +1,92 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE: FILE
+# Do not translate anything in this file
+
+# This file contains the list of all stretchy MathML chars that
+# can be rendered using only Unicode code points.
+
+# [ T/L | M | B/R | G | size0 ... size{N-1} ]
+\u0028 = \u239B\uFFFD\u239D\u239C\u0028 # (
+\u0029 = \u239E\uFFFD\u23A0\u239F\u0029 # )
+\u005B = \u23A1\uFFFD\u23A3\u23A2\u005B # [
+\u005D = \u23A4\uFFFD\u23A6\u23A5\u005D # ]
+\u007B = \u23A7\u23A8\u23A9\u23AA\u007B # {
+\u007C = \uFFFD\uFFFD\uFFFD\u007C\u007C # |
+\u007D = \u23AB\u23AC\u23AD\u23AA\u007D # }
+
+# OverBar is stretched with U+0305 COMBINING OVERLINE which "connects on left and right"
+\u00AF = \uFFFD\uFFFD\uFFFD\u0305\u00AF # OverBar
+#\u0305 doesn't appear to be referenced by the MathML spec
+\u203E = \uFFFD\uFFFD\uFFFD\u0305\u00AF # overline
+\u0332 = \uFFFD\uFFFD\uFFFD\u0332\u0332 # COMBINING LOW LINE, UnderBar
+\u005F = \uFFFD\uFFFD\uFFFD\u0332\u0332 # _ low line
+\u003D = \uFFFD\uFFFD\uFFFD\u003D\u003D # = equal sign
+
+\u2016 = \uFFFD\uFFFD\uFFFD\u2016\u2016 # DOUBLE VERTICAL LINE, Vert, Verbar
+
+\u2190 = \u2190\uFFFD\uFFFD\u23AF\u2190\u27F5 # LeftArrow, larr, leftarrow
+\u2191 = \u2191\uFFFD\uFFFD\u23D0\u2191 # UpArrow, uarr, uparrow
+\u2192 = \uFFFD\uFFFD\u2192\u23AF\u2192\u27F6 # RightArrow, rarr, rightarrow
+\u2193 = \uFFFD\uFFFD\u2193\u23D0\u2193 # DownArrow, darr, downarrow
+\u2194 = \u2190\uFFFD\u2192\u23AF\u2194\u27F7 # LeftRightArrow, harr, leftrightarrow
+\u2195 = \u2191\uFFFD\u2193\u23D0\u2195 # UpDownArrow, updownarrow, varr
+
+# For STIXGeneral U+22A2/U+22A3 RIGHT/LEFT TACK are different heights to U+23AF.
+# Could use LONG RIGHT/LEFT TACK instead, but STIXNonUnicode provides
+# E0B6 stix-maps-to-relation tail
+#\u21A4 = \u2190\uFFFD\u27DE\u23AF\u21A6\u27FB # LeftTeeArrow, mapstoleft
+#\u21A6 = \u27DD\uFFFD\u2192\u23AF\u21A6\u27FC # RightTeeArrow, map, mapsto
+#\u295A = \u21BC\uFFFD\u27DE\u23AF\u295A # LeftTeeVector
+#\u295B = \u27DD\uFFFD\u21C0\u23AF\u295B # RIGHTWARDS HARPOON WITH BARB UP FROM BAR, RightTeeVector
+#\u295E = \u21BD\uFFFD\u27DE\u23AF\u295E # DownLeftTeeVector
+#\u295F = \u27DD\uFFFD\u21C1\u23AF\u295F # RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR, DownRightTeeVector
+# Cambria Math does not have U+27DD/U+27DE
+\u21A4 = \u2190\uFFFD\u22A3\u23AF\u21A6\u27FB # LeftTeeArrow, mapstoleft
+\u21A6 = \u22A2\uFFFD\u2192\u23AF\u21A6\u27FC # RightTeeArrow, map, mapsto
+\u295A = \u21BC\uFFFD\u22A3\u23AF\u295A # LeftTeeVector
+\u295B = \u22A2\uFFFD\u21C0\u23AF\u295B # RIGHTWARDS HARPOON WITH BARB UP FROM BAR, RightTeeVector
+\u295E = \u21BD\uFFFD\u22A3\u23AF\u295E # DownLeftTeeVector
+\u295F = \u22A2\uFFFD\u21C1\u23AF\u295F # RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR, DownRightTeeVector
+
+\u21C0 = \uFFFD\uFFFD\u21C0\u23AF\u21C0 # RightVector, rharu, rightharpoonup
+\u21C1 = \uFFFD\uFFFD\u21C1\u23AF\u21C1 # DownRightVector, rhard, rightharpoon down
+\u21BC = \u21BC\uFFFD\uFFFD\u23AF\u21BC # LeftVector, leftharpoonup, lharu
+\u21BD = \u21BD\uFFFD\uFFFD\u23AF\u21BD # DownLeftVector, leftharpoondown, lhard
+\u21D0 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D0\u27F8 # DoubleLeftArrow, Leftarrow, lArr
+\u21D2 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D2\u27F9 # DoubleRightArrow, Implies, Rightarro
+\u21D4 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D4\u27FA # DoubleLeftRightArrow, Leftrightarrow, hArr, iff
+
+# \u221A radical may be made from RADICAL SYMBOL BOTTOM U+23B7 but few fonts
+# support this character and it is not clear what the appropriate vertical
+# glue whould be.
+
+\u2223 = \uFFFD\uFFFD\uFFFD\u2223\u2223 # VerticalBar, mid
+\u2225 = \uFFFD\uFFFD\uFFFD\u2225\u2225 # DoubleVerticalBar, par, parallel
+
+# If fonts have U+23AE INTEGRAL EXTENSION:
+# (STIXSize1, Cambria Math, DejaVu Sans/Serif, Apple's Symbol)
+\u222B = \u2320\uFFFD\u2321\u23AE\u222B # Integral, int
+# Many fonts don't have U+23AE. For these fonts, a rule can be used as glue:
+# \u222B = \u2320\uFFFD\u2321\uFFFD\u222B # Integral, int
+
+# Using parts of [ and ] (could use box drawings instead)
+\u2308 = \u23A1\uFFFD\uFFFD\u23A2\u2308 # LeftCeiling, lceil
+\u2309 = \u23A4\uFFFD\uFFFD\u23A5\u2309 # RightCeiling, rceil
+\u230A = \uFFFD\uFFFD\u23A3\u23A2\u230A # LeftFloor, lfloor
+\u230B = \uFFFD\uFFFD\u23A6\u23A5\u230B # RightFloor, rfloor
+
+# Support for l/r moustache from the parts of lbrace { and rbrace }
+\u23B0 = \u23A7\uFFFD\u23AD\u23AA\u23B0 # lmoustache, lmoust
+\u23B1 = \u23AB\uFFFD\u23A9\u23AA\u23B1 # rmoustache, rmoust
+
+# Using normal arrows as heads instead of long arrows for the sake of
+# Apple's Symbol font.
+\u27F5 = \u2190\uFFFD\uFFFD\u23AF\u27F5 # LongLeftArrow
+\u27F6 = \uFFFD\uFFFD\u2192\u23AF\u27F6 # LongRightArrow
+\u27F7 = \u2190\uFFFD\u2192\u23AF\u27F7 # LongLeftRightArrow
+
+\u294E = \u21BC\uFFFD\u21C0\u23AF\u294E #LEFT BARB UP RIGHT BARB UP HARPOON, LeftRightVector
+\u2950 = \u21BD\uFFFD\u21C1\u23AF\u2950 #LEFT BARB DOWN RIGHT BARB DOWN HARPOON , DownLeftRightVector
diff --git a/layout/mathml/mathml.css b/layout/mathml/mathml.css
new file mode 100644
index 000000000..44a4c31c9
--- /dev/null
+++ b/layout/mathml/mathml.css
@@ -0,0 +1,338 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/**************************************************************************/
+/* namespace for MathML elements */
+/**************************************************************************/
+
+@namespace url(http://www.w3.org/1998/Math/MathML);
+
+/**************************************************************************/
+/* <math> - outermost math element */
+/**************************************************************************/
+
+math {
+ writing-mode: horizontal-tb !important;
+ direction: ltr;
+ unicode-bidi: embed;
+ display: inline;
+ font-size: inherit;
+ font-style: normal;
+ font-family: serif;
+ line-height: normal;
+ word-spacing: normal;
+ letter-spacing: normal;
+ text-rendering: optimizeLegibility;
+ -moz-float-edge: margin-box;
+ -moz-math-display: inline;
+}
+math[mode="display"], math[display="block"] {
+ display: block;
+ text-align: -moz-center;
+ -moz-math-display: block;
+}
+math[display="inline"] {
+ display: inline;
+ -moz-math-display: inline;
+}
+math[displaystyle="false"] {
+ -moz-math-display: inline;
+}
+math[displaystyle="true"] {
+ -moz-math-display: block;
+}
+
+/**************************************************************************/
+/* Token elements */
+/**************************************************************************/
+
+ms {
+ display: inline;
+}
+ms:before, ms:after {
+ content: "\0022"
+}
+ms[lquote]:before {
+ content: attr(lquote)
+}
+ms[rquote]:after {
+ content: attr(rquote)
+ }
+
+/**************************************************************************/
+/* Links */
+/**************************************************************************/
+:any-link {
+ text-decoration: none !important;
+}
+
+/**************************************************************************/
+/* attributes common to all tags */
+/**************************************************************************/
+
+/* These attributes are mapped to style in nsMathMLElement.cpp:
+
+ - background -> background (deprecated)
+ - color -> color (deprecated)
+ - fontfamily -> font-family (deprecated)
+ - fontsize -> font-size (deprecated)
+ - fontstyle -> font-style (deprecated)
+ - fontweight -> font-weight (deprecated)
+ - mathvariant -> -moz-math-variant
+ - scriptsizemultiplier -> -moz-script-size-multiplier
+ - scriptminsize -> -moz-script-min-size
+ - scriptlevel -> -moz-script-level
+ - mathsize -> font-size
+ - mathcolor -> color
+ - mathbackground -> background
+
+*/
+
+
+/**************************************************************************/
+/* merror */
+/**************************************************************************/
+
+merror {
+ display: block;
+ font-family: sans-serif;
+ font-weight: bold;
+ white-space: pre;
+ margin: 1em;
+ padding: 1em;
+ border-width: thin;
+ border-style: inset;
+ border-color: red;
+ font-size: 14pt;
+ background-color: lightyellow;
+}
+
+/**************************************************************************/
+/* mtable and its related tags */
+/**************************************************************************/
+
+mtable {
+ display: inline-table;
+ border-collapse: separate;
+ border-spacing: 0;
+ text-indent: 0;
+}
+mtable[frame="none"] {
+ border: none;
+}
+mtable[frame="solid"] {
+ border: solid thin;
+}
+mtable[frame="dashed"] {
+ border: dashed thin;
+}
+
+mtr, mlabeledtr {
+ display: table-row;
+ vertical-align: baseline;
+}
+
+mtd {
+ display: table-cell;
+ vertical-align: inherit;
+ text-align: -moz-center;
+ white-space: nowrap;
+}
+
+/* Don't support m(labeled)tr without mtable, nor mtd without m(labeled)tr */
+:not(mtable) > mtr,
+:not(mtable) > mlabeledtr,
+:not(mtr):not(mlabeledtr) > mtd {
+ display: none !important;
+}
+
+/* Hide the label because mlabeledtr is not supported yet (bug 356870). This
+ rule can be overriden by users. */
+mlabeledtr > mtd:first-child {
+ display: none;
+}
+
+/**********************************************************************/
+/* rules to achieve the default spacing between cells. When rowspacing,
+ columnspacing and framespacing aren't set on mtable. The back-end code
+ will set the internal attributes depending on the cell's position.
+ When they are set, the spacing behaviour is handled outside of CSS */
+mtd {
+ padding-right: 0.4em; /* half of columnspacing[colindex] */
+ padding-left: 0.4em; /* half of columnspacing[colindex-1] */
+ padding-bottom: 0.5ex; /* half of rowspacing[rowindex] */
+ padding-top: 0.5ex; /* half of rowspacing[rowindex-1] */
+}
+/* turn off the spacing at the periphery of boundary cells */
+mtr:first-child > mtd {
+ padding-top: 0ex;
+}
+mtr:last-child > mtd {
+ padding-bottom: 0ex;
+}
+mtd:first-child {
+ padding-inline-start: 0em;
+}
+mtd:last-child {
+ padding-inline-end: 0em;
+}
+/* re-instate the spacing if the table has a surrounding frame */
+mtable[frame="solid"] > mtr:first-child > mtd,
+mtable[frame="dashed"] > mtr:first-child > mtd {
+ padding-top: 0.5ex; /* framespacing.top */
+}
+mtable[frame="solid"] > mtr:last-child > mtd,
+mtable[frame="dashed"] > mtr:last-child > mtd {
+ padding-bottom: 0.5ex; /* framespacing.bottom */
+}
+mtable[frame="solid"] > mtr > mtd:first-child,
+mtable[frame="dashed"] > mtr > mtd:first-child {
+ padding-inline-start: 0.4em; /* framespacing.left (or right in rtl)*/
+}
+mtable[frame="solid"] > mtr > mtd:last-child,
+mtable[frame="dashed"] > mtr > mtd:last-child {
+ padding-inline-end: 0.4em; /* framespacing.right (or left in rtl)*/
+}
+
+mtable[rowspacing] > mtr > mtd,
+mtable[columnspacing] > mtr > mtd,
+mtable[framespacing] > mtr > mtd {
+ /* Spacing handled outside of CSS */
+ padding: 0px;
+}
+
+/**************************************************************************/
+/* This rule is used to give a style context suitable for nsMathMLChars.
+ We don't actually style -moz-math-anonymous by default. */
+/*
+::-moz-math-anonymous {
+}
+*/
+
+/**********************************************************************/
+/* This is used when wrapping non-MathML inline elements inside math. */
+*|*::-moz-mathml-anonymous-block {
+ display: inline-block !important;
+ position: static !important;
+ text-indent: 0;
+}
+
+/**************************************************************************/
+/* Controlling Displaystyle and Scriptlevel */
+/**************************************************************************/
+
+/*
+ http://www.w3.org/Math/draft-spec/chapter3.html#presm.scriptlevel
+
+ The determination of -moz-math-display for <math> involves the displaystyle
+ and display attributes. See the <math> section above.
+*/
+
+/*
+ Map mstyle@displaystyle to -moz-math-display.
+*/
+mstyle[displaystyle="false"] {
+ -moz-math-display: inline;
+}
+mstyle[displaystyle="true"] {
+ -moz-math-display: block;
+}
+
+/* munder, mover and munderover change the scriptlevels of their children
+ using -moz-math-increment-script-level because regular CSS rules are
+ insufficient to control when the scriptlevel should be incremented. All other
+ cases can be described using regular CSS, so we do it this way because it's
+ more efficient and less code. */
+:-moz-math-increment-script-level { -moz-script-level: +1; }
+
+/*
+ The mfrac element sets displaystyle to "false", or if it was already false
+ increments scriptlevel by 1, within numerator and denominator.
+*/
+mfrac > * {
+ -moz-script-level: auto;
+ -moz-math-display: inline;
+}
+
+/*
+ The mroot element increments scriptlevel by 2, and sets displaystyle to
+ "false", within index, but leaves both attributes unchanged within base.
+ The msqrt element leaves both attributes unchanged within its argument.
+*/
+mroot > :not(:first-child) {
+ -moz-script-level: +2;
+ -moz-math-display: inline;
+}
+
+/*
+ The msub element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within subscript, but leaves both attributes unchanged within base.
+
+ The msup element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within superscript, but leaves both attributes unchanged within
+ base.
+
+ The msubsup element [...] increments scriptlevel by 1, and sets displaystyle
+ to "false", within subscript and superscript, but leaves both attributes
+ unchanged within base.
+
+ The mmultiscripts element increments scriptlevel by 1, and sets displaystyle
+ to "false", within each of its arguments except base, but leaves both
+ attributes unchanged within base.
+ */
+msub > :not(:first-child),
+msup > :not(:first-child),
+msubsup > :not(:first-child),
+mmultiscripts > :not(:first-child) {
+ -moz-script-level: +1;
+ -moz-math-display: inline;
+}
+
+/*
+ The munder element [...] always sets displaystyle to "false" within the
+ underscript, but increments scriptlevel by 1 only when accentunder is
+ "false". Within base, it always leaves both attributes unchanged.
+
+ The mover element [...] always sets displaystyle to "false" within
+ overscript, but increments scriptlevel by 1 only when accent is "false".
+ Within base, it always leaves both attributes unchanged.
+
+ The munderover [..] always sets displaystyle to "false" within underscript
+ and overscript, but increments scriptlevel by 1 only when accentunder or
+ accent, respectively, are "false". Within base, it always leaves both
+ attributes unchanged.
+*/
+munder > :not(:first-child),
+mover > :not(:first-child),
+munderover > :not(:first-child) {
+ -moz-math-display: inline;
+}
+
+/*
+ The displaystyle attribute is allowed on the mtable element to set the
+ inherited value of the attribute. If the attribute is not present, the
+ mtable element sets displaystyle to "false" within the table elements.
+*/
+mtable { -moz-math-display: inline; }
+mtable[displaystyle="true"] { -moz-math-display: block; }
+
+/*
+ The mscarries element sets displaystyle to "false", and increments
+ scriptlevel by 1, so the children are typically displayed in a smaller font.
+ XXXfredw: This element is not implemented yet. See bug 534967.
+mscarries {
+ -moz-script-level: +1;
+ -moz-math-display: inline;
+}
+*/
+
+/* "The mphantom element renders invisibly, but with the same size and other
+ dimensions, including baseline position, that its contents would have if
+ they were rendered normally.".
+ Also, we do not expose the <mphantom> element to the accessible tree
+ (see bug 1108378). */
+mphantom {
+ visibility: hidden;
+}
diff --git a/layout/mathml/moz.build b/layout/mathml/moz.build
new file mode 100644
index 000000000..f40b2c2c6
--- /dev/null
+++ b/layout/mathml/moz.build
@@ -0,0 +1,72 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'MathML')
+
+if CONFIG['ENABLE_TESTS']:
+ MOCHITEST_MANIFESTS += [
+ 'imptests/mochitest.ini',
+ 'tests/mochitest.ini',
+]
+ MOCHITEST_CHROME_MANIFESTS += [
+ 'tests/chrome.ini',
+]
+
+UNIFIED_SOURCES += [
+ 'nsMathMLChar.cpp',
+ 'nsMathMLContainerFrame.cpp',
+ 'nsMathMLFrame.cpp',
+ 'nsMathMLmactionFrame.cpp',
+ 'nsMathMLmencloseFrame.cpp',
+ 'nsMathMLmfencedFrame.cpp',
+ 'nsMathMLmfracFrame.cpp',
+ 'nsMathMLmmultiscriptsFrame.cpp',
+ 'nsMathMLmoFrame.cpp',
+ 'nsMathMLmpaddedFrame.cpp',
+ 'nsMathMLmrootFrame.cpp',
+ 'nsMathMLmrowFrame.cpp',
+ 'nsMathMLmspaceFrame.cpp',
+ 'nsMathMLmsqrtFrame.cpp',
+ 'nsMathMLmtableFrame.cpp',
+ 'nsMathMLmunderoverFrame.cpp',
+ 'nsMathMLOperators.cpp',
+ 'nsMathMLSelectedFrame.cpp',
+ 'nsMathMLsemanticsFrame.cpp',
+ 'nsMathMLTokenFrame.cpp',
+]
+
+EXPORTS += [
+ 'nsIMathMLFrame.h',
+ 'nsMathMLOperators.h'
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '../base',
+ '../generic',
+ '../style',
+ '../tables',
+ '../xul',
+ '/dom/base',
+ '/dom/mathml',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+RESOURCE_FILES.fonts += [
+ 'mathfont.properties',
+ 'mathfontSTIXGeneral.properties',
+ 'mathfontUnicode.properties',
+]
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ RESOURCE_FILES.fonts += [
+ 'mathfontSymbol.properties',
+ ]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/layout/mathml/nsIMathMLFrame.h b/layout/mathml/nsIMathMLFrame.h
new file mode 100644
index 000000000..3d3c67018
--- /dev/null
+++ b/layout/mathml/nsIMathMLFrame.h
@@ -0,0 +1,387 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+//#define SHOW_BOUNDING_BOX 1
+#ifndef nsIMathMLFrame_h___
+#define nsIMathMLFrame_h___
+
+#include "nsQueryFrame.h"
+#include "nsMathMLOperators.h"
+
+struct nsPresentationData;
+struct nsEmbellishData;
+class nsRenderingContext;
+class nsIFrame;
+namespace mozilla {
+class ReflowOutput;
+} // namespace mozilla
+
+// For MathML, this 'type' will be used to determine the spacing between frames
+// Subclasses can return a 'type' that will give them a particular spacing
+enum eMathMLFrameType {
+ eMathMLFrameType_UNKNOWN = -1,
+ eMathMLFrameType_Ordinary,
+ eMathMLFrameType_OperatorOrdinary,
+ eMathMLFrameType_OperatorInvisible,
+ eMathMLFrameType_OperatorUserDefined,
+ eMathMLFrameType_Inner,
+ eMathMLFrameType_ItalicIdentifier,
+ eMathMLFrameType_UprightIdentifier,
+ eMathMLFrameType_COUNT
+};
+
+// Abstract base class that provides additional methods for MathML frames
+class nsIMathMLFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsIMathMLFrame)
+
+ // helper to check whether the frame is "space-like", as defined by the spec.
+ virtual bool IsSpaceLike() = 0;
+
+ /* SUPPORT FOR PRECISE POSITIONING */
+ /*====================================================================*/
+
+ /* Metrics that _exactly_ enclose the text of the frame.
+ * The frame *must* have *already* being reflowed, before you can call
+ * the GetBoundingMetrics() method.
+ * Note that for a frame with nested children, the bounding metrics
+ * will exactly enclose its children. For example, the bounding metrics
+ * of msub is the smallest rectangle that exactly encloses both the
+ * base and the subscript.
+ */
+ NS_IMETHOD
+ GetBoundingMetrics(nsBoundingMetrics& aBoundingMetrics) = 0;
+
+ NS_IMETHOD
+ SetBoundingMetrics(const nsBoundingMetrics& aBoundingMetrics) = 0;
+
+ NS_IMETHOD
+ SetReference(const nsPoint& aReference) = 0;
+
+ virtual eMathMLFrameType GetMathMLFrameType() = 0;
+
+ /* SUPPORT FOR STRETCHY ELEMENTS */
+ /*====================================================================*/
+
+ /* Stretch :
+ * Called to ask a stretchy MathML frame to stretch itself depending
+ * on its context.
+ *
+ * An embellished frame is treated in a special way. When it receives a
+ * Stretch() command, it passes the command to its embellished child and
+ * the stretched size is bubbled up from the inner-most <mo> frame. In other
+ * words, the stretch command descend through the embellished hierarchy.
+ *
+ * @param aStretchDirection [in] the direction where to attempt to
+ * stretch.
+ * @param aContainerSize [in] struct that suggests the maximumn size for
+ * the stretched frame. Only member data of the struct that are
+ * relevant to the direction are used (the rest is ignored).
+ * @param aDesiredStretchSize [in/out] On input the current size
+ * of the frame, on output the size after stretching.
+ */
+ NS_IMETHOD
+ Stretch(mozilla::gfx::DrawTarget* aDrawTarget,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aContainerSize,
+ mozilla::ReflowOutput& aDesiredStretchSize) = 0;
+
+ /* Get the mEmbellishData member variable. */
+
+ NS_IMETHOD
+ GetEmbellishData(nsEmbellishData& aEmbellishData) = 0;
+
+
+ /* SUPPORT FOR SCRIPTING ELEMENTS */
+ /*====================================================================*/
+
+ /* Get the mPresentationData member variable. */
+
+ NS_IMETHOD
+ GetPresentationData(nsPresentationData& aPresentationData) = 0;
+
+ /* InheritAutomaticData() / TransmitAutomaticData() :
+ * There are precise rules governing each MathML frame and its children.
+ * Properties such as the scriptlevel or the embellished nature of a frame
+ * depend on those rules. Also, certain properties that we use to emulate
+ * TeX rendering rules are frame-dependent too. These two methods are meant
+ * to be implemented by frame classes that need to assert specific properties
+ * within their subtrees.
+ *
+ * InheritAutomaticData() is called in a top-down manner [like nsIFrame::Init],
+ * as we descend the frame tree, whereas TransmitAutomaticData() is called in a
+ * bottom-up manner, as we ascend the tree [like nsIFrame::SetInitialChildList].
+ * However, unlike Init() and SetInitialChildList() which are called only once
+ * during the life-time of a frame (when initially constructing the frame tree),
+ * these two methods are called to build automatic data after the <math>...</math>
+ * subtree has been constructed fully, and are called again as we walk a child's
+ * subtree to handle dynamic changes that happen in the content model.
+ *
+ * As a rule of thumb:
+ *
+ * 1. Use InheritAutomaticData() to set properties related to your ancestors:
+ * - set properties that are intrinsic to yourself
+ * - set properties that depend on the state that you expect your ancestors
+ * to have already reached in their own InheritAutomaticData().
+ * - set properties that your descendants assume that you would have set in
+ * your InheritAutomaticData() -- this way, they can safely query them and
+ * the process will feed upon itself.
+ *
+ * 2. Use TransmitAutomaticData() to set properties related to your descendants:
+ * - set properties that depend on the state that you expect your descendants
+ * to have reached upon processing their own TransmitAutomaticData().
+ * - transmit properties that your descendants expect that you will transmit to
+ * them in your TransmitAutomaticData() -- this way, they remain up-to-date.
+ * - set properties that your ancestors expect that you would set in your
+ * TransmitAutomaticData() -- this way, they can safely query them and the
+ * process will feed upon itself.
+ */
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) = 0;
+
+ NS_IMETHOD
+ TransmitAutomaticData() = 0;
+
+ /* UpdatePresentationData:
+ * Updates the frame's compression flag.
+ * A frame becomes "compressed" (or "cramped") according to TeX rendering
+ * rules (TeXBook, Ch.17, p.140-141).
+ *
+ * @param aFlagsValues [in]
+ * The new values (e.g., compress) that are going to be
+ * updated.
+ *
+ * @param aWhichFlags [in]
+ * The flags that are relevant to this call. Since not all calls
+ * are meant to update all flags at once, aWhichFlags is used
+ * to distinguish flags that need to retain their existing values
+ * from flags that need to be turned on (or turned off). If a bit
+ * is set in aWhichFlags, then the corresponding value (which
+ * can be 0 or 1) is taken from aFlagsValues and applied to the
+ * frame. Therefore, by setting their bits in aWhichFlags, and
+ * setting their desired values in aFlagsValues, it is possible to
+ * update some flags in the frame, leaving the other flags unchanged.
+ */
+ NS_IMETHOD
+ UpdatePresentationData(uint32_t aFlagsValues,
+ uint32_t aWhichFlags) = 0;
+
+ /* UpdatePresentationDataFromChildAt :
+ * Sets compression flag on the whole tree. For child frames
+ * at aFirstIndex up to aLastIndex, this method sets their
+ * compression flags. The update is propagated down the subtrees of each of
+ * these child frames.
+ *
+ * @param aFirstIndex [in]
+ * Index of the first child from where the update is propagated.
+ *
+ * @param aLastIndex [in]
+ * Index of the last child where to stop the update.
+ * A value of -1 means up to last existing child.
+ *
+ * @param aFlagsValues [in]
+ * The new values (e.g., compress) that are going to be
+ * assigned in the whole sub-trees.
+ *
+ * @param aWhichFlags [in]
+ * The flags that are relevant to this call. See UpdatePresentationData()
+ * for more details about this parameter.
+ */
+ NS_IMETHOD
+ UpdatePresentationDataFromChildAt(int32_t aFirstIndex,
+ int32_t aLastIndex,
+ uint32_t aFlagsValues,
+ uint32_t aWhichFlags) = 0;
+
+ // If aFrame is a child frame, returns the script increment which this frame
+ // imposes on the specified frame, ignoring any artificial adjustments to
+ // scriptlevel.
+ // Returns 0 if the specified frame isn't a child frame.
+ virtual uint8_t
+ ScriptIncrement(nsIFrame* aFrame) = 0;
+
+ // Returns true if the frame is considered to be an mrow for layout purposes.
+ // This includes inferred mrows, but excludes <mrow> elements with a single
+ // child. In the latter case, the child is to be treated as if it wasn't
+ // within an mrow, so we pretend the mrow isn't mrow-like.
+ virtual bool
+ IsMrowLike() = 0;
+};
+
+// struct used by a container frame to keep track of its embellishments.
+// By convention, the data that we keep here is bubbled from the embellished
+// hierarchy, and it remains unchanged unless we have to recover from a change
+// that occurs in the embellished hierarchy. The struct remains in its nil
+// state in those frames that are not part of the embellished hierarchy.
+struct nsEmbellishData {
+ // bits used to mark certain properties of our embellishments
+ uint32_t flags;
+
+ // pointer on the <mo> frame at the core of the embellished hierarchy
+ nsIFrame* coreFrame;
+
+ // stretchy direction that the nsMathMLChar owned by the core <mo> supports
+ nsStretchDirection direction;
+
+ // spacing that may come from <mo> depending on its 'form'. Since
+ // the 'form' may also depend on the position of the outermost
+ // embellished ancestor, the set up of these values may require
+ // looking up the position of our ancestors.
+ nscoord leadingSpace;
+ nscoord trailingSpace;
+
+ nsEmbellishData() {
+ flags = 0;
+ coreFrame = nullptr;
+ direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ leadingSpace = 0;
+ trailingSpace = 0;
+ }
+};
+
+// struct used by a container frame to modulate its presentation.
+// By convention, the data that we keep in this struct can change depending
+// on any of our ancestors and/or descendants. If a data can be resolved
+// solely from the embellished hierarchy, and it remains immutable once
+// resolved, we put it in |nsEmbellishData|. If it can be affected by other
+// things, it comes here. This struct is updated as we receive information
+// transmitted by our ancestors and is kept in sync with changes in our
+// descendants that affects us.
+struct nsPresentationData {
+ // bits for: compressed, etc
+ uint32_t flags;
+
+ // handy pointer on our base child (the 'nucleus' in TeX), but it may be
+ // null here (e.g., tags like <mrow>, <mfrac>, <mtable>, etc, won't
+ // pick a particular child in their child list to be the base)
+ nsIFrame* baseFrame;
+
+ nsPresentationData() {
+ flags = 0;
+ baseFrame = nullptr;
+ }
+};
+
+// ==========================================================================
+// Bits used for the presentation flags -- these bits are set
+// in their relevant situation as they become available
+
+// This bit is used to emulate TeX rendering.
+// Internal use only, cannot be set by the user with an attribute.
+#define NS_MATHML_COMPRESSED 0x00000002U
+
+// This bit is set if the frame will fire a vertical stretch
+// command on all its (non-empty) children.
+// Tags like <mrow> (or an inferred mrow), mpadded, etc, will fire a
+// vertical stretch command on all their non-empty children
+#define NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY 0x00000004U
+
+// This bit is set if the frame will fire a horizontal stretch
+// command on all its (non-empty) children.
+// Tags like munder, mover, munderover, will fire a
+// horizontal stretch command on all their non-empty children
+#define NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY 0x00000008U
+
+// This bit is set if the frame is "space-like", as defined by the spec.
+#define NS_MATHML_SPACE_LIKE 0x00000040U
+
+// This bit is set if a token frame should be rendered with the dtls font
+// feature setting.
+#define NS_MATHML_DTLS 0x00000080U
+
+// This bit is set when the frame cannot be formatted due to an
+// error (e.g., invalid markup such as a <msup> without an overscript).
+// When set, a visual feedback will be provided to the user.
+#define NS_MATHML_ERROR 0x80000000U
+
+// a bit used for debug
+#define NS_MATHML_STRETCH_DONE 0x20000000U
+
+// This bit is used for visual debug. When set, the bounding box
+// of your frame is painted. This visual debug enable to ensure that
+// you have properly filled your mReference and mBoundingMetrics in
+// Place().
+#define NS_MATHML_SHOW_BOUNDING_METRICS 0x10000000U
+
+// Macros that retrieve those bits
+
+#define NS_MATHML_IS_COMPRESSED(_flags) \
+ (NS_MATHML_COMPRESSED == ((_flags) & NS_MATHML_COMPRESSED))
+
+#define NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(_flags) \
+ (NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY == ((_flags) & NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY))
+
+#define NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(_flags) \
+ (NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY == ((_flags) & NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY))
+
+#define NS_MATHML_IS_SPACE_LIKE(_flags) \
+ (NS_MATHML_SPACE_LIKE == ((_flags) & NS_MATHML_SPACE_LIKE))
+
+#define NS_MATHML_IS_DTLS_SET(_flags) \
+ (NS_MATHML_DTLS == ((_flags) & NS_MATHML_DTLS))
+
+#define NS_MATHML_HAS_ERROR(_flags) \
+ (NS_MATHML_ERROR == ((_flags) & NS_MATHML_ERROR))
+
+#define NS_MATHML_STRETCH_WAS_DONE(_flags) \
+ (NS_MATHML_STRETCH_DONE == ((_flags) & NS_MATHML_STRETCH_DONE))
+
+#define NS_MATHML_PAINT_BOUNDING_METRICS(_flags) \
+ (NS_MATHML_SHOW_BOUNDING_METRICS == ((_flags) & NS_MATHML_SHOW_BOUNDING_METRICS))
+
+// ==========================================================================
+// Bits used for the embellish flags -- these bits are set
+// in their relevant situation as they become available
+
+// This bit is set if the frame is an embellished operator.
+#define NS_MATHML_EMBELLISH_OPERATOR 0x00000001
+
+// This bit is set if the frame is an <mo> frame or an embellihsed
+// operator for which the core <mo> has movablelimits="true"
+#define NS_MATHML_EMBELLISH_MOVABLELIMITS 0x00000002
+
+// This bit is set if the frame is an <mo> frame or an embellihsed
+// operator for which the core <mo> has accent="true"
+#define NS_MATHML_EMBELLISH_ACCENT 0x00000004
+
+// This bit is set if the frame is an <mover> or <munderover> with
+// an accent frame
+#define NS_MATHML_EMBELLISH_ACCENTOVER 0x00000008
+
+// This bit is set if the frame is an <munder> or <munderover> with
+// an accentunder frame
+#define NS_MATHML_EMBELLISH_ACCENTUNDER 0x00000010
+
+// This bit is set on the core if it is a fence operator.
+#define NS_MATHML_EMBELLISH_FENCE 0x00000020
+
+// This bit is set on the core if it is a separator operator.
+#define NS_MATHML_EMBELLISH_SEPARATOR 0x00000040
+
+// Macros that retrieve those bits
+
+#define NS_MATHML_IS_EMBELLISH_OPERATOR(_flags) \
+ (NS_MATHML_EMBELLISH_OPERATOR == ((_flags) & NS_MATHML_EMBELLISH_OPERATOR))
+
+#define NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(_flags) \
+ (NS_MATHML_EMBELLISH_MOVABLELIMITS == ((_flags) & NS_MATHML_EMBELLISH_MOVABLELIMITS))
+
+#define NS_MATHML_EMBELLISH_IS_ACCENT(_flags) \
+ (NS_MATHML_EMBELLISH_ACCENT == ((_flags) & NS_MATHML_EMBELLISH_ACCENT))
+
+#define NS_MATHML_EMBELLISH_IS_ACCENTOVER(_flags) \
+ (NS_MATHML_EMBELLISH_ACCENTOVER == ((_flags) & NS_MATHML_EMBELLISH_ACCENTOVER))
+
+#define NS_MATHML_EMBELLISH_IS_ACCENTUNDER(_flags) \
+ (NS_MATHML_EMBELLISH_ACCENTUNDER == ((_flags) & NS_MATHML_EMBELLISH_ACCENTUNDER))
+
+#define NS_MATHML_EMBELLISH_IS_FENCE(_flags) \
+ (NS_MATHML_EMBELLISH_FENCE == ((_flags) & NS_MATHML_EMBELLISH_FENCE))
+
+#define NS_MATHML_EMBELLISH_IS_SEPARATOR(_flags) \
+ (NS_MATHML_EMBELLISH_SEPARATOR == ((_flags) & NS_MATHML_EMBELLISH_SEPARATOR))
+
+#endif /* nsIMathMLFrame_h___ */
diff --git a/layout/mathml/nsMathMLAtoms.h b/layout/mathml/nsMathMLAtoms.h
new file mode 100644
index 000000000..349e099ab
--- /dev/null
+++ b/layout/mathml/nsMathMLAtoms.h
@@ -0,0 +1,10 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLAtoms_h___
+#define nsMathMLAtoms_h___
+
+#include "nsGkAtoms.h"
+
+#endif /* nsMathMLAtoms_h___ */
diff --git a/layout/mathml/nsMathMLChar.cpp b/layout/mathml/nsMathMLChar.cpp
new file mode 100644
index 000000000..507e32b22
--- /dev/null
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -0,0 +1,2485 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLChar.h"
+
+#include "gfxTextRun.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Unused.h"
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsDeviceContext.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsUnicharUtils.h"
+#include "nsRenderingContext.h"
+
+#include "mozilla/Preferences.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/LookAndFeel.h"
+#include "nsCSSRendering.h"
+#include "mozilla/Sprintf.h"
+
+#include "nsDisplayList.h"
+
+#include "nsMathMLOperators.h"
+#include <algorithm>
+
+#include "gfxMathTable.h"
+#include "nsUnicodeScriptCodes.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+//#define NOISY_SEARCH 1
+
+// BUG 848725 Drawing failure with stretchy horizontal parenthesis when no fonts
+// are installed. "kMaxScaleFactor" is required to limit the scale for the
+// vertical and horizontal stretchy operators.
+static const float kMaxScaleFactor = 20.0;
+static const float kLargeOpFactor = float(M_SQRT2);
+static const float kIntegralFactor = 2.0;
+
+static void
+NormalizeDefaultFont(nsFont& aFont, float aFontSizeInflation)
+{
+ if (aFont.fontlist.GetDefaultFontType() != eFamily_none) {
+ aFont.fontlist.Append(FontFamilyName(aFont.fontlist.GetDefaultFontType()));
+ aFont.fontlist.SetDefaultFontType(eFamily_none);
+ }
+ aFont.size = NSToCoordRound(aFont.size * aFontSizeInflation);
+}
+
+// -----------------------------------------------------------------------------
+static const nsGlyphCode kNullGlyph = {{{0, 0}}, 0};
+
+// -----------------------------------------------------------------------------
+// nsGlyphTable is a class that provides an interface for accessing glyphs
+// of stretchy chars. It acts like a table that stores the variants of bigger
+// sizes (if any) and the partial glyphs needed to build extensible symbols.
+//
+// Bigger sizes (if any) of the char can then be retrieved with BigOf(...).
+// Partial glyphs can be retrieved with ElementAt(...).
+//
+// A table consists of "nsGlyphCode"s which are viewed either as Unicode
+// points (for nsPropertiesTable) or as direct glyph indices (for
+// nsOpenTypeTable)
+// -----------------------------------------------------------------------------
+
+class nsGlyphTable {
+public:
+ virtual ~nsGlyphTable() {}
+
+ virtual const FontFamilyName&
+ FontNameFor(const nsGlyphCode& aGlyphCode) const = 0;
+
+ // Getters for the parts
+ virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical,
+ uint32_t aPosition) = 0;
+ virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical,
+ uint32_t aSize) = 0;
+
+ // True if this table contains parts to render this char
+ virtual bool HasPartsOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical) = 0;
+
+ virtual already_AddRefed<gfxTextRun>
+ MakeTextRun(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ const nsGlyphCode& aGlyph) = 0;
+protected:
+ nsGlyphTable() : mCharCache(0) {}
+ // For speedy re-use, we always cache the last data used in the table.
+ // mCharCache is the Unicode point of the last char that was queried in this
+ // table.
+ char16_t mCharCache;
+};
+
+// An instance of nsPropertiesTable is associated with one primary font. Extra
+// glyphs can be taken in other additional fonts when stretching certain
+// characters.
+// These supplementary fonts are referred to as "external" fonts to the table.
+
+// General format of MathFont Property Files from which glyph data are
+// retrieved:
+// -----------------------------------------------------------------------------
+// Each font should have its set of glyph data. For example, the glyph data for
+// the "Symbol" font and the "MT Extra" font are in "mathfontSymbol.properties"
+// and "mathfontMTExtra.properties", respectively. The mathfont property file
+// is a set of all the stretchy MathML characters that can be rendered with that
+// font using larger and/or partial glyphs. The entry of each stretchy character
+// in the mathfont property file gives, in that order, the 4 partial glyphs:
+// Top (or Left), Middle, Bottom (or Right), Glue; and the variants of bigger
+// sizes (if any).
+// A position that is not relevant to a particular character is indicated there
+// with the UNICODE REPLACEMENT CHARACTER 0xFFFD.
+// -----------------------------------------------------------------------------
+
+#define NS_TABLE_STATE_ERROR -1
+#define NS_TABLE_STATE_EMPTY 0
+#define NS_TABLE_STATE_READY 1
+
+// helper to trim off comments from data in a MathFont Property File
+static void
+Clean(nsString& aValue)
+{
+ // chop the trailing # comment portion if any ...
+ int32_t comment = aValue.RFindChar('#');
+ if (comment > 0) aValue.Truncate(comment);
+ aValue.CompressWhitespace();
+}
+
+// helper to load a MathFont Property File
+static nsresult
+LoadProperties(const nsString& aName,
+ nsCOMPtr<nsIPersistentProperties>& aProperties)
+{
+ nsAutoString uriStr;
+ uriStr.AssignLiteral("resource://gre/res/fonts/mathfont");
+ uriStr.Append(aName);
+ uriStr.StripWhitespace(); // that may come from aName
+ uriStr.AppendLiteral(".properties");
+ return NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(aProperties),
+ NS_ConvertUTF16toUTF8(uriStr));
+}
+
+class nsPropertiesTable final : public nsGlyphTable {
+public:
+ explicit nsPropertiesTable(const nsString& aPrimaryFontName)
+ : mState(NS_TABLE_STATE_EMPTY)
+ {
+ MOZ_COUNT_CTOR(nsPropertiesTable);
+ mGlyphCodeFonts.AppendElement(FontFamilyName(aPrimaryFontName, eUnquotedName));
+ }
+
+ ~nsPropertiesTable()
+ {
+ MOZ_COUNT_DTOR(nsPropertiesTable);
+ }
+
+ const FontFamilyName& PrimaryFontName() const
+ {
+ return mGlyphCodeFonts[0];
+ }
+
+ const FontFamilyName&
+ FontNameFor(const nsGlyphCode& aGlyphCode) const override
+ {
+ NS_ASSERTION(!aGlyphCode.IsGlyphID(),
+ "nsPropertiesTable can only access glyphs by code point");
+ return mGlyphCodeFonts[aGlyphCode.font];
+ }
+
+ virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical,
+ uint32_t aPosition) override;
+
+ virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical,
+ uint32_t aSize) override
+ {
+ return ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup,
+ aChar, aVertical, 4 + aSize);
+ }
+
+ virtual bool HasPartsOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical) override
+ {
+ return (ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup,
+ aChar, aVertical, 0).Exists() ||
+ ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup,
+ aChar, aVertical, 1).Exists() ||
+ ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup,
+ aChar, aVertical, 2).Exists() ||
+ ElementAt(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup,
+ aChar, aVertical, 3).Exists());
+ }
+
+ virtual already_AddRefed<gfxTextRun>
+ MakeTextRun(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ const nsGlyphCode& aGlyph) override;
+private:
+
+ // mGlyphCodeFonts[0] is the primary font associated to this table. The
+ // others are possible "external" fonts for glyphs not in the primary font
+ // but which are needed to stretch certain characters in the table
+ nsTArray<FontFamilyName> mGlyphCodeFonts;
+
+ // Tri-state variable for error/empty/ready
+ int32_t mState;
+
+ // The set of glyph data in this table, as provided by the MathFont Property
+ // File
+ nsCOMPtr<nsIPersistentProperties> mGlyphProperties;
+
+ // mGlyphCache is a buffer containing the glyph data associated with
+ // mCharCache.
+ // For a property line 'key = value' in the MathFont Property File,
+ // mCharCache will retain the 'key' -- which is a Unicode point, while
+ // mGlyphCache will retain the 'value', which is a consecutive list of
+ // nsGlyphCodes, i.e., the pairs of 'code@font' needed by the char -- in
+ // which 'code@0' can be specified
+ // without the optional '@0'. However, to ease subsequent processing,
+ // mGlyphCache excludes the '@' symbol and explicitly inserts all optional '0'
+ // that indicates the primary font identifier. Specifically therefore, the
+ // k-th glyph is characterized by :
+ // 1) mGlyphCache[3*k],mGlyphCache[3*k+1] : its Unicode point
+ // 2) mGlyphCache[3*k+2] : the numeric identifier of the font where it comes
+ // from.
+ // A font identifier of '0' means the default primary font associated to this
+ // table. Other digits map to the "external" fonts that may have been
+ // specified in the MathFont Property File.
+ nsString mGlyphCache;
+};
+
+/* virtual */
+nsGlyphCode
+nsPropertiesTable::ElementAt(DrawTarget* /* aDrawTarget */,
+ int32_t /* aAppUnitsPerDevPixel */,
+ gfxFontGroup* /* aFontGroup */,
+ char16_t aChar,
+ bool /* aVertical */,
+ uint32_t aPosition)
+{
+ if (mState == NS_TABLE_STATE_ERROR) return kNullGlyph;
+ // Load glyph properties if this is the first time we have been here
+ if (mState == NS_TABLE_STATE_EMPTY) {
+ nsAutoString primaryFontName;
+ mGlyphCodeFonts[0].AppendToString(primaryFontName);
+ nsresult rv = LoadProperties(primaryFontName, mGlyphProperties);
+#ifdef DEBUG
+ nsAutoCString uriStr;
+ uriStr.AssignLiteral("resource://gre/res/fonts/mathfont");
+ LossyAppendUTF16toASCII(primaryFontName, uriStr);
+ uriStr.StripWhitespace(); // that may come from mGlyphCodeFonts
+ uriStr.AppendLiteral(".properties");
+ printf("Loading %s ... %s\n",
+ uriStr.get(),
+ (NS_FAILED(rv)) ? "Failed" : "Done");
+#endif
+ if (NS_FAILED(rv)) {
+ mState = NS_TABLE_STATE_ERROR; // never waste time with this table again
+ return kNullGlyph;
+ }
+ mState = NS_TABLE_STATE_READY;
+
+ // see if there are external fonts needed for certain chars in this table
+ nsAutoCString key;
+ nsAutoString value;
+ for (int32_t i = 1; ; i++) {
+ key.AssignLiteral("external.");
+ key.AppendInt(i, 10);
+ rv = mGlyphProperties->GetStringProperty(key, value);
+ if (NS_FAILED(rv)) break;
+ Clean(value);
+ mGlyphCodeFonts.AppendElement(FontFamilyName(value, eUnquotedName)); // i.e., mGlyphCodeFonts[i] holds this font name
+ }
+ }
+
+ // Update our cache if it is not associated to this character
+ if (mCharCache != aChar) {
+ // The key in the property file is interpreted as ASCII and kept
+ // as such ...
+ char key[10]; SprintfLiteral(key, "\\u%04X", aChar);
+ nsAutoString value;
+ nsresult rv = mGlyphProperties->GetStringProperty(nsDependentCString(key),
+ value);
+ if (NS_FAILED(rv)) return kNullGlyph;
+ Clean(value);
+ // See if this char uses external fonts; e.g., if the 2nd glyph is taken
+ // from the external font '1', the property line looks like
+ // \uNNNN = \uNNNN\uNNNN@1\uNNNN.
+ // This is where mGlyphCache is pre-processed to explicitly store all glyph
+ // codes as combined pairs of 'code@font', excluding the '@' separator. This
+ // means that mGlyphCache[3*k],mGlyphCache[3*k+1] will later be rendered
+ // with mGlyphCodeFonts[mGlyphCache[3*k+2]]
+ // Note: font identifier is internally an ASCII digit to avoid the null
+ // char issue
+ nsAutoString buffer;
+ int32_t length = value.Length();
+ int32_t i = 0; // index in value
+ while (i < length) {
+ char16_t code = value[i];
+ ++i;
+ buffer.Append(code);
+ // Read the next word if we have a non-BMP character.
+ if (i < length && NS_IS_HIGH_SURROGATE(code)) {
+ code = value[i];
+ ++i;
+ } else {
+ code = char16_t('\0');
+ }
+ buffer.Append(code);
+
+ // See if an external font is needed for the code point.
+ // Limit of 9 external fonts
+ char16_t font = 0;
+ if (i+1 < length && value[i] == char16_t('@') &&
+ value[i+1] >= char16_t('0') && value[i+1] <= char16_t('9')) {
+ ++i;
+ font = value[i] - '0';
+ ++i;
+ if (font >= mGlyphCodeFonts.Length()) {
+ NS_ERROR("Nonexistent font referenced in glyph table");
+ return kNullGlyph;
+ }
+ // The char cannot be handled if this font is not installed
+ if (!mGlyphCodeFonts[font].mName.Length()) {
+ return kNullGlyph;
+ }
+ }
+ buffer.Append(font);
+ }
+ // update our cache with the new settings
+ mGlyphCache.Assign(buffer);
+ mCharCache = aChar;
+ }
+
+ // 3* is to account for the code@font pairs
+ uint32_t index = 3*aPosition;
+ if (index+2 >= mGlyphCache.Length()) return kNullGlyph;
+ nsGlyphCode ch;
+ ch.code[0] = mGlyphCache.CharAt(index);
+ ch.code[1] = mGlyphCache.CharAt(index + 1);
+ ch.font = mGlyphCache.CharAt(index + 2);
+ return ch.code[0] == char16_t(0xFFFD) ? kNullGlyph : ch;
+}
+
+/* virtual */
+already_AddRefed<gfxTextRun>
+nsPropertiesTable::MakeTextRun(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ const nsGlyphCode& aGlyph)
+{
+ NS_ASSERTION(!aGlyph.IsGlyphID(),
+ "nsPropertiesTable can only access glyphs by code point");
+ return aFontGroup->
+ MakeTextRun(aGlyph.code, aGlyph.Length(), aDrawTarget,
+ aAppUnitsPerDevPixel, 0, nullptr);
+}
+
+// An instance of nsOpenTypeTable is associated with one gfxFontEntry that
+// corresponds to an Open Type font with a MATH table. All the glyphs come from
+// the same font and the calls to access size variants and parts are directly
+// forwarded to the gfx code.
+class nsOpenTypeTable final : public nsGlyphTable {
+public:
+ ~nsOpenTypeTable()
+ {
+ MOZ_COUNT_DTOR(nsOpenTypeTable);
+ }
+
+ virtual nsGlyphCode ElementAt(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical,
+ uint32_t aPosition) override;
+ virtual nsGlyphCode BigOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical,
+ uint32_t aSize) override;
+ virtual bool HasPartsOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical) override;
+
+ const FontFamilyName&
+ FontNameFor(const nsGlyphCode& aGlyphCode) const override {
+ NS_ASSERTION(aGlyphCode.IsGlyphID(),
+ "nsOpenTypeTable can only access glyphs by id");
+ return mFontFamilyName;
+ }
+
+ virtual already_AddRefed<gfxTextRun>
+ MakeTextRun(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ const nsGlyphCode& aGlyph) override;
+
+ // This returns a new OpenTypeTable instance to give access to OpenType MATH
+ // table or nullptr if the font does not have such table. Ownership is passed
+ // to the caller.
+ static nsOpenTypeTable* Create(gfxFont* aFont)
+ {
+ if (!aFont->TryGetMathTable()) {
+ return nullptr;
+ }
+ return new nsOpenTypeTable(aFont);
+ }
+
+private:
+ RefPtr<gfxFont> mFont;
+ FontFamilyName mFontFamilyName;
+ uint32_t mGlyphID;
+
+ explicit nsOpenTypeTable(gfxFont* aFont)
+ : mFont(aFont),
+ mFontFamilyName(aFont->GetFontEntry()->FamilyName(), eUnquotedName) {
+ MOZ_COUNT_CTOR(nsOpenTypeTable);
+ }
+
+ void UpdateCache(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar);
+};
+
+void
+nsOpenTypeTable::UpdateCache(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar)
+{
+ if (mCharCache != aChar) {
+ RefPtr<gfxTextRun> textRun = aFontGroup->
+ MakeTextRun(&aChar, 1, aDrawTarget, aAppUnitsPerDevPixel, 0, nullptr);
+ const gfxTextRun::CompressedGlyph& data = textRun->GetCharacterGlyphs()[0];
+ if (data.IsSimpleGlyph()) {
+ mGlyphID = data.GetSimpleGlyph();
+ } else if (data.GetGlyphCount() == 1) {
+ mGlyphID = textRun->GetDetailedGlyphs(0)->mGlyphID;
+ } else {
+ mGlyphID = 0;
+ }
+ mCharCache = aChar;
+ }
+}
+
+/* virtual */
+nsGlyphCode
+nsOpenTypeTable::ElementAt(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical,
+ uint32_t aPosition)
+{
+ UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+ uint32_t parts[4];
+ if (!mFont->MathTable()->VariantsParts(mGlyphID, aVertical, parts)) {
+ return kNullGlyph;
+ }
+
+ uint32_t glyphID = parts[aPosition];
+ if (!glyphID) {
+ return kNullGlyph;
+ }
+ nsGlyphCode glyph;
+ glyph.glyphID = glyphID;
+ glyph.font = -1;
+ return glyph;
+}
+
+/* virtual */
+nsGlyphCode
+nsOpenTypeTable::BigOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical,
+ uint32_t aSize)
+{
+ UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+ uint32_t glyphID =
+ mFont->MathTable()->VariantsSize(mGlyphID, aVertical, aSize);
+ if (!glyphID) {
+ return kNullGlyph;
+ }
+
+ nsGlyphCode glyph;
+ glyph.glyphID = glyphID;
+ glyph.font = -1;
+ return glyph;
+}
+
+/* virtual */
+bool
+nsOpenTypeTable::HasPartsOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ char16_t aChar,
+ bool aVertical)
+{
+ UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+ uint32_t parts[4];
+ if (!mFont->MathTable()->VariantsParts(mGlyphID, aVertical, parts)) {
+ return false;
+ }
+
+ return parts[0] || parts[1] || parts[2] || parts[3];
+}
+
+/* virtual */
+already_AddRefed<gfxTextRun>
+nsOpenTypeTable::MakeTextRun(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup,
+ const nsGlyphCode& aGlyph)
+{
+ NS_ASSERTION(aGlyph.IsGlyphID(),
+ "nsOpenTypeTable can only access glyphs by id");
+
+ gfxTextRunFactory::Parameters params = {
+ aDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel
+ };
+ RefPtr<gfxTextRun> textRun =
+ gfxTextRun::Create(&params, 1, aFontGroup, 0);
+ textRun->AddGlyphRun(aFontGroup->GetFirstValidFont(),
+ gfxTextRange::kFontGroup, 0,
+ false, gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL);
+ // We don't care about CSS writing mode here;
+ // math runs are assumed to be horizontal.
+ gfxTextRun::DetailedGlyph detailedGlyph;
+ detailedGlyph.mGlyphID = aGlyph.glyphID;
+ detailedGlyph.mAdvance =
+ NSToCoordRound(aAppUnitsPerDevPixel *
+ aFontGroup->GetFirstValidFont()->
+ GetGlyphHAdvance(aDrawTarget, aGlyph.glyphID));
+ detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0;
+ gfxShapedText::CompressedGlyph g;
+ g.SetComplex(true, true, 1);
+ textRun->SetGlyphs(0, g, &detailedGlyph);
+
+ return textRun.forget();
+}
+
+// -----------------------------------------------------------------------------
+// This is the list of all the applicable glyph tables.
+// We will maintain a single global instance that will only reveal those
+// glyph tables that are associated to fonts currently installed on the
+// user' system. The class is an XPCOM shutdown observer to allow us to
+// free its allocated data at shutdown
+
+class nsGlyphTableList final : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsPropertiesTable mUnicodeTable;
+
+ nsGlyphTableList()
+ : mUnicodeTable(NS_LITERAL_STRING("Unicode"))
+ {
+ MOZ_COUNT_CTOR(nsGlyphTableList);
+ }
+
+ nsresult Initialize();
+ nsresult Finalize();
+
+ // Add a glyph table in the list, return the new table that was added
+ nsGlyphTable*
+ AddGlyphTable(const nsString& aPrimaryFontName);
+
+ // Find the glyph table in the list corresponding to the given font family.
+ nsGlyphTable*
+ GetGlyphTableFor(const nsAString& aFamily);
+
+private:
+ ~nsGlyphTableList()
+ {
+ MOZ_COUNT_DTOR(nsGlyphTableList);
+ }
+
+ nsPropertiesTable* PropertiesTableAt(int32_t aIndex) {
+ return &mPropertiesTableList.ElementAt(aIndex);
+ }
+ int32_t PropertiesTableCount() {
+ return mPropertiesTableList.Length();
+ }
+ // List of glyph tables;
+ nsTArray<nsPropertiesTable> mPropertiesTableList;
+};
+
+NS_IMPL_ISUPPORTS(nsGlyphTableList, nsIObserver)
+
+// -----------------------------------------------------------------------------
+// Here is the global list of applicable glyph tables that we will be using
+static nsGlyphTableList* gGlyphTableList = nullptr;
+
+static bool gGlyphTableInitialized = false;
+
+// XPCOM shutdown observer
+NS_IMETHODIMP
+nsGlyphTableList::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* someData)
+{
+ Finalize();
+ return NS_OK;
+}
+
+// Add an observer to XPCOM shutdown so that we can free our data at shutdown
+nsresult
+nsGlyphTableList::Initialize()
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// Remove our observer and free the memory that were allocated for us
+nsresult
+nsGlyphTableList::Finalize()
+{
+ // Remove our observer from the observer service
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs)
+ rv = obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ else
+ rv = NS_ERROR_FAILURE;
+
+ gGlyphTableInitialized = false;
+ // our oneself will be destroyed when our |Release| is called by the observer
+ NS_IF_RELEASE(gGlyphTableList);
+ return rv;
+}
+
+nsGlyphTable*
+nsGlyphTableList::AddGlyphTable(const nsString& aPrimaryFontName)
+{
+ // See if there is already a special table for this family.
+ nsGlyphTable* glyphTable = GetGlyphTableFor(aPrimaryFontName);
+ if (glyphTable != &mUnicodeTable)
+ return glyphTable;
+
+ // allocate a table
+ glyphTable = mPropertiesTableList.AppendElement(aPrimaryFontName);
+ return glyphTable;
+}
+
+nsGlyphTable*
+nsGlyphTableList::GetGlyphTableFor(const nsAString& aFamily)
+{
+ for (int32_t i = 0; i < PropertiesTableCount(); i++) {
+ nsPropertiesTable* glyphTable = PropertiesTableAt(i);
+ const FontFamilyName& primaryFontName = glyphTable->PrimaryFontName();
+ nsAutoString primaryFontNameStr;
+ primaryFontName.AppendToString(primaryFontNameStr);
+ // TODO: would be nice to consider StripWhitespace and other aliasing
+ if (primaryFontNameStr.Equals(aFamily, nsCaseInsensitiveStringComparator())) {
+ return glyphTable;
+ }
+ }
+ // Fall back to default Unicode table
+ return &mUnicodeTable;
+}
+
+// -----------------------------------------------------------------------------
+
+static nsresult
+InitCharGlobals()
+{
+ NS_ASSERTION(!gGlyphTableInitialized, "Error -- already initialized");
+ gGlyphTableInitialized = true;
+
+ // Allocate the placeholders for the preferred parts and variants
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ RefPtr<nsGlyphTableList> glyphTableList = new nsGlyphTableList();
+ if (glyphTableList) {
+ rv = glyphTableList->Initialize();
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // The gGlyphTableList has been successfully registered as a shutdown
+ // observer and will be deleted at shutdown. We now add some private
+ // per font-family tables for stretchy operators, in order of preference.
+ // Do not include the Unicode table in this list.
+ if (!glyphTableList->AddGlyphTable(NS_LITERAL_STRING("STIXGeneral"))
+#ifdef XP_WIN
+ || !glyphTableList->AddGlyphTable(NS_LITERAL_STRING("Symbol"))
+#endif
+ ) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ glyphTableList.forget(&gGlyphTableList);
+ return rv;
+}
+
+// -----------------------------------------------------------------------------
+// And now the implementation of nsMathMLChar
+
+nsMathMLChar::~nsMathMLChar()
+{
+ MOZ_COUNT_DTOR(nsMathMLChar);
+ mStyleContext->Release();
+}
+
+nsStyleContext*
+nsMathMLChar::GetStyleContext() const
+{
+ NS_ASSERTION(mStyleContext, "chars should always have style context");
+ return mStyleContext;
+}
+
+void
+nsMathMLChar::SetStyleContext(nsStyleContext* aStyleContext)
+{
+ NS_PRECONDITION(aStyleContext, "null ptr");
+ if (aStyleContext != mStyleContext) {
+ if (mStyleContext)
+ mStyleContext->Release();
+ if (aStyleContext) {
+ mStyleContext = aStyleContext;
+ aStyleContext->AddRef();
+ }
+ }
+}
+
+void
+nsMathMLChar::SetData(nsString& aData)
+{
+ if (!gGlyphTableInitialized) {
+ InitCharGlobals();
+ }
+ mData = aData;
+ // some assumptions until proven otherwise
+ // note that mGlyph is not initialized
+ mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ mBoundingMetrics = nsBoundingMetrics();
+ // check if stretching is applicable ...
+ if (gGlyphTableList && (1 == mData.Length())) {
+ mDirection = nsMathMLOperators::GetStretchyDirection(mData);
+ // default tentative table (not the one that is necessarily going
+ // to be used)
+ }
+}
+
+// -----------------------------------------------------------------------------
+/*
+ The Stretch:
+ @param aContainerSize - suggested size for the stretched char
+ @param aDesiredStretchSize - OUT parameter. The desired size
+ after stretching. If no stretching is done, the output will
+ simply give the base size.
+
+ How it works?
+ Summary:-
+ The Stretch() method first looks for a glyph of appropriate
+ size; If a glyph is found, it is cached by this object and
+ its size is returned in aDesiredStretchSize. The cached
+ glyph will then be used at the painting stage.
+ If no glyph of appropriate size is found, a search is made
+ to see if the char can be built by parts.
+
+ Details:-
+ A character gets stretched through the following pipeline :
+
+ 1) If the base size of the char is sufficient to cover the
+ container' size, we use that. If not, it will still be
+ used as a fallback if the other stages in the pipeline fail.
+ Issues :
+ a) The base size, the parts and the variants of a char can
+ be in different fonts. For eg., the base size for '(' should
+ come from a normal ascii font if CMEX10 is used, since CMEX10
+ only contains the stretched versions. Hence, there are two
+ style contexts in use throughout the process. The leaf style
+ context of the char holds fonts with which to try to stretch
+ the char. The parent style context of the char contains fonts
+ for normal rendering. So the parent context is the one used
+ to get the initial base size at the start of the pipeline.
+ b) For operators that can be largeop's in display mode,
+ we will skip the base size even if it fits, so that
+ the next stage in the pipeline is given a chance to find
+ a largeop variant. If the next stage fails, we fallback
+ to the base size.
+
+ 2) We search for the first larger variant of the char that fits the
+ container' size. We first search for larger variants using the glyph
+ table corresponding to the first existing font specified in the list of
+ stretchy fonts held by the leaf style context (from -moz-math-stretchy in
+ mathml.css). Generic fonts are resolved by the preference
+ "font.mathfont-family".
+ Issues :
+ a) the largeop and display settings determine the starting
+ size when we do the above search, regardless of whether
+ smaller variants already fit the container' size.
+ b) if it is a largeopOnly request (i.e., a displaystyle operator
+ with largeop=true and stretchy=false), we break after finding
+ the first starting variant, regardless of whether that
+ variant fits the container's size.
+
+ 3) If a variant of appropriate size wasn't found, we see if the char
+ can be built by parts using the same glyph table.
+ Issue:
+ There are chars that have no middle and glue glyphs. For
+ such chars, the parts need to be joined using the rule.
+ By convention (TeXbook p.225), the descent of the parts is
+ zero while their ascent gives the thickness of the rule that
+ should be used to join them.
+
+ 4) If a match was not found in that glyph table, repeat from 2 to search the
+ ordered list of stretchy fonts for the first font with a glyph table that
+ provides a fit to the container size. If no fit is found, the closest fit
+ is used.
+
+ Of note:
+ When the pipeline completes successfully, the desired size of the
+ stretched char can actually be slightly larger or smaller than
+ aContainerSize. But it is the responsibility of the caller to
+ account for the spacing when setting aContainerSize, and to leave
+ any extra margin when placing the stretched char.
+*/
+// -----------------------------------------------------------------------------
+
+
+// plain TeX settings (TeXbook p.152)
+#define NS_MATHML_DELIMITER_FACTOR 0.901f
+#define NS_MATHML_DELIMITER_SHORTFALL_POINTS 5.0f
+
+static bool
+IsSizeOK(nscoord a, nscoord b, uint32_t aHint)
+{
+ // Normal: True if 'a' is around +/-10% of the target 'b' (10% is
+ // 1-DelimiterFactor). This often gives a chance to the base size to
+ // win, especially in the context of <mfenced> without tall elements
+ // or in sloppy markups without protective <mrow></mrow>
+ bool isNormal =
+ (aHint & NS_STRETCH_NORMAL) &&
+ Abs<float>(a - b) < (1.0f - NS_MATHML_DELIMITER_FACTOR) * float(b);
+
+ // Nearer: True if 'a' is around max{ +/-10% of 'b' , 'b' - 5pt },
+ // as documented in The TeXbook, Ch.17, p.152.
+ // i.e. within 10% and within 5pt
+ bool isNearer = false;
+ if (aHint & (NS_STRETCH_NEARER | NS_STRETCH_LARGEOP)) {
+ float c = std::max(float(b) * NS_MATHML_DELIMITER_FACTOR,
+ float(b) - nsPresContext::
+ CSSPointsToAppUnits(NS_MATHML_DELIMITER_SHORTFALL_POINTS));
+ isNearer = Abs<float>(b - a) <= float(b) - c;
+ }
+
+ // Smaller: Mainly for transitory use, to compare two candidate
+ // choices
+ bool isSmaller =
+ (aHint & NS_STRETCH_SMALLER) &&
+ float(a) >= NS_MATHML_DELIMITER_FACTOR * float(b) &&
+ a <= b;
+
+ // Larger: Critical to the sqrt code to ensure that the radical
+ // size is tall enough
+ bool isLarger =
+ (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP)) &&
+ a >= b;
+
+ return (isNormal || isSmaller || isNearer || isLarger);
+}
+
+static bool
+IsSizeBetter(nscoord a, nscoord olda, nscoord b, uint32_t aHint)
+{
+ if (0 == olda)
+ return true;
+ if (aHint & (NS_STRETCH_LARGER | NS_STRETCH_LARGEOP))
+ return (a >= olda) ? (olda < b) : (a >= b);
+ if (aHint & NS_STRETCH_SMALLER)
+ return (a <= olda) ? (olda > b) : (a <= b);
+
+ // XXXkt prob want log scale here i.e. 1.5 is closer to 1 than 0.5
+ return Abs(a - b) < Abs(olda - b);
+}
+
+// We want to place the glyphs even when they don't fit at their
+// full extent, i.e., we may clip to tolerate a small amount of
+// overlap between the parts. This is important to cater for fonts
+// with long glues.
+static nscoord
+ComputeSizeFromParts(nsPresContext* aPresContext,
+ nsGlyphCode* aGlyphs,
+ nscoord* aSizes,
+ nscoord aTargetSize)
+{
+ enum {first, middle, last, glue};
+ // Add the parts that cannot be left out.
+ nscoord sum = 0;
+ for (int32_t i = first; i <= last; i++) {
+ sum += aSizes[i];
+ }
+
+ // Determine how much is used in joins
+ nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
+ int32_t joins = aGlyphs[middle] == aGlyphs[glue] ? 1 : 2;
+
+ // Pick a maximum size using a maximum number of glue glyphs that we are
+ // prepared to draw for one character.
+ const int32_t maxGlyphs = 1000;
+
+ // This also takes into account the fact that, if the glue has no size,
+ // then the character can't be lengthened.
+ nscoord maxSize = sum - 2 * joins * oneDevPixel + maxGlyphs * aSizes[glue];
+ if (maxSize < aTargetSize)
+ return maxSize; // settle with the maximum size
+
+ // Get the minimum allowable size using some flex.
+ nscoord minSize = NSToCoordRound(NS_MATHML_DELIMITER_FACTOR * sum);
+
+ if (minSize > aTargetSize)
+ return minSize; // settle with the minimum size
+
+ // Fill-up the target area
+ return aTargetSize;
+}
+
+// Update the font if there is a family change and returns the font group.
+bool
+nsMathMLChar::SetFontFamily(nsPresContext* aPresContext,
+ const nsGlyphTable* aGlyphTable,
+ const nsGlyphCode& aGlyphCode,
+ const FontFamilyList& aDefaultFamilyList,
+ nsFont& aFont,
+ RefPtr<gfxFontGroup>* aFontGroup)
+{
+ FontFamilyList glyphCodeFont;
+
+ if (aGlyphCode.font) {
+ glyphCodeFont.Append(aGlyphTable->FontNameFor(aGlyphCode));
+ }
+
+ const FontFamilyList& familyList =
+ aGlyphCode.font ? glyphCodeFont : aDefaultFamilyList;
+
+ if (!*aFontGroup || !(aFont.fontlist == familyList)) {
+ nsFont font = aFont;
+ font.fontlist = familyList;
+ const nsStyleFont* styleFont = mStyleContext->StyleFont();
+ nsFontMetrics::Params params;
+ params.language = styleFont->mLanguage;
+ params.explicitLanguage = styleFont->mExplicitLanguage;
+ params.userFontSet = aPresContext->GetUserFontSet();
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ RefPtr<nsFontMetrics> fm =
+ aPresContext->DeviceContext()->GetMetricsFor(font, params);
+ // Set the font if it is an unicode table
+ // or if the same family name has been found
+ gfxFont *firstFont = fm->GetThebesFontGroup()->GetFirstValidFont();
+ FontFamilyList firstFontList;
+ firstFontList.Append(
+ FontFamilyName(firstFont->GetFontEntry()->FamilyName(), eUnquotedName));
+ if (aGlyphTable == &gGlyphTableList->mUnicodeTable ||
+ firstFontList == familyList) {
+ aFont.fontlist = familyList;
+ *aFontGroup = fm->GetThebesFontGroup();
+ } else {
+ return false; // We did not set the font
+ }
+ }
+ return true;
+}
+
+static nsBoundingMetrics
+MeasureTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun)
+{
+ gfxTextRun::Metrics metrics =
+ aTextRun->MeasureText(gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, aDrawTarget);
+
+ nsBoundingMetrics bm;
+ bm.leftBearing = NSToCoordFloor(metrics.mBoundingBox.X());
+ bm.rightBearing = NSToCoordCeil(metrics.mBoundingBox.XMost());
+ bm.ascent = NSToCoordCeil(-metrics.mBoundingBox.Y());
+ bm.descent = NSToCoordCeil(metrics.mBoundingBox.YMost());
+ bm.width = NSToCoordRound(metrics.mAdvanceWidth);
+
+ return bm;
+}
+
+class nsMathMLChar::StretchEnumContext {
+public:
+ StretchEnumContext(nsMathMLChar* aChar,
+ nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ nsStretchDirection aStretchDirection,
+ nscoord aTargetSize,
+ uint32_t aStretchHint,
+ nsBoundingMetrics& aStretchedMetrics,
+ const FontFamilyList& aFamilyList,
+ bool& aGlyphFound)
+ : mChar(aChar),
+ mPresContext(aPresContext),
+ mDrawTarget(aDrawTarget),
+ mFontSizeInflation(aFontSizeInflation),
+ mDirection(aStretchDirection),
+ mTargetSize(aTargetSize),
+ mStretchHint(aStretchHint),
+ mBoundingMetrics(aStretchedMetrics),
+ mFamilyList(aFamilyList),
+ mTryVariants(true),
+ mTryParts(true),
+ mGlyphFound(aGlyphFound) {}
+
+ static bool
+ EnumCallback(const FontFamilyName& aFamily, bool aGeneric, void *aData);
+
+private:
+ bool TryVariants(nsGlyphTable* aGlyphTable,
+ RefPtr<gfxFontGroup>* aFontGroup,
+ const FontFamilyList& aFamilyList);
+ bool TryParts(nsGlyphTable* aGlyphTable,
+ RefPtr<gfxFontGroup>* aFontGroup,
+ const FontFamilyList& aFamilyList);
+
+ nsMathMLChar* mChar;
+ nsPresContext* mPresContext;
+ DrawTarget* mDrawTarget;
+ float mFontSizeInflation;
+ const nsStretchDirection mDirection;
+ const nscoord mTargetSize;
+ const uint32_t mStretchHint;
+ nsBoundingMetrics& mBoundingMetrics;
+ // Font families to search
+ const FontFamilyList& mFamilyList;
+
+public:
+ bool mTryVariants;
+ bool mTryParts;
+
+private:
+ AutoTArray<nsGlyphTable*,16> mTablesTried;
+ bool& mGlyphFound;
+};
+
+
+// 2. See if there are any glyphs of the appropriate size.
+// Returns true if the size is OK, false to keep searching.
+// Always updates the char if a better match is found.
+bool
+nsMathMLChar::
+StretchEnumContext::TryVariants(nsGlyphTable* aGlyphTable,
+ RefPtr<gfxFontGroup>* aFontGroup,
+ const FontFamilyList& aFamilyList)
+{
+ // Use our stretchy style context now that stretching is in progress
+ nsStyleContext *sc = mChar->mStyleContext;
+ nsFont font = sc->StyleFont()->mFont;
+ NormalizeDefaultFont(font, mFontSizeInflation);
+
+ bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL);
+ nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel();
+ char16_t uchar = mChar->mData[0];
+ bool largeop = (NS_STRETCH_LARGEOP & mStretchHint) != 0;
+ bool largeopOnly =
+ largeop && (NS_STRETCH_VARIABLE_MASK & mStretchHint) == 0;
+ bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0;
+
+ nscoord bestSize =
+ isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent
+ : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
+ bool haveBetter = false;
+
+ // start at size = 1 (size = 0 is the char at its normal size)
+ int32_t size = 1;
+ nsGlyphCode ch;
+ nscoord displayOperatorMinHeight = 0;
+ if (largeopOnly) {
+ NS_ASSERTION(isVertical, "Stretching should be in the vertical direction");
+ ch = aGlyphTable->BigOf(mDrawTarget, oneDevPixel, *aFontGroup, uchar,
+ isVertical, 0);
+ if (ch.IsGlyphID()) {
+ gfxFont* mathFont = aFontGroup->get()->GetFirstMathFont();
+ // For OpenType MATH fonts, we will rely on the DisplayOperatorMinHeight
+ // to select the right size variant. Note that the value is sometimes too
+ // small so we use kLargeOpFactor/kIntegralFactor as a minimum value.
+ if (mathFont) {
+ displayOperatorMinHeight = mathFont->MathTable()->
+ Constant(gfxMathTable::DisplayOperatorMinHeight, oneDevPixel);
+ RefPtr<gfxTextRun> textRun =
+ aGlyphTable->MakeTextRun(mDrawTarget, oneDevPixel, *aFontGroup, ch);
+ nsBoundingMetrics bm = MeasureTextRun(mDrawTarget, textRun.get());
+ float largeopFactor = kLargeOpFactor;
+ if (NS_STRETCH_INTEGRAL & mStretchHint) {
+ // integrals are drawn taller
+ largeopFactor = kIntegralFactor;
+ }
+ nscoord minHeight = largeopFactor * (bm.ascent + bm.descent);
+ if (displayOperatorMinHeight < minHeight) {
+ displayOperatorMinHeight = minHeight;
+ }
+ }
+ }
+ }
+#ifdef NOISY_SEARCH
+ printf(" searching in %s ...\n",
+ NS_LossyConvertUTF16toASCII(aFamily).get());
+#endif
+ while ((ch = aGlyphTable->BigOf(mDrawTarget, oneDevPixel, *aFontGroup,
+ uchar, isVertical, size)).Exists()) {
+
+ if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamilyList, font,
+ aFontGroup)) {
+ // if largeopOnly is set, break now
+ if (largeopOnly) break;
+ ++size;
+ continue;
+ }
+
+ RefPtr<gfxTextRun> textRun =
+ aGlyphTable->MakeTextRun(mDrawTarget, oneDevPixel, *aFontGroup, ch);
+ nsBoundingMetrics bm = MeasureTextRun(mDrawTarget, textRun.get());
+ if (ch.IsGlyphID()) {
+ gfxFont* mathFont = aFontGroup->get()->GetFirstMathFont();
+ if (mathFont) {
+ // MeasureTextRun should have set the advance width to the right
+ // bearing for OpenType MATH fonts. We now subtract the italic
+ // correction, so that nsMathMLmmultiscripts will place the scripts
+ // correctly.
+ // Note that STIX-Word does not provide italic corrections but its
+ // advance widths do not match right bearings.
+ // (http://sourceforge.net/p/stixfonts/tracking/50/)
+ gfxFloat italicCorrection =
+ mathFont->MathTable()->ItalicsCorrection(ch.glyphID);
+ if (italicCorrection) {
+ bm.width -=
+ NSToCoordRound(italicCorrection * oneDevPixel);
+ if (bm.width < 0) {
+ bm.width = 0;
+ }
+ }
+ }
+ }
+
+ nscoord charSize =
+ isVertical ? bm.ascent + bm.descent
+ : bm.rightBearing - bm.leftBearing;
+
+ if (largeopOnly ||
+ IsSizeBetter(charSize, bestSize, mTargetSize, mStretchHint)) {
+ mGlyphFound = true;
+ if (maxWidth) {
+ // IsSizeBetter() checked that charSize < maxsize;
+ // Leave ascent, descent, and bestsize as these contain maxsize.
+ if (mBoundingMetrics.width < bm.width)
+ mBoundingMetrics.width = bm.width;
+ if (mBoundingMetrics.leftBearing > bm.leftBearing)
+ mBoundingMetrics.leftBearing = bm.leftBearing;
+ if (mBoundingMetrics.rightBearing < bm.rightBearing)
+ mBoundingMetrics.rightBearing = bm.rightBearing;
+ // Continue to check other sizes unless largeopOnly
+ haveBetter = largeopOnly;
+ }
+ else {
+ mBoundingMetrics = bm;
+ haveBetter = true;
+ bestSize = charSize;
+ mChar->mGlyphs[0] = Move(textRun);
+ mChar->mDraw = DRAW_VARIANT;
+ }
+#ifdef NOISY_SEARCH
+ printf(" size:%d Current best\n", size);
+#endif
+ }
+ else {
+#ifdef NOISY_SEARCH
+ printf(" size:%d Rejected!\n", size);
+#endif
+ if (haveBetter)
+ break; // Not making an futher progress, stop searching
+ }
+
+ // If this a largeop only operator, we stop if the glyph is large enough.
+ if (largeopOnly && (bm.ascent + bm.descent) >= displayOperatorMinHeight) {
+ break;
+ }
+ ++size;
+ }
+
+ return haveBetter &&
+ (largeopOnly || IsSizeOK(bestSize, mTargetSize, mStretchHint));
+}
+
+// 3. Build by parts.
+// Returns true if the size is OK, false to keep searching.
+// Always updates the char if a better match is found.
+bool
+nsMathMLChar::StretchEnumContext::TryParts(nsGlyphTable* aGlyphTable,
+ RefPtr<gfxFontGroup>* aFontGroup,
+ const FontFamilyList& aFamilyList)
+{
+ // Use our stretchy style context now that stretching is in progress
+ nsFont font = mChar->mStyleContext->StyleFont()->mFont;
+ NormalizeDefaultFont(font, mFontSizeInflation);
+
+ // Compute the bounding metrics of all partial glyphs
+ RefPtr<gfxTextRun> textRun[4];
+ nsGlyphCode chdata[4];
+ nsBoundingMetrics bmdata[4];
+ nscoord sizedata[4];
+
+ bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL);
+ nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel();
+ char16_t uchar = mChar->mData[0];
+ bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0;
+ if (!aGlyphTable->HasPartsOf(mDrawTarget, oneDevPixel, *aFontGroup,
+ uchar, isVertical))
+ return false; // to next table
+
+ for (int32_t i = 0; i < 4; i++) {
+ nsGlyphCode ch = aGlyphTable->ElementAt(mDrawTarget, oneDevPixel,
+ *aFontGroup, uchar, isVertical, i);
+ chdata[i] = ch;
+ if (ch.Exists()) {
+ if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamilyList, font,
+ aFontGroup))
+ return false;
+
+ textRun[i] = aGlyphTable->MakeTextRun(mDrawTarget, oneDevPixel,
+ *aFontGroup, ch);
+ nsBoundingMetrics bm = MeasureTextRun(mDrawTarget, textRun[i].get());
+ bmdata[i] = bm;
+ sizedata[i] = isVertical ? bm.ascent + bm.descent
+ : bm.rightBearing - bm.leftBearing;
+ } else {
+ // Null glue indicates that a rule will be drawn, which can stretch to
+ // fill any space.
+ textRun[i] = nullptr;
+ bmdata[i] = nsBoundingMetrics();
+ sizedata[i] = i == 3 ? mTargetSize : 0;
+ }
+ }
+
+ // For the Unicode table, we check that all the glyphs are actually found and
+ // come from the same font.
+ if (aGlyphTable == &gGlyphTableList->mUnicodeTable) {
+ gfxFont* unicodeFont = nullptr;
+ for (int32_t i = 0; i < 4; i++) {
+ if (!textRun[i]) {
+ continue;
+ }
+ if (textRun[i]->GetLength() != 1 ||
+ textRun[i]->GetCharacterGlyphs()[0].IsMissing()) {
+ return false;
+ }
+ uint32_t numGlyphRuns;
+ const gfxTextRun::GlyphRun* glyphRuns =
+ textRun[i]->GetGlyphRuns(&numGlyphRuns);
+ if (numGlyphRuns != 1) {
+ return false;
+ }
+ if (!unicodeFont) {
+ unicodeFont = glyphRuns[0].mFont;
+ } else if (unicodeFont != glyphRuns[0].mFont) {
+ return false;
+ }
+ }
+ }
+
+ // Build by parts if we have successfully computed the
+ // bounding metrics of all parts.
+ nscoord computedSize = ComputeSizeFromParts(mPresContext, chdata, sizedata,
+ mTargetSize);
+
+ nscoord currentSize =
+ isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent
+ : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
+
+ if (!IsSizeBetter(computedSize, currentSize, mTargetSize, mStretchHint)) {
+#ifdef NOISY_SEARCH
+ printf(" Font %s Rejected!\n",
+ NS_LossyConvertUTF16toASCII(fontName).get());
+#endif
+ return false; // to next table
+ }
+
+#ifdef NOISY_SEARCH
+ printf(" Font %s Current best!\n",
+ NS_LossyConvertUTF16toASCII(fontName).get());
+#endif
+
+ // The computed size is the best we have found so far...
+ // now is the time to compute and cache our bounding metrics
+ if (isVertical) {
+ int32_t i;
+ // Try and find the first existing part and then determine the extremal
+ // horizontal metrics of the parts.
+ for (i = 0; i <= 3 && !textRun[i]; i++);
+ if (i == 4) {
+ NS_ERROR("Cannot stretch - All parts missing");
+ return false;
+ }
+ nscoord lbearing = bmdata[i].leftBearing;
+ nscoord rbearing = bmdata[i].rightBearing;
+ nscoord width = bmdata[i].width;
+ i++;
+ for (; i <= 3; i++) {
+ if (!textRun[i]) continue;
+ lbearing = std::min(lbearing, bmdata[i].leftBearing);
+ rbearing = std::max(rbearing, bmdata[i].rightBearing);
+ width = std::max(width, bmdata[i].width);
+ }
+ if (maxWidth) {
+ lbearing = std::min(lbearing, mBoundingMetrics.leftBearing);
+ rbearing = std::max(rbearing, mBoundingMetrics.rightBearing);
+ width = std::max(width, mBoundingMetrics.width);
+ }
+ mBoundingMetrics.width = width;
+ // When maxWidth, updating ascent and descent indicates that no characters
+ // larger than this character's minimum size need to be checked as they
+ // will not be used.
+ mBoundingMetrics.ascent = bmdata[0].ascent; // not used except with descent
+ // for height
+ mBoundingMetrics.descent = computedSize - mBoundingMetrics.ascent;
+ mBoundingMetrics.leftBearing = lbearing;
+ mBoundingMetrics.rightBearing = rbearing;
+ }
+ else {
+ int32_t i;
+ // Try and find the first existing part and then determine the extremal
+ // vertical metrics of the parts.
+ for (i = 0; i <= 3 && !textRun[i]; i++);
+ if (i == 4) {
+ NS_ERROR("Cannot stretch - All parts missing");
+ return false;
+ }
+ nscoord ascent = bmdata[i].ascent;
+ nscoord descent = bmdata[i].descent;
+ i++;
+ for (; i <= 3; i++) {
+ if (!textRun[i]) continue;
+ ascent = std::max(ascent, bmdata[i].ascent);
+ descent = std::max(descent, bmdata[i].descent);
+ }
+ mBoundingMetrics.width = computedSize;
+ mBoundingMetrics.ascent = ascent;
+ mBoundingMetrics.descent = descent;
+ mBoundingMetrics.leftBearing = 0;
+ mBoundingMetrics.rightBearing = computedSize;
+ }
+ mGlyphFound = true;
+ if (maxWidth)
+ return false; // Continue to check other sizes
+
+ // reset
+ mChar->mDraw = DRAW_PARTS;
+ for (int32_t i = 0; i < 4; i++) {
+ mChar->mGlyphs[i] = Move(textRun[i]);
+ mChar->mBmData[i] = bmdata[i];
+ }
+
+ return IsSizeOK(computedSize, mTargetSize, mStretchHint);
+}
+
+// This is called for each family, whether it exists or not
+bool
+nsMathMLChar::StretchEnumContext::EnumCallback(const FontFamilyName& aFamily,
+ bool aGeneric, void *aData)
+{
+ StretchEnumContext* context = static_cast<StretchEnumContext*>(aData);
+
+ // for comparisons, force use of unquoted names
+ FontFamilyName unquotedFamilyName(aFamily);
+ if (unquotedFamilyName.mType == eFamily_named_quoted) {
+ unquotedFamilyName.mType = eFamily_named;
+ }
+
+ // Check font family if it is not a generic one
+ // We test with the kNullGlyph
+ nsStyleContext *sc = context->mChar->mStyleContext;
+ nsFont font = sc->StyleFont()->mFont;
+ NormalizeDefaultFont(font, context->mFontSizeInflation);
+ RefPtr<gfxFontGroup> fontGroup;
+ FontFamilyList family;
+ family.Append(unquotedFamilyName);
+ if (!aGeneric && !context->mChar->SetFontFamily(context->mPresContext,
+ nullptr, kNullGlyph, family,
+ font, &fontGroup))
+ return true; // Could not set the family
+
+ // Determine the glyph table to use for this font.
+ nsAutoPtr<nsOpenTypeTable> openTypeTable;
+ nsGlyphTable* glyphTable;
+ if (aGeneric) {
+ // This is a generic font, use the Unicode table.
+ glyphTable = &gGlyphTableList->mUnicodeTable;
+ } else {
+ // If the font contains an Open Type MATH table, use it.
+ openTypeTable = nsOpenTypeTable::Create(fontGroup->GetFirstValidFont());
+ if (openTypeTable) {
+ glyphTable = openTypeTable;
+ } else {
+ // Otherwise try to find a .properties file corresponding to that font
+ // family or fallback to the Unicode table.
+ nsAutoString familyName;
+ unquotedFamilyName.AppendToString(familyName);
+ glyphTable = gGlyphTableList->GetGlyphTableFor(familyName);
+ }
+ }
+
+ if (!openTypeTable) {
+ if (context->mTablesTried.Contains(glyphTable))
+ return true; // already tried this one
+
+ // Only try this table once.
+ context->mTablesTried.AppendElement(glyphTable);
+ }
+
+ // If the unicode table is being used, then search all font families. If a
+ // special table is being used then the font in this family should have the
+ // specified glyphs.
+ const FontFamilyList& familyList = glyphTable == &gGlyphTableList->mUnicodeTable ?
+ context->mFamilyList : family;
+
+ if((context->mTryVariants &&
+ context->TryVariants(glyphTable, &fontGroup, familyList)) ||
+ (context->mTryParts && context->TryParts(glyphTable,
+ &fontGroup,
+ familyList)))
+ return false; // no need to continue
+
+ return true; // true means continue
+}
+
+// insert math fallback families just before the first generic or at the end
+// when no generic present
+static void
+InsertMathFallbacks(FontFamilyList& aFamilyList,
+ nsTArray<nsString>& aFallbacks)
+{
+ FontFamilyList aMergedList;
+
+ bool inserted = false;
+ const nsTArray<FontFamilyName>& fontlist = aFamilyList.GetFontlist();
+ uint32_t i, num = fontlist.Length();
+ for (i = 0; i < num; i++) {
+ const FontFamilyName& name = fontlist[i];
+ if (!inserted && name.IsGeneric()) {
+ inserted = true;
+ aMergedList.Append(aFallbacks);
+ }
+ aMergedList.Append(name);
+ }
+
+ if (!inserted) {
+ aMergedList.Append(aFallbacks);
+ }
+ aFamilyList = aMergedList;
+}
+
+nsresult
+nsMathMLChar::StretchInternal(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ nsStretchDirection& aStretchDirection,
+ const nsBoundingMetrics& aContainerSize,
+ nsBoundingMetrics& aDesiredStretchSize,
+ uint32_t aStretchHint,
+ // These are currently only used when
+ // aStretchHint & NS_STRETCH_MAXWIDTH:
+ float aMaxSize,
+ bool aMaxSizeIsAbsolute)
+{
+ // if we have been called before, and we didn't actually stretch, our
+ // direction may have been set to NS_STRETCH_DIRECTION_UNSUPPORTED.
+ // So first set our direction back to its instrinsic value
+ nsStretchDirection direction = nsMathMLOperators::GetStretchyDirection(mData);
+
+ // Set default font and get the default bounding metrics
+ // mStyleContext is a leaf context used only when stretching happens.
+ // For the base size, the default font should come from the parent context
+ nsFont font = mStyleContext->GetParent()->StyleFont()->mFont;
+ NormalizeDefaultFont(font, aFontSizeInflation);
+
+ const nsStyleFont* styleFont = mStyleContext->StyleFont();
+ nsFontMetrics::Params params;
+ params.language = styleFont->mLanguage;
+ params.explicitLanguage = styleFont->mExplicitLanguage;
+ params.userFontSet = aPresContext->GetUserFontSet();
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ RefPtr<nsFontMetrics> fm =
+ aPresContext->DeviceContext()->GetMetricsFor(font, params);
+ uint32_t len = uint32_t(mData.Length());
+ mGlyphs[0] = fm->GetThebesFontGroup()->
+ MakeTextRun(static_cast<const char16_t*>(mData.get()), len, aDrawTarget,
+ aPresContext->AppUnitsPerDevPixel(), 0,
+ aPresContext->MissingFontRecorder());
+ aDesiredStretchSize = MeasureTextRun(aDrawTarget, mGlyphs[0].get());
+
+ bool maxWidth = (NS_STRETCH_MAXWIDTH & aStretchHint) != 0;
+ if (!maxWidth) {
+ mUnscaledAscent = aDesiredStretchSize.ascent;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // 1. Check the common situations where stretching is not actually needed
+ //////////////////////////////////////////////////////////////////////////////
+
+ // quick return if there is nothing special about this char
+ if ((aStretchDirection != direction &&
+ aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT) ||
+ (aStretchHint & ~NS_STRETCH_MAXWIDTH) == NS_STRETCH_NONE) {
+ mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ return NS_OK;
+ }
+
+ // if no specified direction, attempt to stretch in our preferred direction
+ if (aStretchDirection == NS_STRETCH_DIRECTION_DEFAULT) {
+ aStretchDirection = direction;
+ }
+
+ // see if this is a particular largeop or largeopOnly request
+ bool largeop = (NS_STRETCH_LARGEOP & aStretchHint) != 0;
+ bool stretchy = (NS_STRETCH_VARIABLE_MASK & aStretchHint) != 0;
+ bool largeopOnly = largeop && !stretchy;
+
+ bool isVertical = (direction == NS_STRETCH_DIRECTION_VERTICAL);
+
+ nscoord targetSize =
+ isVertical ? aContainerSize.ascent + aContainerSize.descent
+ : aContainerSize.rightBearing - aContainerSize.leftBearing;
+
+ if (maxWidth) {
+ // See if it is only necessary to consider glyphs up to some maximum size.
+ // Set the current height to the maximum size, and set aStretchHint to
+ // NS_STRETCH_SMALLER if the size is variable, so that only smaller sizes
+ // are considered. targetSize from GetMaxWidth() is 0.
+ if (stretchy) {
+ // variable size stretch - consider all sizes < maxsize
+ aStretchHint =
+ (aStretchHint & ~NS_STRETCH_VARIABLE_MASK) | NS_STRETCH_SMALLER;
+ }
+
+ // Use NS_MATHML_DELIMITER_FACTOR to allow some slightly larger glyphs as
+ // maxsize is not enforced exactly.
+ if (aMaxSize == NS_MATHML_OPERATOR_SIZE_INFINITY) {
+ aDesiredStretchSize.ascent = nscoord_MAX;
+ aDesiredStretchSize.descent = 0;
+ }
+ else {
+ nscoord height = aDesiredStretchSize.ascent + aDesiredStretchSize.descent;
+ if (height == 0) {
+ if (aMaxSizeIsAbsolute) {
+ aDesiredStretchSize.ascent =
+ NSToCoordRound(aMaxSize / NS_MATHML_DELIMITER_FACTOR);
+ aDesiredStretchSize.descent = 0;
+ }
+ // else: leave height as 0
+ }
+ else {
+ float scale = aMaxSizeIsAbsolute ? aMaxSize / height : aMaxSize;
+ scale /= NS_MATHML_DELIMITER_FACTOR;
+ aDesiredStretchSize.ascent =
+ NSToCoordRound(scale * aDesiredStretchSize.ascent);
+ aDesiredStretchSize.descent =
+ NSToCoordRound(scale * aDesiredStretchSize.descent);
+ }
+ }
+ }
+
+ nsBoundingMetrics initialSize = aDesiredStretchSize;
+ nscoord charSize =
+ isVertical ? initialSize.ascent + initialSize.descent
+ : initialSize.rightBearing - initialSize.leftBearing;
+
+ bool done = false;
+
+ if (!maxWidth && !largeop) {
+ // Doing Stretch() not GetMaxWidth(),
+ // and not a largeop in display mode; we're done if size fits
+ if ((targetSize <= 0) ||
+ ((isVertical && charSize >= targetSize) ||
+ IsSizeOK(charSize, targetSize, aStretchHint)))
+ done = true;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // 2/3. Search for a glyph or set of part glyphs of appropriate size
+ //////////////////////////////////////////////////////////////////////////////
+
+ bool glyphFound = false;
+
+ if (!done) { // normal case
+ // Use the css font-family but add preferred fallback fonts.
+ font = mStyleContext->StyleFont()->mFont;
+ NormalizeDefaultFont(font, aFontSizeInflation);
+
+ // really shouldn't be doing things this way but for now
+ // insert fallbacks into the list
+ AutoTArray<nsString, 16> mathFallbacks;
+ gfxFontUtils::GetPrefsFontList("font.name.serif.x-math", mathFallbacks);
+ gfxFontUtils::AppendPrefsFontList("font.name-list.serif.x-math",
+ mathFallbacks);
+ InsertMathFallbacks(font.fontlist, mathFallbacks);
+
+
+#ifdef NOISY_SEARCH
+ nsAutoString fontlistStr;
+ font.fontlist.ToString(fontlistStr, false, true);
+ printf("Searching in "%s" for a glyph of appropriate size for: 0x%04X:%c\n",
+ NS_ConvertUTF16toUTF8(fontlistStr).get(), mData[0], mData[0]&0x00FF);
+#endif
+ StretchEnumContext enumData(this, aPresContext, aDrawTarget,
+ aFontSizeInflation,
+ aStretchDirection, targetSize, aStretchHint,
+ aDesiredStretchSize, font.fontlist, glyphFound);
+ enumData.mTryParts = !largeopOnly;
+
+ const nsTArray<FontFamilyName>& fontlist = font.fontlist.GetFontlist();
+ uint32_t i, num = fontlist.Length();
+ bool next = true;
+ for (i = 0; i < num && next; i++) {
+ const FontFamilyName& name = fontlist[i];
+ next = StretchEnumContext::EnumCallback(name, name.IsGeneric(), &enumData);
+ }
+ }
+
+ if (!maxWidth) {
+ // Now, we know how we are going to draw the char. Update the member
+ // variables accordingly.
+ mUnscaledAscent = aDesiredStretchSize.ascent;
+ }
+
+ if (glyphFound) {
+ return NS_OK;
+ }
+
+ // We did not find a size variant or a glyph assembly to stretch this
+ // operator. Verify whether a font with an OpenType MATH table is available
+ // and record missing math script otherwise.
+ gfxMissingFontRecorder* MFR = aPresContext->MissingFontRecorder();
+ if (MFR && !fm->GetThebesFontGroup()->GetFirstMathFont()) {
+ MFR->RecordScript(unicode::Script::MATHEMATICAL_NOTATION);
+ }
+
+ // If the scale_stretchy_operators option is disabled, we are done.
+ if (!Preferences::GetBool("mathml.scale_stretchy_operators.enabled", true)) {
+ return NS_OK;
+ }
+
+ // stretchy character
+ if (stretchy) {
+ if (isVertical) {
+ float scale =
+ std::min(kMaxScaleFactor, float(aContainerSize.ascent + aContainerSize.descent) /
+ (aDesiredStretchSize.ascent + aDesiredStretchSize.descent));
+ if (!largeop || scale > 1.0) {
+ // make the character match the desired height.
+ if (!maxWidth) {
+ mScaleY *= scale;
+ }
+ aDesiredStretchSize.ascent *= scale;
+ aDesiredStretchSize.descent *= scale;
+ }
+ } else {
+ float scale =
+ std::min(kMaxScaleFactor, float(aContainerSize.rightBearing - aContainerSize.leftBearing) /
+ (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing));
+ if (!largeop || scale > 1.0) {
+ // make the character match the desired width.
+ if (!maxWidth) {
+ mScaleX *= scale;
+ }
+ aDesiredStretchSize.leftBearing *= scale;
+ aDesiredStretchSize.rightBearing *= scale;
+ aDesiredStretchSize.width *= scale;
+ }
+ }
+ }
+
+ // We do not have a char variant for this largeop in display mode, so we
+ // apply a scale transform to the base char.
+ if (largeop) {
+ float scale;
+ float largeopFactor = kLargeOpFactor;
+
+ // increase the width if it is not largeopFactor times larger
+ // than the initial one.
+ if ((aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing) <
+ largeopFactor * (initialSize.rightBearing - initialSize.leftBearing)) {
+ scale = (largeopFactor *
+ (initialSize.rightBearing - initialSize.leftBearing)) /
+ (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing);
+ if (!maxWidth) {
+ mScaleX *= scale;
+ }
+ aDesiredStretchSize.leftBearing *= scale;
+ aDesiredStretchSize.rightBearing *= scale;
+ aDesiredStretchSize.width *= scale;
+ }
+
+ // increase the height if it is not largeopFactor times larger
+ // than the initial one.
+ if (NS_STRETCH_INTEGRAL & aStretchHint) {
+ // integrals are drawn taller
+ largeopFactor = kIntegralFactor;
+ }
+ if ((aDesiredStretchSize.ascent + aDesiredStretchSize.descent) <
+ largeopFactor * (initialSize.ascent + initialSize.descent)) {
+ scale = (largeopFactor *
+ (initialSize.ascent + initialSize.descent)) /
+ (aDesiredStretchSize.ascent + aDesiredStretchSize.descent);
+ if (!maxWidth) {
+ mScaleY *= scale;
+ }
+ aDesiredStretchSize.ascent *= scale;
+ aDesiredStretchSize.descent *= scale;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMathMLChar::Stretch(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ nsStretchDirection aStretchDirection,
+ const nsBoundingMetrics& aContainerSize,
+ nsBoundingMetrics& aDesiredStretchSize,
+ uint32_t aStretchHint,
+ bool aRTL)
+{
+ NS_ASSERTION(!(aStretchHint &
+ ~(NS_STRETCH_VARIABLE_MASK | NS_STRETCH_LARGEOP |
+ NS_STRETCH_INTEGRAL)),
+ "Unexpected stretch flags");
+
+ mDraw = DRAW_NORMAL;
+ mMirrored = aRTL && nsMathMLOperators::IsMirrorableOperator(mData);
+ mScaleY = mScaleX = 1.0;
+ mDirection = aStretchDirection;
+ nsresult rv =
+ StretchInternal(aPresContext, aDrawTarget, aFontSizeInflation, mDirection,
+ aContainerSize, aDesiredStretchSize, aStretchHint);
+
+ // Record the metrics
+ mBoundingMetrics = aDesiredStretchSize;
+
+ return rv;
+}
+
+// What happens here is that the StretchInternal algorithm is used but
+// modified by passing the NS_STRETCH_MAXWIDTH stretch hint. That causes
+// StretchInternal to return horizontal bounding metrics that are the maximum
+// that might be returned from a Stretch.
+//
+// In order to avoid considering widths of some characters in fonts that will
+// not be used for any stretch size, StretchInternal sets the initial height
+// to infinity and looks for any characters smaller than this height. When a
+// character built from parts is considered, (it will be used by Stretch for
+// any characters greater than its minimum size, so) the height is set to its
+// minimum size, so that only widths of smaller subsequent characters are
+// considered.
+nscoord
+nsMathMLChar::GetMaxWidth(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
+ float aFontSizeInflation, uint32_t aStretchHint)
+{
+ nsBoundingMetrics bm;
+ nsStretchDirection direction = NS_STRETCH_DIRECTION_VERTICAL;
+ const nsBoundingMetrics container; // zero target size
+
+ StretchInternal(aPresContext, aDrawTarget, aFontSizeInflation, direction,
+ container, bm, aStretchHint | NS_STRETCH_MAXWIDTH);
+
+ return std::max(bm.width, bm.rightBearing) - std::min(0, bm.leftBearing);
+}
+
+class nsDisplayMathMLSelectionRect : public nsDisplayItem {
+public:
+ nsDisplayMathMLSelectionRect(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect)
+ : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLSelectionRect);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayMathMLSelectionRect() {
+ MOZ_COUNT_DTOR(nsDisplayMathMLSelectionRect);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLSelectionRect", TYPE_MATHML_SELECTION_RECT)
+private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLSelectionRect::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ Rect rect = NSRectToSnappedRect(mRect + ToReferenceFrame(),
+ mFrame->PresContext()->AppUnitsPerDevPixel(),
+ *drawTarget);
+ // get color to use for selection from the look&feel object
+ nscolor bgColor =
+ LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground,
+ NS_RGB(0, 0, 0));
+ drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(bgColor)));
+}
+
+class nsDisplayMathMLCharForeground : public nsDisplayItem {
+public:
+ nsDisplayMathMLCharForeground(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsMathMLChar* aChar,
+ uint32_t aIndex, bool aIsSelected)
+ : nsDisplayItem(aBuilder, aFrame), mChar(aChar),
+ mIndex(aIndex), mIsSelected(aIsSelected) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLCharForeground);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayMathMLCharForeground() {
+ MOZ_COUNT_DTOR(nsDisplayMathMLCharForeground);
+ }
+#endif
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override {
+ *aSnap = false;
+ nsRect rect;
+ mChar->GetRect(rect);
+ nsPoint offset = ToReferenceFrame() + rect.TopLeft();
+ nsBoundingMetrics bm;
+ mChar->GetBoundingMetrics(bm);
+ nsRect temp(offset.x + bm.leftBearing, offset.y,
+ bm.rightBearing - bm.leftBearing, bm.ascent + bm.descent);
+ // Bug 748220
+ temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
+ return temp;
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override
+ {
+ mChar->PaintForeground(mFrame->PresContext(), *aCtx,
+ ToReferenceFrame(), mIsSelected);
+ }
+
+ NS_DISPLAY_DECL_NAME("MathMLCharForeground", TYPE_MATHML_CHAR_FOREGROUND)
+
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+ {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+
+ virtual uint32_t GetPerFrameKey() override {
+ return (mIndex << nsDisplayItem::TYPE_BITS)
+ | nsDisplayItem::GetPerFrameKey();
+ }
+
+private:
+ nsMathMLChar* mChar;
+ uint32_t mIndex;
+ bool mIsSelected;
+};
+
+#ifdef DEBUG
+class nsDisplayMathMLCharDebug : public nsDisplayItem {
+public:
+ nsDisplayMathMLCharDebug(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect)
+ : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLCharDebug);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayMathMLCharDebug() {
+ MOZ_COUNT_DTOR(nsDisplayMathMLCharDebug);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLCharDebug", TYPE_MATHML_CHAR_DEBUG)
+
+private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLCharDebug::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ // for visual debug
+ Sides skipSides;
+ nsPresContext* presContext = mFrame->PresContext();
+ nsStyleContext* styleContext = mFrame->StyleContext();
+ nsRect rect = mRect + ToReferenceFrame();
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SYNC_DECODE_IMAGES
+ : PaintBorderFlags();
+
+ // Since this is used only for debugging, we don't need to worry about
+ // tracking the DrawResult.
+ Unused <<
+ nsCSSRendering::PaintBorder(presContext, *aCtx, mFrame, mVisibleRect,
+ rect, styleContext, flags, skipSides);
+
+ nsCSSRendering::PaintOutline(presContext, *aCtx, mFrame,
+ mVisibleRect, rect, styleContext);
+}
+#endif
+
+
+void
+nsMathMLChar::Display(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aForFrame,
+ const nsDisplayListSet& aLists,
+ uint32_t aIndex,
+ const nsRect* aSelectedRect)
+{
+ nsStyleContext* parentContext = mStyleContext->GetParent();
+ nsStyleContext* styleContext = mStyleContext;
+
+ if (mDraw == DRAW_NORMAL) {
+ // normal drawing if there is nothing special about this char
+ // Set default context to the parent context
+ styleContext = parentContext;
+ }
+
+ if (!styleContext->StyleVisibility()->IsVisible())
+ return;
+
+ // if the leaf style context that we use for stretchy chars has a background
+ // color we use it -- this feature is mostly used for testing and debugging
+ // purposes. Normally, users will set the background on the container frame.
+ // paint the selection background -- beware MathML frames overlap a lot
+ if (aSelectedRect && !aSelectedRect->IsEmpty()) {
+ aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
+ nsDisplayMathMLSelectionRect(aBuilder, aForFrame, *aSelectedRect));
+ }
+ else if (mRect.width && mRect.height) {
+ const nsStyleBackground* backg = styleContext->StyleBackground();
+ if (styleContext != parentContext &&
+ NS_GET_A(backg->mBackgroundColor) > 0) {
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, aForFrame, mRect, aLists.BorderBackground(),
+ /* aAllowWillPaintBorderOptimization */ true, styleContext);
+ }
+ //else
+ // our container frame will take care of painting its background
+
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ // for visual debug
+ aLists.BorderBackground()->AppendToTop(new (aBuilder)
+ nsDisplayMathMLCharDebug(aBuilder, aForFrame, mRect));
+#endif
+ }
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayMathMLCharForeground(aBuilder, aForFrame, this,
+ aIndex,
+ aSelectedRect &&
+ !aSelectedRect->IsEmpty()));
+}
+
+void
+nsMathMLChar::ApplyTransforms(gfxContext* aThebesContext,
+ int32_t aAppUnitsPerGfxUnit,
+ nsRect &r)
+{
+ // apply the transforms
+ if (mMirrored) {
+ nsPoint pt = r.TopRight();
+ gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit),
+ NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit));
+ aThebesContext->SetMatrix(
+ aThebesContext->CurrentMatrix().Translate(devPixelOffset).
+ Scale(-mScaleX, mScaleY));
+ } else {
+ nsPoint pt = r.TopLeft();
+ gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit),
+ NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit));
+ aThebesContext->SetMatrix(
+ aThebesContext->CurrentMatrix().Translate(devPixelOffset).
+ Scale(mScaleX, mScaleY));
+ }
+
+ // update the bounding rectangle.
+ r.x = r.y = 0;
+ r.width /= mScaleX;
+ r.height /= mScaleY;
+}
+
+void
+nsMathMLChar::PaintForeground(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsPoint aPt,
+ bool aIsSelected)
+{
+ nsStyleContext* parentContext = mStyleContext->GetParent();
+ nsStyleContext* styleContext = mStyleContext;
+
+ if (mDraw == DRAW_NORMAL) {
+ // normal drawing if there is nothing special about this char
+ // Set default context to the parent context
+ styleContext = parentContext;
+ }
+
+ RefPtr<gfxContext> thebesContext = aRenderingContext.ThebesContext();
+
+ // Set color ...
+ nscolor fgColor = styleContext->
+ GetVisitedDependentColor(eCSSProperty__webkit_text_fill_color);
+ if (aIsSelected) {
+ // get color to use for selection from the look&feel object
+ fgColor = LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground,
+ fgColor);
+ }
+ thebesContext->SetColor(Color::FromABGR(fgColor));
+ thebesContext->Save();
+ nsRect r = mRect + aPt;
+ ApplyTransforms(thebesContext, aPresContext->AppUnitsPerDevPixel(), r);
+
+ switch(mDraw)
+ {
+ case DRAW_NORMAL:
+ case DRAW_VARIANT:
+ // draw a single glyph (base size or size variant)
+ // XXXfredw verify if mGlyphs[0] is non-null to workaround bug 973322.
+ if (mGlyphs[0]) {
+ mGlyphs[0]->Draw(Range(mGlyphs[0].get()), gfxPoint(0.0, mUnscaledAscent),
+ gfxTextRun::DrawParams(thebesContext));
+ }
+ break;
+ case DRAW_PARTS: {
+ // paint by parts
+ if (NS_STRETCH_DIRECTION_VERTICAL == mDirection)
+ PaintVertically(aPresContext, thebesContext, r, fgColor);
+ else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection)
+ PaintHorizontally(aPresContext, thebesContext, r, fgColor);
+ break;
+ }
+ default:
+ NS_NOTREACHED("Unknown drawing method");
+ break;
+ }
+
+ thebesContext->Restore();
+}
+
+/* =============================================================================
+ Helper routines that actually do the job of painting the char by parts
+ */
+
+class AutoPushClipRect {
+ gfxContext* mThebesContext;
+public:
+ AutoPushClipRect(gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit,
+ const nsRect& aRect)
+ : mThebesContext(aThebesContext) {
+ mThebesContext->Save();
+ mThebesContext->NewPath();
+ gfxRect clip = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerGfxUnit);
+ mThebesContext->SnappedRectangle(clip);
+ mThebesContext->Clip();
+ }
+ ~AutoPushClipRect() {
+ mThebesContext->Restore();
+ }
+};
+
+static nsPoint
+SnapToDevPixels(const gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit,
+ const nsPoint& aPt)
+{
+ gfxPoint pt(NSAppUnitsToFloatPixels(aPt.x, aAppUnitsPerGfxUnit),
+ NSAppUnitsToFloatPixels(aPt.y, aAppUnitsPerGfxUnit));
+ pt = aThebesContext->UserToDevice(pt);
+ pt.Round();
+ pt = aThebesContext->DeviceToUser(pt);
+ return nsPoint(NSFloatPixelsToAppUnits(pt.x, aAppUnitsPerGfxUnit),
+ NSFloatPixelsToAppUnits(pt.y, aAppUnitsPerGfxUnit));
+}
+
+static void
+PaintRule(DrawTarget& aDrawTarget,
+ int32_t aAppUnitsPerGfxUnit,
+ nsRect& aRect,
+ nscolor aColor)
+{
+ Rect rect = NSRectToSnappedRect(aRect, aAppUnitsPerGfxUnit, aDrawTarget);
+ ColorPattern color(ToDeviceColor(aColor));
+ aDrawTarget.FillRect(rect, color);
+}
+
+// paint a stretchy char by assembling glyphs vertically
+nsresult
+nsMathMLChar::PaintVertically(nsPresContext* aPresContext,
+ gfxContext* aThebesContext,
+ nsRect& aRect,
+ nscolor aColor)
+{
+ DrawTarget& aDrawTarget = *aThebesContext->GetDrawTarget();
+
+ // Get the device pixel size in the vertical direction.
+ // (This makes no effort to optimize for non-translation transformations.)
+ nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
+
+ // get metrics data to be re-used later
+ int32_t i = 0;
+ nscoord dx = aRect.x;
+ nscoord offset[3], start[3], end[3];
+ for (i = 0; i <= 2; ++i) {
+ const nsBoundingMetrics& bm = mBmData[i];
+ nscoord dy;
+ if (0 == i) { // top
+ dy = aRect.y + bm.ascent;
+ }
+ else if (2 == i) { // bottom
+ dy = aRect.y + aRect.height - bm.descent;
+ }
+ else { // middle
+ dy = aRect.y + bm.ascent + (aRect.height - (bm.ascent + bm.descent))/2;
+ }
+ // _cairo_scaled_font_show_glyphs snaps origins to device pixels.
+ // Do this now so that we can get the other dimensions right.
+ // (This may not achieve much with non-rectangular transformations.)
+ dy = SnapToDevPixels(aThebesContext, oneDevPixel, nsPoint(dx, dy)).y;
+ // abcissa passed to Draw
+ offset[i] = dy;
+ // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest
+ // pixel, so the bm values can include 1 row of faint pixels on each edge.
+ // Don't rely on this pixel as it can look like a gap.
+ if (bm.ascent + bm.descent >= 2 * oneDevPixel) {
+ start[i] = dy - bm.ascent + oneDevPixel; // top join
+ end[i] = dy + bm.descent - oneDevPixel; // bottom join
+ } else {
+ // To avoid overlaps, we don't add one pixel on each side when the part
+ // is too small.
+ start[i] = dy - bm.ascent; // top join
+ end[i] = dy + bm.descent; // bottom join
+ }
+ }
+
+ // If there are overlaps, then join at the mid point
+ for (i = 0; i < 2; ++i) {
+ if (end[i] > start[i+1]) {
+ end[i] = (end[i] + start[i+1]) / 2;
+ start[i+1] = end[i];
+ }
+ }
+
+ nsRect unionRect = aRect;
+ unionRect.x += mBoundingMetrics.leftBearing;
+ unionRect.width =
+ mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
+ unionRect.Inflate(oneDevPixel, oneDevPixel);
+
+ gfxTextRun::DrawParams params(aThebesContext);
+
+ /////////////////////////////////////
+ // draw top, middle, bottom
+ for (i = 0; i <= 2; ++i) {
+ // glue can be null
+ if (mGlyphs[i]) {
+ nscoord dy = offset[i];
+ // Draw a glyph in a clipped area so that we don't have hairy chars
+ // pending outside
+ nsRect clipRect = unionRect;
+ // Clip at the join to get a solid edge (without overlap or gap), when
+ // this won't change the glyph too much. If the glyph is too small to
+ // clip then we'll overlap rather than have a gap.
+ nscoord height = mBmData[i].ascent + mBmData[i].descent;
+ if (height * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) {
+ if (0 == i) { // top
+ clipRect.height = end[i] - clipRect.y;
+ }
+ else if (2 == i) { // bottom
+ clipRect.height -= start[i] - clipRect.y;
+ clipRect.y = start[i];
+ }
+ else { // middle
+ clipRect.y = start[i];
+ clipRect.height = end[i] - start[i];
+ }
+ }
+ if (!clipRect.IsEmpty()) {
+ AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
+ mGlyphs[i]->Draw(Range(mGlyphs[i].get()), gfxPoint(dx, dy), params);
+ }
+ }
+ }
+
+ ///////////////
+ // fill the gap between top and middle, and between middle and bottom.
+ if (!mGlyphs[3]) { // null glue : draw a rule
+ // figure out the dimensions of the rule to be drawn :
+ // set lbearing to rightmost lbearing among the two current successive
+ // parts.
+ // set rbearing to leftmost rbearing among the two current successive parts.
+ // this not only satisfies the convention used for over/underbraces
+ // in TeX, but also takes care of broken fonts like the stretchy integral
+ // in Symbol for small font sizes in unix.
+ nscoord lbearing, rbearing;
+ int32_t first = 0, last = 1;
+ while (last <= 2) {
+ if (mGlyphs[last]) {
+ lbearing = mBmData[last].leftBearing;
+ rbearing = mBmData[last].rightBearing;
+ if (mGlyphs[first]) {
+ if (lbearing < mBmData[first].leftBearing)
+ lbearing = mBmData[first].leftBearing;
+ if (rbearing > mBmData[first].rightBearing)
+ rbearing = mBmData[first].rightBearing;
+ }
+ }
+ else if (mGlyphs[first]) {
+ lbearing = mBmData[first].leftBearing;
+ rbearing = mBmData[first].rightBearing;
+ }
+ else {
+ NS_ERROR("Cannot stretch - All parts missing");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // paint the rule between the parts
+ nsRect rule(aRect.x + lbearing, end[first],
+ rbearing - lbearing, start[last] - end[first]);
+ PaintRule(aDrawTarget, oneDevPixel, rule, aColor);
+ first = last;
+ last++;
+ }
+ }
+ else if (mBmData[3].ascent + mBmData[3].descent > 0) {
+ // glue is present
+ nsBoundingMetrics& bm = mBmData[3];
+ // Ensure the stride for the glue is not reduced to less than one pixel
+ if (bm.ascent + bm.descent >= 3 * oneDevPixel) {
+ // To protect against gaps, pretend the glue is smaller than it is,
+ // in order to trim off ends and thus get a solid edge for the join.
+ bm.ascent -= oneDevPixel;
+ bm.descent -= oneDevPixel;
+ }
+
+ nsRect clipRect = unionRect;
+
+ for (i = 0; i < 2; ++i) {
+ // Make sure not to draw outside the character
+ nscoord dy = std::max(end[i], aRect.y);
+ nscoord fillEnd = std::min(start[i+1], aRect.YMost());
+ while (dy < fillEnd) {
+ clipRect.y = dy;
+ clipRect.height = std::min(bm.ascent + bm.descent, fillEnd - dy);
+ AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
+ dy += bm.ascent;
+ mGlyphs[3]->Draw(Range(mGlyphs[3].get()), gfxPoint(dx, dy), params);
+ dy += bm.descent;
+ }
+ }
+ }
+#ifdef DEBUG
+ else {
+ for (i = 0; i < 2; ++i) {
+ NS_ASSERTION(end[i] >= start[i+1],
+ "gap between parts with missing glue glyph");
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+// paint a stretchy char by assembling glyphs horizontally
+nsresult
+nsMathMLChar::PaintHorizontally(nsPresContext* aPresContext,
+ gfxContext* aThebesContext,
+ nsRect& aRect,
+ nscolor aColor)
+{
+ DrawTarget& aDrawTarget = *aThebesContext->GetDrawTarget();
+
+ // Get the device pixel size in the horizontal direction.
+ // (This makes no effort to optimize for non-translation transformations.)
+ nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
+
+ // get metrics data to be re-used later
+ int32_t i = 0;
+ nscoord dy = aRect.y + mBoundingMetrics.ascent;
+ nscoord offset[3], start[3], end[3];
+ for (i = 0; i <= 2; ++i) {
+ const nsBoundingMetrics& bm = mBmData[i];
+ nscoord dx;
+ if (0 == i) { // left
+ dx = aRect.x - bm.leftBearing;
+ }
+ else if (2 == i) { // right
+ dx = aRect.x + aRect.width - bm.rightBearing;
+ }
+ else { // middle
+ dx = aRect.x + (aRect.width - bm.width)/2;
+ }
+ // _cairo_scaled_font_show_glyphs snaps origins to device pixels.
+ // Do this now so that we can get the other dimensions right.
+ // (This may not achieve much with non-rectangular transformations.)
+ dx = SnapToDevPixels(aThebesContext, oneDevPixel, nsPoint(dx, dy)).x;
+ // abcissa passed to Draw
+ offset[i] = dx;
+ // _cairo_scaled_font_glyph_device_extents rounds outwards to the nearest
+ // pixel, so the bm values can include 1 row of faint pixels on each edge.
+ // Don't rely on this pixel as it can look like a gap.
+ if (bm.rightBearing - bm.leftBearing >= 2 * oneDevPixel) {
+ start[i] = dx + bm.leftBearing + oneDevPixel; // left join
+ end[i] = dx + bm.rightBearing - oneDevPixel; // right join
+ } else {
+ // To avoid overlaps, we don't add one pixel on each side when the part
+ // is too small.
+ start[i] = dx + bm.leftBearing; // left join
+ end[i] = dx + bm.rightBearing; // right join
+ }
+ }
+
+ // If there are overlaps, then join at the mid point
+ for (i = 0; i < 2; ++i) {
+ if (end[i] > start[i+1]) {
+ end[i] = (end[i] + start[i+1]) / 2;
+ start[i+1] = end[i];
+ }
+ }
+
+ nsRect unionRect = aRect;
+ unionRect.Inflate(oneDevPixel, oneDevPixel);
+
+ gfxTextRun::DrawParams params(aThebesContext);
+
+ ///////////////////////////
+ // draw left, middle, right
+ for (i = 0; i <= 2; ++i) {
+ // glue can be null
+ if (mGlyphs[i]) {
+ nscoord dx = offset[i];
+ nsRect clipRect = unionRect;
+ // Clip at the join to get a solid edge (without overlap or gap), when
+ // this won't change the glyph too much. If the glyph is too small to
+ // clip then we'll overlap rather than have a gap.
+ nscoord width = mBmData[i].rightBearing - mBmData[i].leftBearing;
+ if (width * (1.0 - NS_MATHML_DELIMITER_FACTOR) > oneDevPixel) {
+ if (0 == i) { // left
+ clipRect.width = end[i] - clipRect.x;
+ }
+ else if (2 == i) { // right
+ clipRect.width -= start[i] - clipRect.x;
+ clipRect.x = start[i];
+ }
+ else { // middle
+ clipRect.x = start[i];
+ clipRect.width = end[i] - start[i];
+ }
+ }
+ if (!clipRect.IsEmpty()) {
+ AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
+ mGlyphs[i]->Draw(Range(mGlyphs[i].get()), gfxPoint(dx, dy), params);
+ }
+ }
+ }
+
+ ////////////////
+ // fill the gap between left and middle, and between middle and right.
+ if (!mGlyphs[3]) { // null glue : draw a rule
+ // figure out the dimensions of the rule to be drawn :
+ // set ascent to lowest ascent among the two current successive parts.
+ // set descent to highest descent among the two current successive parts.
+ // this satisfies the convention used for over/underbraces, and helps
+ // fix broken fonts.
+ nscoord ascent, descent;
+ int32_t first = 0, last = 1;
+ while (last <= 2) {
+ if (mGlyphs[last]) {
+ ascent = mBmData[last].ascent;
+ descent = mBmData[last].descent;
+ if (mGlyphs[first]) {
+ if (ascent > mBmData[first].ascent)
+ ascent = mBmData[first].ascent;
+ if (descent > mBmData[first].descent)
+ descent = mBmData[first].descent;
+ }
+ }
+ else if (mGlyphs[first]) {
+ ascent = mBmData[first].ascent;
+ descent = mBmData[first].descent;
+ }
+ else {
+ NS_ERROR("Cannot stretch - All parts missing");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // paint the rule between the parts
+ nsRect rule(end[first], dy - ascent,
+ start[last] - end[first], ascent + descent);
+ PaintRule(aDrawTarget, oneDevPixel, rule, aColor);
+ first = last;
+ last++;
+ }
+ }
+ else if (mBmData[3].rightBearing - mBmData[3].leftBearing > 0) {
+ // glue is present
+ nsBoundingMetrics& bm = mBmData[3];
+ // Ensure the stride for the glue is not reduced to less than one pixel
+ if (bm.rightBearing - bm.leftBearing >= 3 * oneDevPixel) {
+ // To protect against gaps, pretend the glue is smaller than it is,
+ // in order to trim off ends and thus get a solid edge for the join.
+ bm.leftBearing += oneDevPixel;
+ bm.rightBearing -= oneDevPixel;
+ }
+
+ nsRect clipRect = unionRect;
+
+ for (i = 0; i < 2; ++i) {
+ // Make sure not to draw outside the character
+ nscoord dx = std::max(end[i], aRect.x);
+ nscoord fillEnd = std::min(start[i+1], aRect.XMost());
+ while (dx < fillEnd) {
+ clipRect.x = dx;
+ clipRect.width = std::min(bm.rightBearing - bm.leftBearing, fillEnd - dx);
+ AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
+ dx -= bm.leftBearing;
+ mGlyphs[3]->Draw(Range(mGlyphs[3].get()), gfxPoint(dx, dy), params);
+ dx += bm.rightBearing;
+ }
+ }
+ }
+#ifdef DEBUG
+ else { // no glue
+ for (i = 0; i < 2; ++i) {
+ NS_ASSERTION(end[i] >= start[i+1],
+ "gap between parts with missing glue glyph");
+ }
+ }
+#endif
+ return NS_OK;
+}
diff --git a/layout/mathml/nsMathMLChar.h b/layout/mathml/nsMathMLChar.h
new file mode 100644
index 000000000..fc61d1939
--- /dev/null
+++ b/layout/mathml/nsMathMLChar.h
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLChar_h___
+#define nsMathMLChar_h___
+
+#include "nsColor.h"
+#include "nsMathMLOperators.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsBoundingMetrics.h"
+#include "gfxTextRun.h"
+
+class nsGlyphTable;
+class nsIFrame;
+class nsDisplayListBuilder;
+class nsDisplayListSet;
+class nsPresContext;
+class nsRenderingContext;
+struct nsBoundingMetrics;
+class nsStyleContext;
+struct nsFont;
+
+// Hints for Stretch() to indicate criteria for stretching
+enum {
+ // Don't stretch
+ NS_STRETCH_NONE = 0x00,
+ // Variable size stretches
+ NS_STRETCH_VARIABLE_MASK = 0x0F,
+ NS_STRETCH_NORMAL = 0x01, // try to stretch to requested size
+ NS_STRETCH_NEARER = 0x02, // stretch very close to requested size
+ NS_STRETCH_SMALLER = 0x04, // don't stretch more than requested size
+ NS_STRETCH_LARGER = 0x08, // don't stretch less than requested size
+ // A largeop in displaystyle
+ NS_STRETCH_LARGEOP = 0x10,
+ NS_STRETCH_INTEGRAL = 0x20,
+
+ // Intended for internal use:
+ // Find the widest metrics that might be returned from a vertical stretch
+ NS_STRETCH_MAXWIDTH = 0x40
+};
+
+// A single glyph in our internal representation is either
+// 1) a 'code@font' pair from the mathfontFONTFAMILY.properties table. The
+// 'code' is interpreted as a Unicode point. The 'font' is a numeric
+// identifier given to the font to which the glyph belongs, which is 0 for the
+// FONTFAMILY and > 0 for 'external' fonts.
+// 2) a glyph index from the Open Type MATH table. In that case, all the glyphs
+// come from the font containing that table and 'font' is just set to -1.
+struct nsGlyphCode {
+ union {
+ char16_t code[2];
+ uint32_t glyphID;
+ };
+ int8_t font;
+
+ bool IsGlyphID() const { return font == -1; }
+
+ int32_t Length() const {
+ return (IsGlyphID() || code[1] == char16_t('\0') ? 1 : 2);
+ }
+ bool Exists() const
+ {
+ return IsGlyphID() ? glyphID != 0 : code[0] != 0;
+ }
+ bool operator==(const nsGlyphCode& other) const
+ {
+ return (other.font == font &&
+ ((IsGlyphID() && other.glyphID == glyphID) ||
+ (!IsGlyphID() && other.code[0] == code[0] &&
+ other.code[1] == code[1])));
+ }
+ bool operator!=(const nsGlyphCode& other) const
+ {
+ return ! operator==(other);
+ }
+};
+
+// Class used to handle stretchy symbols (accent, delimiter and boundary
+// symbols).
+class nsMathMLChar
+{
+public:
+ typedef gfxTextRun::Range Range;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ // constructor and destructor
+ nsMathMLChar() {
+ MOZ_COUNT_CTOR(nsMathMLChar);
+ mStyleContext = nullptr;
+ mUnscaledAscent = 0;
+ mScaleX = mScaleY = 1.0;
+ mDraw = DRAW_NORMAL;
+ mMirrored = false;
+ }
+
+ // not a virtual destructor: this class is not intended to be subclassed
+ ~nsMathMLChar();
+
+ void Display(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aForFrame,
+ const nsDisplayListSet& aLists,
+ uint32_t aIndex,
+ const nsRect* aSelectedRect = nullptr);
+
+ void PaintForeground(nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ nsPoint aPt,
+ bool aIsSelected);
+
+ // This is the method called to ask the char to stretch itself.
+ // @param aContainerSize - IN - suggested size for the stretched char
+ // @param aDesiredStretchSize - OUT - the size that the char wants
+ nsresult
+ Stretch(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ nsStretchDirection aStretchDirection,
+ const nsBoundingMetrics& aContainerSize,
+ nsBoundingMetrics& aDesiredStretchSize,
+ uint32_t aStretchHint,
+ bool aRTL);
+
+ void
+ SetData(nsString& aData);
+
+ void
+ GetData(nsString& aData) {
+ aData = mData;
+ }
+
+ int32_t
+ Length() {
+ return mData.Length();
+ }
+
+ nsStretchDirection
+ GetStretchDirection() {
+ return mDirection;
+ }
+
+ // Sometimes we only want to pass the data to another routine,
+ // this function helps to avoid copying
+ const char16_t*
+ get() {
+ return mData.get();
+ }
+
+ void
+ GetRect(nsRect& aRect) {
+ aRect = mRect;
+ }
+
+ void
+ SetRect(const nsRect& aRect) {
+ mRect = aRect;
+ }
+
+ // Get the maximum width that the character might have after a vertical
+ // Stretch().
+ //
+ // @param aStretchHint can be the value that will be passed to Stretch().
+ // It is used to determine whether the operator is stretchy or a largeop.
+ nscoord
+ GetMaxWidth(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ uint32_t aStretchHint = NS_STRETCH_NORMAL);
+
+ // Metrics that _exactly_ enclose the char. The char *must* have *already*
+ // being stretched before you can call the GetBoundingMetrics() method.
+ // IMPORTANT: since chars have their own style contexts, and may be rendered
+ // with glyphs that are not in the parent font, just calling the default
+ // aRenderingContext.GetBoundingMetrics(aChar) can give incorrect results.
+ void
+ GetBoundingMetrics(nsBoundingMetrics& aBoundingMetrics) {
+ aBoundingMetrics = mBoundingMetrics;
+ }
+
+ void
+ SetBoundingMetrics(nsBoundingMetrics& aBoundingMetrics) {
+ mBoundingMetrics = aBoundingMetrics;
+ }
+
+ // Hooks to access the extra leaf style contexts given to the MathMLChars.
+ // They provide an interface to make them accessible to the Style System via
+ // the Get/Set AdditionalStyleContext() APIs. Owners of MathMLChars
+ // should honor these APIs.
+ nsStyleContext* GetStyleContext() const;
+
+ void SetStyleContext(nsStyleContext* aStyleContext);
+
+protected:
+ friend class nsGlyphTable;
+ friend class nsPropertiesTable;
+ friend class nsOpenTypeTable;
+ nsString mData;
+
+private:
+ nsRect mRect;
+ nsStretchDirection mDirection;
+ nsBoundingMetrics mBoundingMetrics;
+ nsStyleContext* mStyleContext;
+ // mGlyphs/mBmData are arrays describing the glyphs used to draw the operator.
+ // See the drawing methods below.
+ RefPtr<gfxTextRun> mGlyphs[4];
+ nsBoundingMetrics mBmData[4];
+ // mUnscaledAscent is the actual ascent of the char.
+ nscoord mUnscaledAscent;
+ // mScaleX, mScaleY are the factors by which we scale the char.
+ float mScaleX, mScaleY;
+
+ // mDraw indicates how we draw the stretchy operator:
+ // - DRAW_NORMAL: we render the mData string normally.
+ // - DRAW_VARIANT: we draw a larger size variant given by mGlyphs[0].
+ // - DRAW_PARTS: we assemble several parts given by mGlyphs[0], ... mGlyphs[4]
+ // XXXfredw: the MATH table can have any numbers of parts and extenders.
+ enum DrawingMethod {
+ DRAW_NORMAL, DRAW_VARIANT, DRAW_PARTS
+ };
+ DrawingMethod mDraw;
+
+ // mMirrored indicates whether the character is mirrored.
+ bool mMirrored;
+
+ class StretchEnumContext;
+ friend class StretchEnumContext;
+
+ // helper methods
+ bool
+ SetFontFamily(nsPresContext* aPresContext,
+ const nsGlyphTable* aGlyphTable,
+ const nsGlyphCode& aGlyphCode,
+ const mozilla::FontFamilyList& aDefaultFamily,
+ nsFont& aFont,
+ RefPtr<gfxFontGroup>* aFontGroup);
+
+ nsresult
+ StretchInternal(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ nsStretchDirection& aStretchDirection,
+ const nsBoundingMetrics& aContainerSize,
+ nsBoundingMetrics& aDesiredStretchSize,
+ uint32_t aStretchHint,
+ float aMaxSize = NS_MATHML_OPERATOR_SIZE_INFINITY,
+ bool aMaxSizeIsAbsolute = false);
+
+ nsresult
+ PaintVertically(nsPresContext* aPresContext,
+ gfxContext* aThebesContext,
+ nsRect& aRect,
+ nscolor aColor);
+
+ nsresult
+ PaintHorizontally(nsPresContext* aPresContext,
+ gfxContext* aThebesContext,
+ nsRect& aRect,
+ nscolor aColor);
+
+ void
+ ApplyTransforms(gfxContext* aThebesContext, int32_t aAppUnitsPerGfxUnit,
+ nsRect &r);
+};
+
+#endif /* nsMathMLChar_h___ */
diff --git a/layout/mathml/nsMathMLContainerFrame.cpp b/layout/mathml/nsMathMLContainerFrame.cpp
new file mode 100644
index 000000000..ad1b13efd
--- /dev/null
+++ b/layout/mathml/nsMathMLContainerFrame.cpp
@@ -0,0 +1,1607 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLContainerFrame.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsStyleContext.h"
+#include "nsNameSpaceManager.h"
+#include "nsRenderingContext.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsGkAtoms.h"
+#include "nsDisplayList.h"
+#include "nsIReflowCallback.h"
+#include "mozilla/Likely.h"
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+#include "nsMathMLElement.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+//
+// nsMathMLContainerFrame implementation
+//
+
+NS_QUERYFRAME_HEAD(nsMathMLContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsIMathMLFrame)
+ NS_QUERYFRAME_ENTRY(nsMathMLContainerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+// =============================================================================
+
+// error handlers
+// provide a feedback to the user when a frame with bad markup can not be rendered
+nsresult
+nsMathMLContainerFrame::ReflowError(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize)
+{
+ // clear all other flags and record that there is an error with this frame
+ mEmbellishData.flags = 0;
+ mPresentationData.flags = NS_MATHML_ERROR;
+
+ ///////////////
+ // Set font
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
+
+ // bounding metrics
+ nsAutoString errorMsg; errorMsg.AssignLiteral("invalid-markup");
+ mBoundingMetrics =
+ nsLayoutUtils::AppUnitBoundsOfString(errorMsg.get(), errorMsg.Length(),
+ *fm, aDrawTarget);
+
+ // reflow metrics
+ WritingMode wm = aDesiredSize.GetWritingMode();
+ aDesiredSize.SetBlockStartAscent(fm->MaxAscent());
+ nscoord descent = fm->MaxDescent();
+ aDesiredSize.BSize(wm) = aDesiredSize.BlockStartAscent() + descent;
+ aDesiredSize.ISize(wm) = mBoundingMetrics.width;
+
+ // Also return our bounding metrics
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ return NS_OK;
+}
+
+class nsDisplayMathMLError : public nsDisplayItem {
+public:
+ nsDisplayMathMLError(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLError);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayMathMLError() {
+ MOZ_COUNT_DTOR(nsDisplayMathMLError);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLError", TYPE_MATHML_ERROR)
+};
+
+void nsDisplayMathMLError::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ // Set color and font ...
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);
+
+ nsPoint pt = ToReferenceFrame();
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ Rect rect = NSRectToSnappedRect(nsRect(pt, mFrame->GetSize()),
+ appUnitsPerDevPixel,
+ *drawTarget);
+ ColorPattern red(ToDeviceColor(Color(1.f, 0.f, 0.f, 1.f)));
+ drawTarget->FillRect(rect, red);
+
+ aCtx->ThebesContext()->SetColor(Color(1.f, 1.f, 1.f));
+ nscoord ascent = fm->MaxAscent();
+ NS_NAMED_LITERAL_STRING(errorMsg, "invalid-markup");
+ nsLayoutUtils::DrawUniDirString(errorMsg.get(), uint32_t(errorMsg.Length()),
+ nsPoint(pt.x, pt.y + ascent), *fm, *aCtx);
+}
+
+/* /////////////
+ * nsIMathMLFrame - support methods for stretchy elements
+ * =============================================================================
+ */
+
+static bool
+IsForeignChild(const nsIFrame* aFrame)
+{
+ // This counts nsMathMLmathBlockFrame as a foreign child, because it
+ // uses block reflow
+ return !(aFrame->IsFrameOfType(nsIFrame::eMathML)) ||
+ aFrame->GetType() == nsGkAtoms::blockFrame;
+}
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(HTMLReflowOutputProperty,
+ ReflowOutput)
+
+/* static */ void
+nsMathMLContainerFrame::SaveReflowAndBoundingMetricsFor(nsIFrame* aFrame,
+ const ReflowOutput& aReflowOutput,
+ const nsBoundingMetrics& aBoundingMetrics)
+{
+ ReflowOutput* reflowOutput = new ReflowOutput(aReflowOutput);
+ reflowOutput->mBoundingMetrics = aBoundingMetrics;
+ aFrame->Properties().Set(HTMLReflowOutputProperty(), reflowOutput);
+}
+
+// helper method to facilitate getting the reflow and bounding metrics
+/* static */ void
+nsMathMLContainerFrame::GetReflowAndBoundingMetricsFor(nsIFrame* aFrame,
+ ReflowOutput& aReflowOutput,
+ nsBoundingMetrics& aBoundingMetrics,
+ eMathMLFrameType* aMathMLFrameType)
+{
+ NS_PRECONDITION(aFrame, "null arg");
+
+ ReflowOutput* reflowOutput =
+ aFrame->Properties().Get(HTMLReflowOutputProperty());
+
+ // IMPORTANT: This function is only meant to be called in Place() methods
+ // where it is assumed that SaveReflowAndBoundingMetricsFor has recorded the
+ // information.
+ NS_ASSERTION(reflowOutput, "Didn't SaveReflowAndBoundingMetricsFor frame!");
+ if (reflowOutput) {
+ aReflowOutput = *reflowOutput;
+ aBoundingMetrics = reflowOutput->mBoundingMetrics;
+ }
+
+ if (aMathMLFrameType) {
+ if (!IsForeignChild(aFrame)) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
+ if (mathMLFrame) {
+ *aMathMLFrameType = mathMLFrame->GetMathMLFrameType();
+ return;
+ }
+ }
+ *aMathMLFrameType = eMathMLFrameType_UNKNOWN;
+ }
+
+}
+
+void
+nsMathMLContainerFrame::ClearSavedChildMetrics()
+{
+ nsIFrame* childFrame = mFrames.FirstChild();
+ FramePropertyTable* props = PresContext()->PropertyTable();
+ while (childFrame) {
+ props->Delete(childFrame, HTMLReflowOutputProperty());
+ childFrame = childFrame->GetNextSibling();
+ }
+}
+
+// helper to get the preferred size that a container frame should use to fire
+// the stretch on its stretchy child frames.
+void
+nsMathMLContainerFrame::GetPreferredStretchSize(DrawTarget* aDrawTarget,
+ uint32_t aOptions,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aPreferredStretchSize)
+{
+ if (aOptions & STRETCH_CONSIDER_ACTUAL_SIZE) {
+ // when our actual size is ok, just use it
+ aPreferredStretchSize = mBoundingMetrics;
+ }
+ else if (aOptions & STRETCH_CONSIDER_EMBELLISHMENTS) {
+ // compute our up-to-date size using Place()
+ ReflowOutput reflowOutput(GetWritingMode());
+ Place(aDrawTarget, false, reflowOutput);
+ aPreferredStretchSize = reflowOutput.mBoundingMetrics;
+ }
+ else {
+ // compute a size that includes embellishments iff the container stretches
+ // in the same direction as the embellished operator.
+ bool stretchAll = aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL ?
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) :
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags);
+ NS_ASSERTION(aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL ||
+ aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL,
+ "You must specify a direction in which to stretch");
+ NS_ASSERTION(NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) ||
+ stretchAll,
+ "invalid call to GetPreferredStretchSize");
+ bool firstTime = true;
+ nsBoundingMetrics bm, bmChild;
+ nsIFrame* childFrame =
+ stretchAll ? PrincipalChildList().FirstChild() : mPresentationData.baseFrame;
+ while (childFrame) {
+ // initializations in case this child happens not to be a MathML frame
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame);
+ if (mathMLFrame) {
+ nsEmbellishData embellishData;
+ nsPresentationData presentationData;
+ mathMLFrame->GetEmbellishData(embellishData);
+ mathMLFrame->GetPresentationData(presentationData);
+ if (NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags) &&
+ embellishData.direction == aStretchDirection &&
+ presentationData.baseFrame) {
+ // embellishements are not included, only consider the inner first child itself
+ // XXXkt Does that mean the core descendent frame should be used
+ // instead of the base child?
+ nsIMathMLFrame* mathMLchildFrame = do_QueryFrame(presentationData.baseFrame);
+ if (mathMLchildFrame) {
+ mathMLFrame = mathMLchildFrame;
+ }
+ }
+ mathMLFrame->GetBoundingMetrics(bmChild);
+ }
+ else {
+ ReflowOutput unused(GetWritingMode());
+ GetReflowAndBoundingMetricsFor(childFrame, unused, bmChild);
+ }
+
+ if (firstTime) {
+ firstTime = false;
+ bm = bmChild;
+ if (!stretchAll) {
+ // we may get here for cases such as <msup><mo>...</mo> ... </msup>,
+ // or <maction>...<mo>...</mo></maction>.
+ break;
+ }
+ }
+ else {
+ if (aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL) {
+ // if we get here, it means this is container that will stack its children
+ // vertically and fire an horizontal stretch on each them. This is the case
+ // for \munder, \mover, \munderover. We just sum-up the size vertically.
+ bm.descent += bmChild.ascent + bmChild.descent;
+ // Sometimes non-spacing marks (when width is zero) are positioned
+ // to the left of the origin, but it is the distance between left
+ // and right bearing that is important rather than the offsets from
+ // the origin.
+ if (bmChild.width == 0) {
+ bmChild.rightBearing -= bmChild.leftBearing;
+ bmChild.leftBearing = 0;
+ }
+ if (bm.leftBearing > bmChild.leftBearing)
+ bm.leftBearing = bmChild.leftBearing;
+ if (bm.rightBearing < bmChild.rightBearing)
+ bm.rightBearing = bmChild.rightBearing;
+ }
+ else if (aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL) {
+ // just sum-up the sizes horizontally.
+ bm += bmChild;
+ }
+ else {
+ NS_ERROR("unexpected case in GetPreferredStretchSize");
+ break;
+ }
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ aPreferredStretchSize = bm;
+ }
+}
+
+NS_IMETHODIMP
+nsMathMLContainerFrame::Stretch(DrawTarget* aDrawTarget,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aContainerSize,
+ ReflowOutput& aDesiredStretchSize)
+{
+ if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) {
+
+ if (NS_MATHML_STRETCH_WAS_DONE(mPresentationData.flags)) {
+ NS_WARNING("it is wrong to fire stretch more than once on a frame");
+ return NS_OK;
+ }
+ mPresentationData.flags |= NS_MATHML_STRETCH_DONE;
+
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
+ NS_WARNING("it is wrong to fire stretch on a erroneous frame");
+ return NS_OK;
+ }
+
+ // Pass the stretch to the base child ...
+
+ nsIFrame* baseFrame = mPresentationData.baseFrame;
+ if (baseFrame) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(baseFrame);
+ NS_ASSERTION(mathMLFrame, "Something is wrong somewhere");
+ if (mathMLFrame) {
+
+ // And the trick is that the child's rect.x is still holding the descent,
+ // and rect.y is still holding the ascent ...
+ ReflowOutput childSize(aDesiredStretchSize);
+ GetReflowAndBoundingMetricsFor(baseFrame, childSize, childSize.mBoundingMetrics);
+
+ // See if we should downsize and confine the stretch to us...
+ // XXX there may be other cases where we can downsize the stretch,
+ // e.g., the first &Sum; might appear big in the following situation
+ // <math xmlns='http://www.w3.org/1998/Math/MathML'>
+ // <mstyle>
+ // <msub>
+ // <msub><mo>&Sum;</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub>
+ // <msub><mo>&Sum;</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub>
+ // </msub>
+ // </mstyle>
+ // </math>
+ nsBoundingMetrics containerSize = aContainerSize;
+ if (aStretchDirection != mEmbellishData.direction &&
+ mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED) {
+ NS_ASSERTION(mEmbellishData.direction != NS_STRETCH_DIRECTION_DEFAULT,
+ "Stretches may have a default direction, operators can not.");
+ if (mEmbellishData.direction == NS_STRETCH_DIRECTION_VERTICAL ?
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) :
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags)) {
+ GetPreferredStretchSize(aDrawTarget, 0,
+ mEmbellishData.direction, containerSize);
+ // Stop further recalculations
+ aStretchDirection = mEmbellishData.direction;
+ } else {
+ // We aren't going to stretch the child, so just use the child metrics.
+ containerSize = childSize.mBoundingMetrics;
+ }
+ }
+
+ // do the stretching...
+ mathMLFrame->Stretch(aDrawTarget,
+ aStretchDirection, containerSize, childSize);
+ // store the updated metrics
+ SaveReflowAndBoundingMetricsFor(baseFrame, childSize,
+ childSize.mBoundingMetrics);
+
+ // Remember the siblings which were _deferred_.
+ // Now that this embellished child may have changed, we need to
+ // fire the stretch on its siblings using our updated size
+
+ if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) ||
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags)) {
+
+ nsStretchDirection stretchDir =
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) ?
+ NS_STRETCH_DIRECTION_VERTICAL : NS_STRETCH_DIRECTION_HORIZONTAL;
+
+ GetPreferredStretchSize(aDrawTarget, STRETCH_CONSIDER_EMBELLISHMENTS,
+ stretchDir, containerSize);
+
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ if (childFrame != mPresentationData.baseFrame) {
+ mathMLFrame = do_QueryFrame(childFrame);
+ if (mathMLFrame) {
+ // retrieve the metrics that was stored at the previous pass
+ GetReflowAndBoundingMetricsFor(childFrame,
+ childSize, childSize.mBoundingMetrics);
+ // do the stretching...
+ mathMLFrame->Stretch(aDrawTarget, stretchDir,
+ containerSize, childSize);
+ // store the updated metrics
+ SaveReflowAndBoundingMetricsFor(childFrame, childSize,
+ childSize.mBoundingMetrics);
+ }
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ }
+
+ // re-position all our children
+ nsresult rv = Place(aDrawTarget, true, aDesiredStretchSize);
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
+ // Make sure the child frames get their DidReflow() calls.
+ DidReflowChildren(mFrames.FirstChild());
+ }
+
+ // If our parent is not embellished, it means we are the outermost embellished
+ // container and so we put the spacing, otherwise we don't include the spacing,
+ // the outermost embellished container will take care of it.
+
+ nsEmbellishData parentData;
+ GetEmbellishDataFrom(GetParent(), parentData);
+ // ensure that we are the embellished child, not just a sibling
+ // (need to test coreFrame since <mfrac> resets other things)
+ if (parentData.coreFrame != mEmbellishData.coreFrame) {
+ // (we fetch values from the core since they may use units that depend
+ // on style data, and style changes could have occurred in the core since
+ // our last visit there)
+ nsEmbellishData coreData;
+ GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
+
+ mBoundingMetrics.width +=
+ coreData.leadingSpace + coreData.trailingSpace;
+ aDesiredStretchSize.Width() = mBoundingMetrics.width;
+ aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width;
+
+ nscoord dx = (StyleVisibility()->mDirection ?
+ coreData.trailingSpace : coreData.leadingSpace);
+ if (dx != 0) {
+ mBoundingMetrics.leftBearing += dx;
+ mBoundingMetrics.rightBearing += dx;
+ aDesiredStretchSize.mBoundingMetrics.leftBearing += dx;
+ aDesiredStretchSize.mBoundingMetrics.rightBearing += dx;
+
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ childFrame->SetPosition(childFrame->GetPosition()
+ + nsPoint(dx, 0));
+ childFrame = childFrame->GetNextSibling();
+ }
+ }
+ }
+
+ // Finished with these:
+ ClearSavedChildMetrics();
+ // Set our overflow area
+ GatherAndStoreOverflow(&aDesiredStretchSize);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMathMLContainerFrame::FinalizeReflow(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize)
+{
+ // During reflow, we use rect.x and rect.y as placeholders for the child's ascent
+ // and descent in expectation of a stretch command. Hence we need to ensure that
+ // a stretch command will actually be fired later on, after exiting from our
+ // reflow. If the stretch is not fired, the rect.x, and rect.y will remain
+ // with inappropriate data causing children to be improperly positioned.
+ // This helper method checks to see if our parent will fire a stretch command
+ // targeted at us. If not, we go ahead and fire an involutive stretch on
+ // ourselves. This will clear all the rect.x and rect.y, and return our
+ // desired size.
+
+
+ // First, complete the post-reflow hook.
+ // We use the information in our children rectangles to position them.
+ // If placeOrigin==false, then Place() will not touch rect.x, and rect.y.
+ // They will still be holding the ascent and descent for each child.
+
+ // The first clause caters for any non-embellished container.
+ // The second clause is for a container which won't fire stretch even though it is
+ // embellished, e.g., as in <mfrac><mo>...</mo> ... </mfrac>, the test is convoluted
+ // because it excludes the particular case of the core <mo>...</mo> itself.
+ // (<mo> needs to fire stretch on its MathMLChar in any case to initialize it)
+ bool placeOrigin = !NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) ||
+ (mEmbellishData.coreFrame != this && !mPresentationData.baseFrame &&
+ mEmbellishData.direction == NS_STRETCH_DIRECTION_UNSUPPORTED);
+ nsresult rv = Place(aDrawTarget, placeOrigin, aDesiredSize);
+
+ // Place() will call FinishReflowChild() when placeOrigin is true but if
+ // it returns before reaching FinishReflowChild() due to errors we need
+ // to fulfill the reflow protocol by calling DidReflow for the child frames
+ // that still needs it here (or we may crash - bug 366012).
+ // If placeOrigin is false we should reach Place() with aPlaceOrigin == true
+ // through Stretch() eventually.
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
+ GatherAndStoreOverflow(&aDesiredSize);
+ DidReflowChildren(PrincipalChildList().FirstChild());
+ return rv;
+ }
+
+ bool parentWillFireStretch = false;
+ if (!placeOrigin) {
+ // This means the rect.x and rect.y of our children were not set!!
+ // Don't go without checking to see if our parent will later fire a Stretch() command
+ // targeted at us. The Stretch() will cause the rect.x and rect.y to clear...
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetParent());
+ if (mathMLFrame) {
+ nsEmbellishData embellishData;
+ nsPresentationData presentationData;
+ mathMLFrame->GetEmbellishData(embellishData);
+ mathMLFrame->GetPresentationData(presentationData);
+ if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(presentationData.flags) ||
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(presentationData.flags) ||
+ (NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags)
+ && presentationData.baseFrame == this))
+ {
+ parentWillFireStretch = true;
+ }
+ }
+ if (!parentWillFireStretch) {
+ // There is nobody who will fire the stretch for us, we do it ourselves!
+
+ bool stretchAll =
+ /* NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) || */
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags);
+
+ nsStretchDirection stretchDir;
+ if (mEmbellishData.coreFrame == this || /* case of a bare <mo>...</mo> itself */
+ (mEmbellishData.direction == NS_STRETCH_DIRECTION_HORIZONTAL &&
+ stretchAll) || /* or <mover><mo>...</mo>...</mover>, or friends */
+ mEmbellishData.direction == NS_STRETCH_DIRECTION_UNSUPPORTED) { /* Doesn't stretch */
+ stretchDir = mEmbellishData.direction;
+ } else {
+ // Let the Stretch() call decide the direction.
+ stretchDir = NS_STRETCH_DIRECTION_DEFAULT;
+ }
+ // Use our current size as computed earlier by Place()
+ // The stretch call will detect if this is incorrect and recalculate the size.
+ nsBoundingMetrics defaultSize = aDesiredSize.mBoundingMetrics;
+
+ Stretch(aDrawTarget, stretchDir, defaultSize, aDesiredSize);
+#ifdef DEBUG
+ {
+ // The Place() call above didn't request FinishReflowChild(),
+ // so let's check that we eventually did through Stretch().
+ for (nsIFrame* childFrame : PrincipalChildList()) {
+ NS_ASSERTION(!(childFrame->GetStateBits() & NS_FRAME_IN_REFLOW),
+ "DidReflow() was never called");
+ }
+ }
+#endif
+ }
+ }
+
+ // Also return our bounding metrics
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ // see if we should fix the spacing
+ FixInterFrameSpacing(aDesiredSize);
+
+ if (!parentWillFireStretch) {
+ // Not expecting a stretch.
+ // Finished with these:
+ ClearSavedChildMetrics();
+ // Set our overflow area.
+ GatherAndStoreOverflow(&aDesiredSize);
+ }
+
+ return NS_OK;
+}
+
+
+/* /////////////
+ * nsIMathMLFrame - support methods for scripting elements (nested frames
+ * within msub, msup, msubsup, munder, mover, munderover, mmultiscripts,
+ * mfrac, mroot, mtable).
+ * =============================================================================
+ */
+
+// helper to let the update of presentation data pass through
+// a subtree that may contain non-mathml container frames
+/* static */ void
+nsMathMLContainerFrame::PropagatePresentationDataFor(nsIFrame* aFrame,
+ uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate)
+{
+ if (!aFrame || !aFlagsToUpdate)
+ return;
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
+ if (mathMLFrame) {
+ // update
+ mathMLFrame->UpdatePresentationData(aFlagsValues,
+ aFlagsToUpdate);
+ // propagate using the base method to make sure that the control
+ // is passed on to MathML frames that may be overloading the method
+ mathMLFrame->UpdatePresentationDataFromChildAt(0, -1,
+ aFlagsValues, aFlagsToUpdate);
+ }
+ else {
+ // propagate down the subtrees
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ PropagatePresentationDataFor(childFrame,
+ aFlagsValues, aFlagsToUpdate);
+ }
+ }
+}
+
+/* static */ void
+nsMathMLContainerFrame::PropagatePresentationDataFromChildAt(nsIFrame* aParentFrame,
+ int32_t aFirstChildIndex,
+ int32_t aLastChildIndex,
+ uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate)
+{
+ if (!aParentFrame || !aFlagsToUpdate)
+ return;
+ int32_t index = 0;
+ for (nsIFrame* childFrame : aParentFrame->PrincipalChildList()) {
+ if ((index >= aFirstChildIndex) &&
+ ((aLastChildIndex <= 0) || ((aLastChildIndex > 0) &&
+ (index <= aLastChildIndex)))) {
+ PropagatePresentationDataFor(childFrame,
+ aFlagsValues, aFlagsToUpdate);
+ }
+ index++;
+ }
+}
+
+/* //////////////////
+ * Frame construction
+ * =============================================================================
+ */
+
+
+void
+nsMathMLContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // report an error if something wrong was found in this frame
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
+ if (!IsVisibleForPainting(aBuilder))
+ return;
+
+ aLists.Content()->AppendNewToTop(
+ new (aBuilder) nsDisplayMathMLError(aBuilder, this));
+ return;
+ }
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists,
+ DISPLAY_CHILD_INLINE);
+
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ // for visual debug
+ // ----------------
+ // if you want to see your bounding box, make sure to properly fill
+ // your mBoundingMetrics and mReference point, and set
+ // mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS
+ // in the Init() of your sub-class
+ DisplayBoundingMetrics(aBuilder, this, mReference, mBoundingMetrics, aLists);
+#endif
+}
+
+// Note that this method re-builds the automatic data in the children -- not
+// in aParentFrame itself (except for those particular operations that the
+// parent frame may do in its TransmitAutomaticData()).
+/* static */ void
+nsMathMLContainerFrame::RebuildAutomaticDataForChildren(nsIFrame* aParentFrame)
+{
+ // 1. As we descend the tree, make each child frame inherit data from
+ // the parent
+ // 2. As we ascend the tree, transmit any specific change that we want
+ // down the subtrees
+ for (nsIFrame* childFrame : aParentFrame->PrincipalChildList()) {
+ nsIMathMLFrame* childMathMLFrame = do_QueryFrame(childFrame);
+ if (childMathMLFrame) {
+ childMathMLFrame->InheritAutomaticData(aParentFrame);
+ }
+ RebuildAutomaticDataForChildren(childFrame);
+ }
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(aParentFrame);
+ if (mathMLFrame) {
+ mathMLFrame->TransmitAutomaticData();
+ }
+}
+
+/* static */ nsresult
+nsMathMLContainerFrame::ReLayoutChildren(nsIFrame* aParentFrame)
+{
+ if (!aParentFrame)
+ return NS_OK;
+
+ // walk-up to the first frame that is a MathML frame, stop if we reach <math>
+ nsIFrame* frame = aParentFrame;
+ while (1) {
+ nsIFrame* parent = frame->GetParent();
+ if (!parent || !parent->GetContent())
+ break;
+
+ // stop if it is a MathML frame
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame);
+ if (mathMLFrame)
+ break;
+
+ // stop if we reach the root <math> tag
+ nsIContent* content = frame->GetContent();
+ NS_ASSERTION(content, "dangling frame without a content node");
+ if (!content)
+ break;
+ if (content->IsMathMLElement(nsGkAtoms::math))
+ break;
+
+ frame = parent;
+ }
+
+ // re-sync the presentation data and embellishment data of our children
+ RebuildAutomaticDataForChildren(frame);
+
+ // Ask our parent frame to reflow us
+ nsIFrame* parent = frame->GetParent();
+ NS_ASSERTION(parent, "No parent to pass the reflow request up to");
+ if (!parent)
+ return NS_OK;
+
+ frame->PresContext()->PresShell()->
+ FrameNeedsReflow(frame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+
+ return NS_OK;
+}
+
+// There are precise rules governing children of a MathML frame,
+// and properties such as the scriptlevel depends on those rules.
+// Hence for things to work, callers must use Append/Insert/etc wisely.
+
+nsresult
+nsMathMLContainerFrame::ChildListChanged(int32_t aModType)
+{
+ // If this is an embellished frame we need to rebuild the
+ // embellished hierarchy by walking-up to the parent of the
+ // outermost embellished container.
+ nsIFrame* frame = this;
+ if (mEmbellishData.coreFrame) {
+ nsIFrame* parent = GetParent();
+ nsEmbellishData embellishData;
+ for ( ; parent; frame = parent, parent = parent->GetParent()) {
+ GetEmbellishDataFrom(parent, embellishData);
+ if (embellishData.coreFrame != mEmbellishData.coreFrame)
+ break;
+ }
+ }
+ return ReLayoutChildren(frame);
+}
+
+void
+nsMathMLContainerFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ MOZ_ASSERT(aListID == kPrincipalList);
+ mFrames.AppendFrames(this, aFrameList);
+ ChildListChanged(nsIDOMMutationEvent::ADDITION);
+}
+
+void
+nsMathMLContainerFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ MOZ_ASSERT(aListID == kPrincipalList);
+ mFrames.InsertFrames(this, aPrevFrame, aFrameList);
+ ChildListChanged(nsIDOMMutationEvent::ADDITION);
+}
+
+void
+nsMathMLContainerFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ MOZ_ASSERT(aListID == kPrincipalList);
+ mFrames.DestroyFrame(aOldFrame);
+ ChildListChanged(nsIDOMMutationEvent::REMOVAL);
+}
+
+nsresult
+nsMathMLContainerFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // XXX Since they are numerous MathML attributes that affect layout, and
+ // we can't check all of them here, play safe by requesting a reflow.
+ // XXXldb This should only do work for attributes that cause changes!
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+
+ return NS_OK;
+}
+
+void
+nsMathMLContainerFrame::GatherAndStoreOverflow(ReflowOutput* aMetrics)
+{
+ mBlockStartAscent = aMetrics->BlockStartAscent();
+
+ // nsIFrame::FinishAndStoreOverflow likes the overflow area to include the
+ // frame rectangle.
+ aMetrics->SetOverflowAreasToDesiredBounds();
+
+ ComputeCustomOverflow(aMetrics->mOverflowAreas);
+
+ // mBoundingMetrics does not necessarily include content of <mpadded>
+ // elements whose mBoundingMetrics may not be representative of the true
+ // bounds, and doesn't include the CSS2 outline rectangles of children, so
+ // make such to include child overflow areas.
+ UnionChildOverflow(aMetrics->mOverflowAreas);
+
+ FinishAndStoreOverflow(aMetrics);
+}
+
+bool
+nsMathMLContainerFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
+{
+ // All non-child-frame content such as nsMathMLChars (and most child-frame
+ // content) is included in mBoundingMetrics.
+ nsRect boundingBox(mBoundingMetrics.leftBearing,
+ mBlockStartAscent - mBoundingMetrics.ascent,
+ mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing,
+ mBoundingMetrics.ascent + mBoundingMetrics.descent);
+
+ // REVIEW: Maybe this should contribute only to visual overflow
+ // and not scrollable?
+ aOverflowAreas.UnionAllWith(boundingBox);
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+void
+nsMathMLContainerFrame::ReflowChild(nsIFrame* aChildFrame,
+ nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ // Having foreign/hybrid children, e.g., from html markups, is not defined by
+ // the MathML spec. But it can happen in practice, e.g., <html:img> allows us
+ // to do some cool demos... or we may have a child that is an nsInlineFrame
+ // from a generated content such as :before { content: open-quote } or
+ // :after { content: close-quote }. Unfortunately, the other frames out-there
+ // may expect their own invariants that are not met when we mix things.
+ // Hence we do not claim their support, but we will nevertheless attempt to keep
+ // them in the flow, if we can get their desired size. We observed that most
+ // frames may be reflowed generically, but nsInlineFrames need extra care.
+
+#ifdef DEBUG
+ nsInlineFrame* inlineFrame = do_QueryFrame(aChildFrame);
+ NS_ASSERTION(!inlineFrame, "Inline frames should be wrapped in blocks");
+#endif
+
+ nsContainerFrame::
+ ReflowChild(aChildFrame, aPresContext, aDesiredSize, aReflowInput,
+ 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus);
+
+ if (aDesiredSize.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
+ // This will be suitable for inline frames, which are wrapped in a block.
+ nscoord ascent;
+ WritingMode wm = aDesiredSize.GetWritingMode();
+ if (!nsLayoutUtils::GetLastLineBaseline(wm, aChildFrame, &ascent)) {
+ // We don't expect any other block children so just place the frame on
+ // the baseline instead of going through DidReflow() and
+ // GetBaseline(). This is what nsFrame::GetBaseline() will do anyway.
+ aDesiredSize.SetBlockStartAscent(aDesiredSize.BSize(wm));
+ } else {
+ aDesiredSize.SetBlockStartAscent(ascent);
+ }
+ }
+ if (IsForeignChild(aChildFrame)) {
+ // use ComputeTightBounds API as aDesiredSize.mBoundingMetrics is not set.
+ nsRect r = aChildFrame->ComputeTightBounds(aReflowInput.mRenderingContext->GetDrawTarget());
+ aDesiredSize.mBoundingMetrics.leftBearing = r.x;
+ aDesiredSize.mBoundingMetrics.rightBearing = r.XMost();
+ aDesiredSize.mBoundingMetrics.ascent = aDesiredSize.BlockStartAscent() - r.y;
+ aDesiredSize.mBoundingMetrics.descent = r.YMost() - aDesiredSize.BlockStartAscent();
+ aDesiredSize.mBoundingMetrics.width = aDesiredSize.Width();
+ }
+}
+
+void
+nsMathMLContainerFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ aDesiredSize.Width() = aDesiredSize.Height() = 0;
+ aDesiredSize.SetBlockStartAscent(0);
+ aDesiredSize.mBoundingMetrics = nsBoundingMetrics();
+
+ /////////////
+ // Reflow children
+ // Asking each child to cache its bounding metrics
+
+ nsReflowStatus childStatus;
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ ReflowOutput childDesiredSize(aReflowInput, // ???
+ aDesiredSize.mFlags);
+ WritingMode wm = childFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput childReflowInput(aPresContext, aReflowInput,
+ childFrame, availSize);
+ ReflowChild(childFrame, aPresContext, childDesiredSize,
+ childReflowInput, childStatus);
+ //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status");
+ SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
+ childDesiredSize.mBoundingMetrics);
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ /////////////
+ // If we are a container which is entitled to stretch its children, then we
+ // ask our stretchy children to stretch themselves
+
+ // The stretching of siblings of an embellished child is _deferred_ until
+ // after finishing the stretching of the embellished child - bug 117652
+
+ DrawTarget* drawTarget = aReflowInput.mRenderingContext->GetDrawTarget();
+
+ if (!NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) &&
+ (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) ||
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags))) {
+
+ // get the stretchy direction
+ nsStretchDirection stretchDir =
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags)
+ ? NS_STRETCH_DIRECTION_VERTICAL
+ : NS_STRETCH_DIRECTION_HORIZONTAL;
+
+ // what size should we use to stretch our stretchy children
+ // We don't use STRETCH_CONSIDER_ACTUAL_SIZE -- because our size is not known yet
+ // We don't use STRETCH_CONSIDER_EMBELLISHMENTS -- because we don't want to
+ // include them in the caculations of the size of stretchy elements
+ nsBoundingMetrics containerSize;
+ GetPreferredStretchSize(drawTarget, 0, stretchDir, containerSize);
+
+ // fire the stretch on each child
+ childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame);
+ if (mathMLFrame) {
+ // retrieve the metrics that was stored at the previous pass
+ ReflowOutput childDesiredSize(aReflowInput);
+ GetReflowAndBoundingMetricsFor(childFrame,
+ childDesiredSize, childDesiredSize.mBoundingMetrics);
+
+ mathMLFrame->Stretch(drawTarget, stretchDir,
+ containerSize, childDesiredSize);
+ // store the updated metrics
+ SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
+ childDesiredSize.mBoundingMetrics);
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ }
+
+ /////////////
+ // Place children now by re-adjusting the origins to align the baselines
+ FinalizeReflow(drawTarget, aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+static nscoord AddInterFrameSpacingToSize(ReflowOutput& aDesiredSize,
+ nsMathMLContainerFrame* aFrame);
+
+/* virtual */ void
+nsMathMLContainerFrame::MarkIntrinsicISizesDirty()
+{
+ mIntrinsicWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
+ nsContainerFrame::MarkIntrinsicISizesDirty();
+}
+
+void
+nsMathMLContainerFrame::UpdateIntrinsicWidth(nsRenderingContext* aRenderingContext)
+{
+ if (mIntrinsicWidth == NS_INTRINSIC_WIDTH_UNKNOWN) {
+ ReflowOutput desiredSize(GetWritingMode());
+ GetIntrinsicISizeMetrics(aRenderingContext, desiredSize);
+
+ // Include the additional width added by FixInterFrameSpacing to ensure
+ // consistent width calculations.
+ AddInterFrameSpacingToSize(desiredSize, this);
+ mIntrinsicWidth = desiredSize.ISize(GetWritingMode());
+ }
+}
+
+/* virtual */ nscoord
+nsMathMLContainerFrame::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+ UpdateIntrinsicWidth(aRenderingContext);
+ result = mIntrinsicWidth;
+ return result;
+}
+
+/* virtual */ nscoord
+nsMathMLContainerFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+ UpdateIntrinsicWidth(aRenderingContext);
+ result = mIntrinsicWidth;
+ return result;
+}
+
+/* virtual */ void
+nsMathMLContainerFrame::GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext,
+ ReflowOutput& aDesiredSize)
+{
+ // Get child widths
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ ReflowOutput childDesiredSize(GetWritingMode()); // ???
+
+ nsMathMLContainerFrame* containerFrame = do_QueryFrame(childFrame);
+ if (containerFrame) {
+ containerFrame->GetIntrinsicISizeMetrics(aRenderingContext,
+ childDesiredSize);
+ } else {
+ // XXX This includes margin while Reflow currently doesn't consider
+ // margin, so we may end up with too much space, but, with stretchy
+ // characters, this is an approximation anyway.
+ nscoord width =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame,
+ nsLayoutUtils::PREF_ISIZE);
+
+ childDesiredSize.Width() = width;
+ childDesiredSize.mBoundingMetrics.width = width;
+ childDesiredSize.mBoundingMetrics.leftBearing = 0;
+ childDesiredSize.mBoundingMetrics.rightBearing = width;
+
+ nscoord x, xMost;
+ if (NS_SUCCEEDED(childFrame->GetPrefWidthTightBounds(aRenderingContext,
+ &x, &xMost))) {
+ childDesiredSize.mBoundingMetrics.leftBearing = x;
+ childDesiredSize.mBoundingMetrics.rightBearing = xMost;
+ }
+ }
+
+ SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
+ childDesiredSize.mBoundingMetrics);
+
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ // Measure
+ nsresult rv = MeasureForWidth(aRenderingContext->GetDrawTarget(), aDesiredSize);
+ if (NS_FAILED(rv)) {
+ ReflowError(aRenderingContext->GetDrawTarget(), aDesiredSize);
+ }
+
+ ClearSavedChildMetrics();
+}
+
+/* virtual */ nsresult
+nsMathMLContainerFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize)
+{
+ return Place(aDrawTarget, false, aDesiredSize);
+}
+
+
+// see spacing table in Chapter 18, TeXBook (p.170)
+// Our table isn't quite identical to TeX because operators have
+// built-in values for lspace & rspace in the Operator Dictionary.
+static int32_t kInterFrameSpacingTable[eMathMLFrameType_COUNT][eMathMLFrameType_COUNT] =
+{
+ // in units of muspace.
+ // upper half of the byte is set if the
+ // spacing is not to be used for scriptlevel > 0
+
+ /* Ord OpOrd OpInv OpUsr Inner Italic Upright */
+ /*Ord */ {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00},
+ /*OpOrd*/ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ /*OpInv*/ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ /*OpUsr*/ {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01},
+ /*Inner*/ {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01},
+ /*Italic*/ {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01},
+ /*Upright*/ {0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00}
+};
+
+#define GET_INTERSPACE(scriptlevel_, frametype1_, frametype2_, space_) \
+ /* no space if there is a frame that we know nothing about */ \
+ if (frametype1_ == eMathMLFrameType_UNKNOWN || \
+ frametype2_ == eMathMLFrameType_UNKNOWN) \
+ space_ = 0; \
+ else { \
+ space_ = kInterFrameSpacingTable[frametype1_][frametype2_]; \
+ space_ = (scriptlevel_ > 0 && (space_ & 0xF0)) \
+ ? 0 /* spacing is disabled */ \
+ : space_ & 0x0F; \
+ } \
+
+// This function computes the inter-space between two frames. However,
+// since invisible operators need special treatment, the inter-space may
+// be delayed when an invisible operator is encountered. In this case,
+// the function will carry the inter-space forward until it is determined
+// that it can be applied properly (i.e., until we encounter a visible
+// frame where to decide whether to accept or reject the inter-space).
+// aFromFrameType: remembers the frame when the carry-forward initiated.
+// aCarrySpace: keeps track of the inter-space that is delayed.
+// @returns: current inter-space (which is 0 when the true inter-space is
+// delayed -- and thus has no effect since the frame is invisible anyway).
+static nscoord
+GetInterFrameSpacing(int32_t aScriptLevel,
+ eMathMLFrameType aFirstFrameType,
+ eMathMLFrameType aSecondFrameType,
+ eMathMLFrameType* aFromFrameType, // IN/OUT
+ int32_t* aCarrySpace) // IN/OUT
+{
+ eMathMLFrameType firstType = aFirstFrameType;
+ eMathMLFrameType secondType = aSecondFrameType;
+
+ int32_t space;
+ GET_INTERSPACE(aScriptLevel, firstType, secondType, space);
+
+ // feedback control to avoid the inter-space to be added when not necessary
+ if (secondType == eMathMLFrameType_OperatorInvisible) {
+ // see if we should start to carry the space forward until we
+ // encounter a visible frame
+ if (*aFromFrameType == eMathMLFrameType_UNKNOWN) {
+ *aFromFrameType = firstType;
+ *aCarrySpace = space;
+ }
+ // keep carrying *aCarrySpace forward, while returning 0 for this stage
+ space = 0;
+ }
+ else if (*aFromFrameType != eMathMLFrameType_UNKNOWN) {
+ // no carry-forward anymore, get the real inter-space between
+ // the two frames of interest
+
+ firstType = *aFromFrameType;
+
+ // But... the invisible operator that we encountered earlier could
+ // be sitting between italic and upright identifiers, e.g.,
+ //
+ // 1. <mi>sin</mi> <mo>&ApplyFunction;</mo> <mi>x</mi>
+ // 2. <mi>x</mi> <mo>&InvisibileTime;</mo> <mi>sin</mi>
+ //
+ // the trick to get the inter-space in either situation
+ // is to promote "<mi>sin</mi><mo>&ApplyFunction;</mo>" and
+ // "<mo>&InvisibileTime;</mo><mi>sin</mi>" to user-defined operators...
+ if (firstType == eMathMLFrameType_UprightIdentifier) {
+ firstType = eMathMLFrameType_OperatorUserDefined;
+ }
+ else if (secondType == eMathMLFrameType_UprightIdentifier) {
+ secondType = eMathMLFrameType_OperatorUserDefined;
+ }
+
+ GET_INTERSPACE(aScriptLevel, firstType, secondType, space);
+
+ // Now, we have two values: the computed space and the space that
+ // has been carried forward until now. Which value do we pick?
+ // If the second type is an operator (e.g., fence), it already has
+ // built-in lspace & rspace, so we let them win. Otherwise we pick
+ // the max between the two values that we have.
+ if (secondType != eMathMLFrameType_OperatorOrdinary &&
+ space < *aCarrySpace)
+ space = *aCarrySpace;
+
+ // reset everything now that the carry-forward is done
+ *aFromFrameType = eMathMLFrameType_UNKNOWN;
+ *aCarrySpace = 0;
+ }
+
+ return space;
+}
+
+static nscoord GetThinSpace(const nsStyleFont* aStyleFont)
+{
+ return NSToCoordRound(float(aStyleFont->mFont.size)*float(3) / float(18));
+}
+
+class nsMathMLContainerFrame::RowChildFrameIterator {
+public:
+ explicit RowChildFrameIterator(nsMathMLContainerFrame* aParentFrame) :
+ mParentFrame(aParentFrame),
+ mReflowOutput(aParentFrame->GetWritingMode()),
+ mX(0),
+ mCarrySpace(0),
+ mFromFrameType(eMathMLFrameType_UNKNOWN),
+ mRTL(aParentFrame->StyleVisibility()->mDirection)
+ {
+ if (!mRTL) {
+ mChildFrame = aParentFrame->mFrames.FirstChild();
+ } else {
+ mChildFrame = aParentFrame->mFrames.LastChild();
+ }
+
+ if (!mChildFrame)
+ return;
+
+ InitMetricsForChild();
+ }
+
+ RowChildFrameIterator& operator++()
+ {
+ // add child size + italic correction
+ mX += mReflowOutput.mBoundingMetrics.width + mItalicCorrection;
+
+ if (!mRTL) {
+ mChildFrame = mChildFrame->GetNextSibling();
+ } else {
+ mChildFrame = mChildFrame->GetPrevSibling();
+ }
+
+ if (!mChildFrame)
+ return *this;
+
+ eMathMLFrameType prevFrameType = mChildFrameType;
+ InitMetricsForChild();
+
+ // add inter frame spacing
+ const nsStyleFont* font = mParentFrame->StyleFont();
+ nscoord space =
+ GetInterFrameSpacing(font->mScriptLevel,
+ prevFrameType, mChildFrameType,
+ &mFromFrameType, &mCarrySpace);
+ mX += space * GetThinSpace(font);
+ return *this;
+ }
+
+ nsIFrame* Frame() const { return mChildFrame; }
+ nscoord X() const { return mX; }
+ const ReflowOutput& GetReflowOutput() const { return mReflowOutput; }
+ nscoord Ascent() const { return mReflowOutput.BlockStartAscent(); }
+ nscoord Descent() const { return mReflowOutput.Height() - mReflowOutput.BlockStartAscent(); }
+ const nsBoundingMetrics& BoundingMetrics() const {
+ return mReflowOutput.mBoundingMetrics;
+ }
+
+private:
+ const nsMathMLContainerFrame* mParentFrame;
+ nsIFrame* mChildFrame;
+ ReflowOutput mReflowOutput;
+ nscoord mX;
+
+ nscoord mItalicCorrection;
+ eMathMLFrameType mChildFrameType;
+ int32_t mCarrySpace;
+ eMathMLFrameType mFromFrameType;
+
+ bool mRTL;
+
+ void InitMetricsForChild()
+ {
+ GetReflowAndBoundingMetricsFor(mChildFrame, mReflowOutput, mReflowOutput.mBoundingMetrics,
+ &mChildFrameType);
+ nscoord leftCorrection, rightCorrection;
+ GetItalicCorrection(mReflowOutput.mBoundingMetrics,
+ leftCorrection, rightCorrection);
+ if (!mChildFrame->GetPrevSibling() &&
+ mParentFrame->GetContent()->IsMathMLElement(nsGkAtoms::msqrt_)) {
+ // Remove leading correction in <msqrt> because the sqrt glyph itself is
+ // there first.
+ if (!mRTL) {
+ leftCorrection = 0;
+ } else {
+ rightCorrection = 0;
+ }
+ }
+ // add left correction -- this fixes the problem of the italic 'f'
+ // e.g., <mo>q</mo> <mi>f</mi> <mo>I</mo>
+ mX += leftCorrection;
+ mItalicCorrection = rightCorrection;
+ }
+};
+
+/* virtual */ nsresult
+nsMathMLContainerFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ // This is needed in case this frame is empty (i.e., no child frames)
+ mBoundingMetrics = nsBoundingMetrics();
+
+ RowChildFrameIterator child(this);
+ nscoord ascent = 0, descent = 0;
+ while (child.Frame()) {
+ if (descent < child.Descent())
+ descent = child.Descent();
+ if (ascent < child.Ascent())
+ ascent = child.Ascent();
+ // add the child size
+ mBoundingMetrics.width = child.X();
+ mBoundingMetrics += child.BoundingMetrics();
+ ++child;
+ }
+ // Add the italic correction at the end (including the last child).
+ // This gives a nice gap between math and non-math frames, and still
+ // gives the same math inter-spacing in case this frame connects to
+ // another math frame
+ mBoundingMetrics.width = child.X();
+
+ aDesiredSize.Width() = std::max(0, mBoundingMetrics.width);
+ aDesiredSize.Height() = ascent + descent;
+ aDesiredSize.SetBlockStartAscent(ascent);
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ //////////////////
+ // Place Children
+
+ if (aPlaceOrigin) {
+ PositionRowChildFrames(0, aDesiredSize.BlockStartAscent());
+ }
+
+ return NS_OK;
+}
+
+void
+nsMathMLContainerFrame::PositionRowChildFrames(nscoord aOffsetX,
+ nscoord aBaseline)
+{
+ RowChildFrameIterator child(this);
+ while (child.Frame()) {
+ nscoord dx = aOffsetX + child.X();
+ nscoord dy = aBaseline - child.Ascent();
+ FinishReflowChild(child.Frame(), PresContext(), child.GetReflowOutput(),
+ nullptr, dx, dy, 0);
+ ++child;
+ }
+}
+
+class ForceReflow : public nsIReflowCallback {
+public:
+ virtual bool ReflowFinished() override {
+ return true;
+ }
+ virtual void ReflowCallbackCanceled() override {}
+};
+
+// We only need one of these so we just make it a static global, no need
+// to dynamically allocate/destroy it.
+static ForceReflow gForceReflow;
+
+void
+nsMathMLContainerFrame::SetIncrementScriptLevel(int32_t aChildIndex, bool aIncrement)
+{
+ nsIFrame* child = PrincipalChildList().FrameAt(aChildIndex);
+ if (!child)
+ return;
+ nsIContent* content = child->GetContent();
+ if (!content->IsMathMLElement())
+ return;
+ nsMathMLElement* element = static_cast<nsMathMLElement*>(content);
+
+ if (element->GetIncrementScriptLevel() == aIncrement)
+ return;
+
+ // XXXroc this does a ContentStatesChanged, is it safe to call here? If
+ // not we should do it in a post-reflow callback.
+ element->SetIncrementScriptLevel(aIncrement, true);
+ PresContext()->PresShell()->PostReflowCallback(&gForceReflow);
+}
+
+// helpers to fix the inter-spacing when <math> is the only parent
+// e.g., it fixes <math> <mi>f</mi> <mo>q</mo> <mi>f</mi> <mo>I</mo> </math>
+
+static nscoord
+GetInterFrameSpacingFor(int32_t aScriptLevel,
+ nsIFrame* aParentFrame,
+ nsIFrame* aChildFrame)
+{
+ nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild();
+ if (!childFrame || aChildFrame == childFrame)
+ return 0;
+
+ int32_t carrySpace = 0;
+ eMathMLFrameType fromFrameType = eMathMLFrameType_UNKNOWN;
+ eMathMLFrameType prevFrameType = eMathMLFrameType_UNKNOWN;
+ eMathMLFrameType childFrameType = nsMathMLFrame::GetMathMLFrameTypeFor(childFrame);
+ childFrame = childFrame->GetNextSibling();
+ while (childFrame) {
+ prevFrameType = childFrameType;
+ childFrameType = nsMathMLFrame::GetMathMLFrameTypeFor(childFrame);
+ nscoord space = GetInterFrameSpacing(aScriptLevel,
+ prevFrameType, childFrameType, &fromFrameType, &carrySpace);
+ if (aChildFrame == childFrame) {
+ // get thinspace
+ nsStyleContext* parentContext = aParentFrame->StyleContext();
+ nscoord thinSpace = GetThinSpace(parentContext->StyleFont());
+ // we are done
+ return space * thinSpace;
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ NS_NOTREACHED("child not in the childlist of its parent");
+ return 0;
+}
+
+static nscoord
+AddInterFrameSpacingToSize(ReflowOutput& aDesiredSize,
+ nsMathMLContainerFrame* aFrame)
+{
+ nscoord gap = 0;
+ nsIFrame* parent = aFrame->GetParent();
+ nsIContent* parentContent = parent->GetContent();
+ if (MOZ_UNLIKELY(!parentContent)) {
+ return 0;
+ }
+ if (parentContent->IsAnyOfMathMLElements(nsGkAtoms::math,
+ nsGkAtoms::mtd_)) {
+ gap = GetInterFrameSpacingFor(aFrame->StyleFont()->mScriptLevel,
+ parent, aFrame);
+ // add our own italic correction
+ nscoord leftCorrection = 0, italicCorrection = 0;
+ aFrame->GetItalicCorrection(aDesiredSize.mBoundingMetrics,
+ leftCorrection, italicCorrection);
+ gap += leftCorrection;
+ if (gap) {
+ aDesiredSize.mBoundingMetrics.leftBearing += gap;
+ aDesiredSize.mBoundingMetrics.rightBearing += gap;
+ aDesiredSize.mBoundingMetrics.width += gap;
+ aDesiredSize.Width() += gap;
+ }
+ aDesiredSize.mBoundingMetrics.width += italicCorrection;
+ aDesiredSize.Width() += italicCorrection;
+ }
+ return gap;
+}
+
+nscoord
+nsMathMLContainerFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize)
+{
+ nscoord gap = 0;
+ gap = AddInterFrameSpacingToSize(aDesiredSize, this);
+ if (gap) {
+ // Shift our children to account for the correction
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ childFrame->SetPosition(childFrame->GetPosition() + nsPoint(gap, 0));
+ childFrame = childFrame->GetNextSibling();
+ }
+ }
+ return gap;
+}
+
+/* static */ void
+nsMathMLContainerFrame::DidReflowChildren(nsIFrame* aFirst, nsIFrame* aStop)
+
+{
+ if (MOZ_UNLIKELY(!aFirst))
+ return;
+
+ for (nsIFrame* frame = aFirst;
+ frame != aStop;
+ frame = frame->GetNextSibling()) {
+ NS_ASSERTION(frame, "aStop isn't a sibling");
+ if (frame->GetStateBits() & NS_FRAME_IN_REFLOW) {
+ // finish off principal descendants, too
+ nsIFrame* grandchild = frame->PrincipalChildList().FirstChild();
+ if (grandchild)
+ DidReflowChildren(grandchild, nullptr);
+
+ frame->DidReflow(frame->PresContext(), nullptr,
+ nsDidReflowStatus::FINISHED);
+ }
+ }
+}
+
+// helper used by mstyle, mphantom, mpadded and mrow in their implementations
+// of TransmitAutomaticData().
+nsresult
+nsMathMLContainerFrame::TransmitAutomaticDataForMrowLikeElement()
+{
+ //
+ // One loop to check both conditions below:
+ //
+ // 1) whether all the children of the mrow-like element are space-like.
+ //
+ // The REC defines the following elements to be "space-like":
+ // * an mstyle, mphantom, or mpadded element, all of whose direct
+ // sub-expressions are space-like;
+ // * an mrow all of whose direct sub-expressions are space-like.
+ //
+ // 2) whether all but one child of the mrow-like element are space-like and
+ // this non-space-like child is an embellished operator.
+ //
+ // The REC defines the following elements to be embellished operators:
+ // * one of the elements mstyle, mphantom, or mpadded, such that an mrow
+ // containing the same arguments would be an embellished operator;
+ // * an mrow whose arguments consist (in any order) of one embellished
+ // operator and zero or more space-like elements.
+ //
+ nsIFrame *childFrame, *baseFrame;
+ bool embellishedOpFound = false;
+ nsEmbellishData embellishData;
+
+ for (childFrame = PrincipalChildList().FirstChild();
+ childFrame;
+ childFrame = childFrame->GetNextSibling()) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame);
+ if (!mathMLFrame) break;
+ if (!mathMLFrame->IsSpaceLike()) {
+ if (embellishedOpFound) break;
+ baseFrame = childFrame;
+ GetEmbellishDataFrom(baseFrame, embellishData);
+ if (!NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags)) break;
+ embellishedOpFound = true;
+ }
+ }
+
+ if (!childFrame) {
+ // we successfully went to the end of the loop. This means that one of
+ // condition 1) or 2) holds.
+ if (!embellishedOpFound) {
+ // the mrow-like element is space-like.
+ mPresentationData.flags |= NS_MATHML_SPACE_LIKE;
+ } else {
+ // the mrow-like element is an embellished operator.
+ // let the state of the embellished operator found bubble to us.
+ mPresentationData.baseFrame = baseFrame;
+ mEmbellishData = embellishData;
+ }
+ }
+
+ if (childFrame || !embellishedOpFound) {
+ // The element is not embellished operator
+ mPresentationData.baseFrame = nullptr;
+ mEmbellishData.flags = 0;
+ mEmbellishData.coreFrame = nullptr;
+ mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ mEmbellishData.leadingSpace = 0;
+ mEmbellishData.trailingSpace = 0;
+ }
+
+ if (childFrame || embellishedOpFound) {
+ // The element is not space-like
+ mPresentationData.flags &= ~NS_MATHML_SPACE_LIKE;
+ }
+
+ return NS_OK;
+}
+
+/*static*/ void
+nsMathMLContainerFrame::PropagateFrameFlagFor(nsIFrame* aFrame,
+ nsFrameState aFlags)
+{
+ if (!aFrame || !aFlags)
+ return;
+
+ aFrame->AddStateBits(aFlags);
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ PropagateFrameFlagFor(childFrame, aFlags);
+ }
+}
+
+nsresult
+nsMathMLContainerFrame::ReportErrorToConsole(const char* errorMsgId,
+ const char16_t** aParams,
+ uint32_t aParamCount)
+{
+ return nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("Layout: MathML"), mContent->OwnerDoc(),
+ nsContentUtils::eMATHML_PROPERTIES,
+ errorMsgId, aParams, aParamCount);
+}
+
+nsresult
+nsMathMLContainerFrame::ReportParseError(const char16_t* aAttribute,
+ const char16_t* aValue)
+{
+ const char16_t* argv[] =
+ { aValue, aAttribute, mContent->NodeInfo()->NameAtom()->GetUTF16String() };
+ return ReportErrorToConsole("AttributeParsingError", argv, 3);
+}
+
+nsresult
+nsMathMLContainerFrame::ReportChildCountError()
+{
+ const char16_t* arg = mContent->NodeInfo()->NameAtom()->GetUTF16String();
+ return ReportErrorToConsole("ChildCountIncorrect", &arg, 1);
+}
+
+nsresult
+nsMathMLContainerFrame::ReportInvalidChildError(nsIAtom* aChildTag)
+{
+ const char16_t* argv[] =
+ { aChildTag->GetUTF16String(),
+ mContent->NodeInfo()->NameAtom()->GetUTF16String() };
+ return ReportErrorToConsole("InvalidChild", argv, 2);
+}
+
+//==========================
+
+nsContainerFrame*
+NS_NewMathMLmathBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmathBlockFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathBlockFrame)
+
+NS_QUERYFRAME_HEAD(nsMathMLmathBlockFrame)
+ NS_QUERYFRAME_ENTRY(nsMathMLmathBlockFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
+
+nsContainerFrame*
+NS_NewMathMLmathInlineFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmathInlineFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathInlineFrame)
+
+NS_QUERYFRAME_HEAD(nsMathMLmathInlineFrame)
+ NS_QUERYFRAME_ENTRY(nsIMathMLFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)
diff --git a/layout/mathml/nsMathMLContainerFrame.h b/layout/mathml/nsMathMLContainerFrame.h
new file mode 100644
index 000000000..94ccf70d2
--- /dev/null
+++ b/layout/mathml/nsMathMLContainerFrame.h
@@ -0,0 +1,565 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLContainerFrame_h___
+#define nsMathMLContainerFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsBlockFrame.h"
+#include "nsInlineFrame.h"
+#include "nsMathMLOperators.h"
+#include "nsMathMLFrame.h"
+#include "mozilla/Likely.h"
+
+/*
+ * Base class for MathML container frames. It acts like an inferred
+ * mrow. By default, this frame uses its Reflow() method to lay its
+ * children horizontally and ensure that their baselines are aligned.
+ * The Reflow() method relies upon Place() to position children.
+ * By overloading Place() in derived classes, it is therefore possible
+ * to position children in various customized ways.
+ */
+
+// Options for the preferred size at which to stretch our stretchy children
+#define STRETCH_CONSIDER_ACTUAL_SIZE 0x00000001 // just use our current size
+#define STRETCH_CONSIDER_EMBELLISHMENTS 0x00000002 // size calculations include embellishments
+
+class nsMathMLContainerFrame : public nsContainerFrame,
+ public nsMathMLFrame {
+ friend class nsMathMLmfencedFrame;
+public:
+ explicit nsMathMLContainerFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ , mIntrinsicWidth(NS_INTRINSIC_WIDTH_UNKNOWN)
+ , mBlockStartAscent(0)
+ {}
+
+ NS_DECL_QUERYFRAME_TARGET(nsMathMLContainerFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_ABSTRACT_FRAME(nsMathMLContainerFrame)
+
+ // --------------------------------------------------------------------------
+ // Overloaded nsMathMLFrame methods -- see documentation in nsIMathMLFrame.h
+
+ NS_IMETHOD
+ Stretch(DrawTarget* aDrawTarget,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aContainerSize,
+ ReflowOutput& aDesiredStretchSize) override;
+
+ NS_IMETHOD
+ UpdatePresentationDataFromChildAt(int32_t aFirstIndex,
+ int32_t aLastIndex,
+ uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate) override
+ {
+ PropagatePresentationDataFromChildAt(this, aFirstIndex, aLastIndex,
+ aFlagsValues, aFlagsToUpdate);
+ return NS_OK;
+ }
+
+ // helper to set the "increment script level" flag on the element belonging
+ // to a child frame given by aChildIndex.
+ // When this flag is set, the style system will increment the scriptlevel
+ // for the child element. This is needed for situations where the style system
+ // cannot itself determine the scriptlevel (mfrac, munder, mover, munderover).
+ // This should be called during reflow. We set the flag and if it changed,
+ // we request appropriate restyling and also queue a post-reflow callback
+ // to ensure that restyle and reflow happens immediately after the current
+ // reflow.
+ void
+ SetIncrementScriptLevel(int32_t aChildIndex, bool aIncrement);
+
+ // --------------------------------------------------------------------------
+ // Overloaded nsContainerFrame methods -- see documentation in nsIFrame.h
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return !(aFlags & nsIFrame::eLineParticipant) &&
+ nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eMathML | nsIFrame::eExcludesIgnorableWhitespace));
+ }
+
+ virtual void
+ AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+
+ virtual void
+ InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+
+ virtual void
+ RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ /**
+ * Both GetMinISize and GetPrefISize use the intrinsic width metrics
+ * returned by GetIntrinsicMetrics, including ink overflow.
+ */
+ virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
+
+ /**
+ * Return the intrinsic horizontal metrics of the frame's content area.
+ */
+ virtual void
+ GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext,
+ ReflowOutput& aDesiredSize);
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput,
+ nsDidReflowStatus aStatus) override
+
+ {
+ mPresentationData.flags &= ~NS_MATHML_STRETCH_DONE;
+ return nsContainerFrame::DidReflow(aPresContext, aReflowInput, aStatus);
+ }
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override;
+
+ virtual void MarkIntrinsicISizesDirty() override;
+
+ // Notification when an attribute is changed. The MathML module uses the
+ // following paradigm:
+ //
+ // 1. If the MathML frame class doesn't have any cached automatic data that
+ // depends on the attribute: we just reflow (e.g., this happens with <msub>,
+ // <msup>, <mmultiscripts>, etc). This is the default behavior implemented
+ // by this base class.
+ //
+ // 2. If the MathML frame class has cached automatic data that depends on
+ // the attribute:
+ // 2a. If the automatic data to update resides only within the descendants,
+ // we just re-layout them using ReLayoutChildren(this);
+ // (e.g., this happens with <ms>).
+ // 2b. If the automatic data to update affects us in some way, we ask our parent
+ // to re-layout its children using ReLayoutChildren(mParent);
+ // Therefore, there is an overhead here in that our siblings are re-laid
+ // too (e.g., this happens with <munder>, <mover>, <munderover>).
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ // helper function to apply mirroring to a horizontal coordinate, if needed.
+ nscoord
+ MirrorIfRTL(nscoord aParentWidth, nscoord aChildWidth, nscoord aChildLeading)
+ {
+ return (StyleVisibility()->mDirection ?
+ aParentWidth - aChildWidth - aChildLeading : aChildLeading);
+ }
+
+ // --------------------------------------------------------------------------
+ // Additional methods
+
+protected:
+ /* Place :
+ * This method is used to measure or position child frames and other
+ * elements. It may be called any number of times with aPlaceOrigin
+ * false to measure, and the final call of the Reflow process before
+ * returning from Reflow() or Stretch() will have aPlaceOrigin true
+ * to position the elements.
+ *
+ * IMPORTANT: This method uses GetReflowAndBoundingMetricsFor() which must
+ * have been set up with SaveReflowAndBoundingMetricsFor().
+ *
+ * The Place() method will use this information to compute the desired size
+ * of the frame.
+ *
+ * @param aPlaceOrigin [in]
+ * If aPlaceOrigin is false, compute your desired size using the
+ * information from GetReflowAndBoundingMetricsFor. However, child
+ * frames or other elements should not be repositioned.
+ *
+ * If aPlaceOrigin is true, reflow is finished. You should position
+ * all your children, and return your desired size. You should now
+ * use FinishReflowChild() on your children to complete post-reflow
+ * operations.
+ *
+ * @param aDesiredSize [out] parameter where you should return your desired
+ * size and your ascent/descent info. Compute your desired size using
+ * the information from GetReflowAndBoundingMetricsFor, and include
+ * any space you want for border/padding in the desired size you
+ * return.
+ */
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize);
+
+ // MeasureForWidth:
+ //
+ // A method used by nsMathMLContainerFrame::GetIntrinsicISize to get the
+ // width that a particular Place method desires. For most frames, this will
+ // just call the object's Place method. However <msqrt> and <menclose> use
+ // nsMathMLContainerFrame::GetIntrinsicISize to measure the child frames as
+ // if in an <mrow>, and so their frames implement MeasureForWidth to use
+ // nsMathMLContainerFrame::Place.
+ virtual nsresult
+ MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize);
+
+
+ // helper to re-sync the automatic data in our children and notify our parent to
+ // reflow us when changes (e.g., append/insert/remove) happen in our child list
+ virtual nsresult
+ ChildListChanged(int32_t aModType);
+
+ // helper to get the preferred size that a container frame should use to fire
+ // the stretch on its stretchy child frames.
+ void
+ GetPreferredStretchSize(DrawTarget* aDrawTarget,
+ uint32_t aOptions,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aPreferredStretchSize);
+
+ // helper used by mstyle, mphantom, mpadded and mrow in their implementation
+ // of TransmitAutomaticData() to determine whether they are space-like.
+ nsresult
+ TransmitAutomaticDataForMrowLikeElement();
+
+public:
+ // error handlers to provide a visual feedback to the user when an error
+ // (typically invalid markup) was encountered during reflow.
+ nsresult
+ ReflowError(DrawTarget* aDrawTarget, ReflowOutput& aDesiredSize);
+ /*
+ * Helper to call ReportErrorToConsole for parse errors involving
+ * attribute/value pairs.
+ * @param aAttribute The attribute for which the parse error occured.
+ * @param aValue The value for which the parse error occured.
+ */
+ nsresult
+ ReportParseError(const char16_t* aAttribute,
+ const char16_t* aValue);
+
+ /*
+ * Helper to call ReportErrorToConsole when certain tags
+ * have more than the expected amount of children.
+ */
+ nsresult
+ ReportChildCountError();
+
+ /*
+ * Helper to call ReportErrorToConsole when certain tags have
+ * invalid child tags
+ * @param aChildTag The tag which is forbidden in this context
+ */
+ nsresult
+ ReportInvalidChildError(nsIAtom* aChildTag);
+
+ /*
+ * Helper to call ReportToConsole when an error occurs.
+ * @param aParams see nsContentUtils::ReportToConsole
+ */
+ nsresult
+ ReportErrorToConsole(const char* aErrorMsgId,
+ const char16_t** aParams = nullptr,
+ uint32_t aParamCount = 0);
+
+ // helper method to reflow a child frame. We are inline frames, and we don't
+ // know our positions until reflow is finished. That's why we ask the
+ // base method not to worry about our position.
+ void
+ ReflowChild(nsIFrame* aKidFrame,
+ nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus);
+
+protected:
+ // helper to add the inter-spacing when <math> is the immediate parent.
+ // Since we don't (yet) handle the root <math> element ourselves, we need to
+ // take special care of the inter-frame spacing on elements for which <math>
+ // is the direct xml parent. This function will be repeatedly called from
+ // left to right on the childframes of <math>, and by so doing it will
+ // emulate the spacing that would have been done by a <mrow> container.
+ // e.g., it fixes <math> <mi>f</mi> <mo>q</mo> <mi>f</mi> <mo>I</mo> </math>
+ virtual nscoord
+ FixInterFrameSpacing(ReflowOutput& aDesiredSize);
+
+ // helper method to complete the post-reflow hook and ensure that embellished
+ // operators don't terminate their Reflow without receiving a Stretch command.
+ virtual nsresult
+ FinalizeReflow(DrawTarget* aDrawTarget, ReflowOutput& aDesiredSize);
+
+ // Record metrics of a child frame for recovery through the following method
+ static void
+ SaveReflowAndBoundingMetricsFor(nsIFrame* aFrame,
+ const ReflowOutput& aReflowOutput,
+ const nsBoundingMetrics& aBoundingMetrics);
+
+ // helper method to facilitate getting the reflow and bounding metrics of a
+ // child frame. The argument aMathMLFrameType, when non null, will return
+ // the 'type' of the frame, which is used to determine the inter-frame
+ // spacing.
+ // IMPORTANT: This function is only meant to be called in Place() methods as
+ // the information is available only when set up with the above method
+ // during Reflow/Stretch() and GetPrefISize().
+ static void
+ GetReflowAndBoundingMetricsFor(nsIFrame* aFrame,
+ ReflowOutput& aReflowOutput,
+ nsBoundingMetrics& aBoundingMetrics,
+ eMathMLFrameType* aMathMLFrameType = nullptr);
+
+ // helper method to clear metrics saved with
+ // SaveReflowAndBoundingMetricsFor() from all child frames.
+ void ClearSavedChildMetrics();
+
+ // helper to let the update of presentation data pass through
+ // a subtree that may contain non-MathML container frames
+ static void
+ PropagatePresentationDataFor(nsIFrame* aFrame,
+ uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate);
+
+public:
+ static void
+ PropagatePresentationDataFromChildAt(nsIFrame* aParentFrame,
+ int32_t aFirstChildIndex,
+ int32_t aLastChildIndex,
+ uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate);
+
+ // Sets flags on aFrame and all descendant frames
+ static void
+ PropagateFrameFlagFor(nsIFrame* aFrame,
+ nsFrameState aFlags);
+
+ // helper to let the rebuild of automatic data (presentation data
+ // and embellishement data) walk through a subtree that may contain
+ // non-MathML container frames. Note that this method re-builds the
+ // automatic data in the children -- not in aParentFrame itself (except
+ // for those particular operations that the parent frame may do in its
+ // TransmitAutomaticData()). The reason it works this way is because
+ // a container frame knows what it wants for its children, whereas children
+ // have no clue who their parent is. For example, it is <mfrac> who knows
+ // that its children have to be in scriptsizes, and has to transmit this
+ // information to them. Hence, when changes occur in a child frame, the child
+ // has to request the re-build from its parent. Unfortunately, the extra cost
+ // for this is that it will re-sync in the siblings of the child as well.
+ static void
+ RebuildAutomaticDataForChildren(nsIFrame* aParentFrame);
+
+ // helper to blow away the automatic data cached in a frame's subtree and
+ // re-layout its subtree to reflect changes that may have happen. In the
+ // event where aParentFrame isn't a MathML frame, it will first walk up to
+ // the ancestor that is a MathML frame, and re-layout from there -- this is
+ // to guarantee that automatic data will be rebuilt properly. Note that this
+ // method re-builds the automatic data in the children -- not in the parent
+ // frame itself (except for those particular operations that the parent frame
+ // may do do its TransmitAutomaticData()). @see RebuildAutomaticDataForChildren
+ //
+ // aBits are the bits to pass to FrameNeedsReflow() when we call it.
+ static nsresult
+ ReLayoutChildren(nsIFrame* aParentFrame);
+
+protected:
+ // Helper method which positions child frames as an <mrow> on given baseline
+ // y = aBaseline starting from x = aOffsetX, calling FinishReflowChild()
+ // on the frames.
+ void
+ PositionRowChildFrames(nscoord aOffsetX, nscoord aBaseline);
+
+ // A variant on FinishAndStoreOverflow() that uses the union of child
+ // overflows, the frame bounds, and mBoundingMetrics to set and store the
+ // overflow.
+ void GatherAndStoreOverflow(ReflowOutput* aMetrics);
+
+ /**
+ * Call DidReflow() if the NS_FRAME_IN_REFLOW frame bit is set on aFirst and
+ * all its next siblings up to, but not including, aStop.
+ * aStop == nullptr meaning all next siblings with the bit set.
+ * The method does nothing if aFirst == nullptr.
+ */
+ static void DidReflowChildren(nsIFrame* aFirst, nsIFrame* aStop = nullptr);
+
+ /**
+ * Recompute mIntrinsicWidth if it's not already up to date.
+ */
+ void UpdateIntrinsicWidth(nsRenderingContext* aRenderingContext);
+
+ nscoord mIntrinsicWidth;
+
+ nscoord mBlockStartAscent;
+
+private:
+ class RowChildFrameIterator;
+ friend class RowChildFrameIterator;
+};
+
+
+// --------------------------------------------------------------------------
+// Currently, to benefit from line-breaking inside the <math> element, <math> is
+// simply mapping to nsBlockFrame or nsInlineFrame.
+// A separate implemention needs to provide:
+// 1) line-breaking
+// 2) proper inter-frame spacing
+// 3) firing of Stretch() (in which case FinalizeReflow() would have to be cleaned)
+// Issues: If/when mathml becomes a pluggable component, the separation will be needed.
+class nsMathMLmathBlockFrame : public nsBlockFrame {
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsMathMLmathBlockFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsContainerFrame* NS_NewMathMLmathBlockFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ // beware, mFrames is not set by nsBlockFrame
+ // cannot use mFrames{.FirstChild()|.etc} since the block code doesn't set mFrames
+ virtual void
+ SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override
+ {
+ MOZ_ASSERT(aListID == kPrincipalList || aListID == kBackdropList,
+ "unexpected frame list");
+ nsBlockFrame::SetInitialChildList(aListID, aChildList);
+ if (aListID == kPrincipalList) {
+ // re-resolve our subtree to set any mathml-expected data
+ nsMathMLContainerFrame::RebuildAutomaticDataForChildren(this);
+ }
+ }
+
+ virtual void
+ AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override
+ {
+ NS_ASSERTION(aListID == kPrincipalList || aListID == kNoReflowPrincipalList,
+ "unexpected frame list");
+ nsBlockFrame::AppendFrames(aListID, aFrameList);
+ if (MOZ_LIKELY(aListID == kPrincipalList))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual void
+ InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override
+ {
+ NS_ASSERTION(aListID == kPrincipalList || aListID == kNoReflowPrincipalList,
+ "unexpected frame list");
+ nsBlockFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
+ if (MOZ_LIKELY(aListID == kPrincipalList))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual void
+ RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override
+ {
+ NS_ASSERTION(aListID == kPrincipalList || aListID == kNoReflowPrincipalList,
+ "unexpected frame list");
+ nsBlockFrame::RemoveFrame(aListID, aOldFrame);
+ if (MOZ_LIKELY(aListID == kPrincipalList))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override {
+ return nsBlockFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eMathML | nsIFrame::eExcludesIgnorableWhitespace));
+ }
+
+ // See nsIMathMLFrame.h
+ bool IsMrowLike() {
+ return mFrames.FirstChild() != mFrames.LastChild() ||
+ !mFrames.FirstChild();
+ }
+
+protected:
+ explicit nsMathMLmathBlockFrame(nsStyleContext* aContext) : nsBlockFrame(aContext) {
+ // We should always have a float manager. Not that things can really try
+ // to float out of us anyway, but we need one for line layout.
+ // Bug 1301881: Do we still need to set NS_BLOCK_FLOAT_MGR?
+ // AddStateBits(NS_BLOCK_FLOAT_MGR);
+ }
+ virtual ~nsMathMLmathBlockFrame() {}
+};
+
+// --------------
+
+class nsMathMLmathInlineFrame : public nsInlineFrame,
+ public nsMathMLFrame {
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsMathMLmathInlineFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsContainerFrame* NS_NewMathMLmathInlineFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ virtual void
+ SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override
+ {
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected frame list");
+ nsInlineFrame::SetInitialChildList(aListID, aChildList);
+ // re-resolve our subtree to set any mathml-expected data
+ nsMathMLContainerFrame::RebuildAutomaticDataForChildren(this);
+ }
+
+ virtual void
+ AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override
+ {
+ NS_ASSERTION(aListID == kPrincipalList || aListID == kNoReflowPrincipalList,
+ "unexpected frame list");
+ nsInlineFrame::AppendFrames(aListID, aFrameList);
+ if (MOZ_LIKELY(aListID == kPrincipalList))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual void
+ InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override
+ {
+ NS_ASSERTION(aListID == kPrincipalList || aListID == kNoReflowPrincipalList,
+ "unexpected frame list");
+ nsInlineFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
+ if (MOZ_LIKELY(aListID == kPrincipalList))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual void
+ RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override
+ {
+ NS_ASSERTION(aListID == kPrincipalList || aListID == kNoReflowPrincipalList,
+ "unexpected frame list");
+ nsInlineFrame::RemoveFrame(aListID, aOldFrame);
+ if (MOZ_LIKELY(aListID == kPrincipalList))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override {
+ return nsInlineFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eMathML | nsIFrame::eExcludesIgnorableWhitespace));
+ }
+
+ bool
+ IsMrowLike() override {
+ return mFrames.FirstChild() != mFrames.LastChild() ||
+ !mFrames.FirstChild();
+ }
+
+protected:
+ explicit nsMathMLmathInlineFrame(nsStyleContext* aContext) : nsInlineFrame(aContext) {}
+ virtual ~nsMathMLmathInlineFrame() {}
+};
+
+#endif /* nsMathMLContainerFrame_h___ */
diff --git a/layout/mathml/nsMathMLFrame.cpp b/layout/mathml/nsMathMLFrame.cpp
new file mode 100644
index 000000000..acb69e387
--- /dev/null
+++ b/layout/mathml/nsMathMLFrame.cpp
@@ -0,0 +1,427 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLFrame.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "nsLayoutUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsMathMLChar.h"
+#include "nsCSSPseudoElements.h"
+#include "nsMathMLElement.h"
+#include "gfxMathTable.h"
+
+// used to map attributes into CSS rules
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsDisplayList.h"
+#include "nsRenderingContext.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+eMathMLFrameType
+nsMathMLFrame::GetMathMLFrameType()
+{
+ // see if it is an embellished operator (mapped to 'Op' in TeX)
+ if (mEmbellishData.coreFrame)
+ return GetMathMLFrameTypeFor(mEmbellishData.coreFrame);
+
+ // if it has a prescribed base, fetch the type from there
+ if (mPresentationData.baseFrame)
+ return GetMathMLFrameTypeFor(mPresentationData.baseFrame);
+
+ // everything else is treated as ordinary (mapped to 'Ord' in TeX)
+ return eMathMLFrameType_Ordinary;
+}
+
+NS_IMETHODIMP
+nsMathMLFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ mEmbellishData.flags = 0;
+ mEmbellishData.coreFrame = nullptr;
+ mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ mEmbellishData.leadingSpace = 0;
+ mEmbellishData.trailingSpace = 0;
+
+ mPresentationData.flags = 0;
+ mPresentationData.baseFrame = nullptr;
+
+ // by default, just inherit the display of our parent
+ nsPresentationData parentData;
+ GetPresentationDataFrom(aParent, parentData);
+
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS;
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMathMLFrame::UpdatePresentationData(uint32_t aFlagsValues,
+ uint32_t aWhichFlags)
+{
+ NS_ASSERTION(NS_MATHML_IS_COMPRESSED(aWhichFlags) ||
+ NS_MATHML_IS_DTLS_SET(aWhichFlags),
+ "aWhichFlags should only be compression or dtls flag");
+
+ if (NS_MATHML_IS_COMPRESSED(aWhichFlags)) {
+ // updating the compression flag is allowed
+ if (NS_MATHML_IS_COMPRESSED(aFlagsValues)) {
+ // 'compressed' means 'prime' style in App. G, TeXbook
+ mPresentationData.flags |= NS_MATHML_COMPRESSED;
+ }
+ // no else. the flag is sticky. it retains its value once it is set
+ }
+ // These flags determine whether the dtls font feature settings should
+ // be applied.
+ if (NS_MATHML_IS_DTLS_SET(aWhichFlags)) {
+ if (NS_MATHML_IS_DTLS_SET(aFlagsValues)) {
+ mPresentationData.flags |= NS_MATHML_DTLS;
+ } else {
+ mPresentationData.flags &= ~NS_MATHML_DTLS;
+ }
+ }
+ return NS_OK;
+}
+
+// Helper to give a style context suitable for doing the stretching of
+// a MathMLChar. Frame classes that use this should ensure that the
+// extra leaf style contexts given to the MathMLChars are accessible to
+// the Style System via the Get/Set AdditionalStyleContext() APIs.
+/* static */ void
+nsMathMLFrame::ResolveMathMLCharStyle(nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleContext* aParentStyleContext,
+ nsMathMLChar* aMathMLChar)
+{
+ CSSPseudoElementType pseudoType =
+ CSSPseudoElementType::mozMathAnonymous; // savings
+ RefPtr<nsStyleContext> newStyleContext;
+ newStyleContext = aPresContext->StyleSet()->
+ ResolvePseudoElementStyle(aContent->AsElement(), pseudoType,
+ aParentStyleContext, nullptr);
+
+ aMathMLChar->SetStyleContext(newStyleContext);
+}
+
+/* static */ void
+nsMathMLFrame::GetEmbellishDataFrom(nsIFrame* aFrame,
+ nsEmbellishData& aEmbellishData)
+{
+ // initialize OUT params
+ aEmbellishData.flags = 0;
+ aEmbellishData.coreFrame = nullptr;
+ aEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ aEmbellishData.leadingSpace = 0;
+ aEmbellishData.trailingSpace = 0;
+
+ if (aFrame && aFrame->IsFrameOfType(nsIFrame::eMathML)) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
+ if (mathMLFrame) {
+ mathMLFrame->GetEmbellishData(aEmbellishData);
+ }
+ }
+}
+
+// helper to get the presentation data of a frame, by possibly walking up
+// the frame hierarchy if we happen to be surrounded by non-MathML frames.
+/* static */ void
+nsMathMLFrame::GetPresentationDataFrom(nsIFrame* aFrame,
+ nsPresentationData& aPresentationData,
+ bool aClimbTree)
+{
+ // initialize OUT params
+ aPresentationData.flags = 0;
+ aPresentationData.baseFrame = nullptr;
+
+ nsIFrame* frame = aFrame;
+ while (frame) {
+ if (frame->IsFrameOfType(nsIFrame::eMathML)) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame);
+ if (mathMLFrame) {
+ mathMLFrame->GetPresentationData(aPresentationData);
+ break;
+ }
+ }
+ // stop if the caller doesn't want to lookup beyond the frame
+ if (!aClimbTree) {
+ break;
+ }
+ // stop if we reach the root <math> tag
+ nsIContent* content = frame->GetContent();
+ NS_ASSERTION(content || !frame->GetParent(), // no assert for the root
+ "dangling frame without a content node");
+ if (!content)
+ break;
+
+ if (content->IsMathMLElement(nsGkAtoms::math)) {
+ break;
+ }
+ frame = frame->GetParent();
+ }
+ NS_WARNING_ASSERTION(
+ frame && frame->GetContent(),
+ "bad MathML markup - could not find the top <math> element");
+}
+
+/* static */ void
+nsMathMLFrame::GetRuleThickness(DrawTarget* aDrawTarget,
+ nsFontMetrics* aFontMetrics,
+ nscoord& aRuleThickness)
+{
+ nscoord xHeight = aFontMetrics->XHeight();
+ char16_t overBar = 0x00AF;
+ nsBoundingMetrics bm =
+ nsLayoutUtils::AppUnitBoundsOfString(&overBar, 1, *aFontMetrics,
+ aDrawTarget);
+ aRuleThickness = bm.ascent + bm.descent;
+ if (aRuleThickness <= 0 || aRuleThickness >= xHeight) {
+ // fall-back to the other version
+ GetRuleThickness(aFontMetrics, aRuleThickness);
+ }
+}
+
+/* static */ void
+nsMathMLFrame::GetAxisHeight(DrawTarget* aDrawTarget,
+ nsFontMetrics* aFontMetrics,
+ nscoord& aAxisHeight)
+{
+ gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
+ if (mathFont) {
+ aAxisHeight =
+ mathFont->MathTable()->Constant(gfxMathTable::AxisHeight,
+ aFontMetrics->AppUnitsPerDevPixel());
+ return;
+ }
+
+ nscoord xHeight = aFontMetrics->XHeight();
+ char16_t minus = 0x2212; // not '-', but official Unicode minus sign
+ nsBoundingMetrics bm =
+ nsLayoutUtils::AppUnitBoundsOfString(&minus, 1, *aFontMetrics, aDrawTarget);
+ aAxisHeight = bm.ascent - (bm.ascent + bm.descent)/2;
+ if (aAxisHeight <= 0 || aAxisHeight >= xHeight) {
+ // fall-back to the other version
+ GetAxisHeight(aFontMetrics, aAxisHeight);
+ }
+}
+
+/* static */ nscoord
+nsMathMLFrame::CalcLength(nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ const nsCSSValue& aCSSValue,
+ float aFontSizeInflation)
+{
+ NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit");
+
+ if (aCSSValue.IsFixedLengthUnit()) {
+ return aCSSValue.GetFixedLength(aPresContext);
+ }
+ if (aCSSValue.IsPixelLengthUnit()) {
+ return aCSSValue.GetPixelLength();
+ }
+
+ nsCSSUnit unit = aCSSValue.GetUnit();
+
+ if (eCSSUnit_EM == unit) {
+ const nsStyleFont* font = aStyleContext->StyleFont();
+ return NSToCoordRound(aCSSValue.GetFloatValue() * (float)font->mFont.size);
+ }
+ else if (eCSSUnit_XHeight == unit) {
+ aPresContext->SetUsesExChUnits(true);
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::
+ GetFontMetricsForStyleContext(aStyleContext, aFontSizeInflation);
+ nscoord xHeight = fm->XHeight();
+ return NSToCoordRound(aCSSValue.GetFloatValue() * (float)xHeight);
+ }
+
+ // MathML doesn't specify other CSS units such as rem or ch
+ NS_ERROR("Unsupported unit");
+ return 0;
+}
+
+/* static */ void
+nsMathMLFrame::ParseNumericValue(const nsString& aString,
+ nscoord* aLengthValue,
+ uint32_t aFlags,
+ nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ float aFontSizeInflation)
+{
+ nsCSSValue cssValue;
+
+ if (!nsMathMLElement::ParseNumericValue(aString, cssValue, aFlags,
+ aPresContext->Document())) {
+ // Invalid attribute value. aLengthValue remains unchanged, so the default
+ // length value is used.
+ return;
+ }
+
+ nsCSSUnit unit = cssValue.GetUnit();
+
+ if (unit == eCSSUnit_Percent || unit == eCSSUnit_Number) {
+ // Relative units. A multiple of the default length value is used.
+ *aLengthValue = NSToCoordRound(*aLengthValue * (unit == eCSSUnit_Percent ?
+ cssValue.GetPercentValue() :
+ cssValue.GetFloatValue()));
+ return;
+ }
+
+ // Absolute units.
+ *aLengthValue = CalcLength(aPresContext, aStyleContext, cssValue,
+ aFontSizeInflation);
+}
+
+// ================
+// Utils to map attributes into CSS rules (work-around to bug 69409 which
+// is not scheduled to be fixed anytime soon)
+//
+
+struct
+nsCSSMapping {
+ int32_t compatibility;
+ const nsIAtom* attrAtom;
+ const char* cssProperty;
+};
+
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+class nsDisplayMathMLBoundingMetrics : public nsDisplayItem {
+public:
+ nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect)
+ : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLBoundingMetrics);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayMathMLBoundingMetrics() {
+ MOZ_COUNT_DTOR(nsDisplayMathMLBoundingMetrics);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLBoundingMetrics", TYPE_MATHML_BOUNDING_METRICS)
+private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLBoundingMetrics::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ Rect r = NSRectToRect(mRect + ToReferenceFrame(),
+ mFrame->PresContext()->AppUnitsPerDevPixel());
+ ColorPattern blue(ToDeviceColor(Color(0.f, 0.f, 1.f, 1.f)));
+ drawTarget->StrokeRect(r, blue);
+}
+
+void
+nsMathMLFrame::DisplayBoundingMetrics(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsPoint& aPt,
+ const nsBoundingMetrics& aMetrics,
+ const nsDisplayListSet& aLists) {
+ if (!NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags))
+ return;
+
+ nscoord x = aPt.x + aMetrics.leftBearing;
+ nscoord y = aPt.y - aMetrics.ascent;
+ nscoord w = aMetrics.rightBearing - aMetrics.leftBearing;
+ nscoord h = aMetrics.ascent + aMetrics.descent;
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayMathMLBoundingMetrics(aBuilder, aFrame, nsRect(x,y,w,h)));
+}
+#endif
+
+class nsDisplayMathMLBar : public nsDisplayItem {
+public:
+ nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect)
+ : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLBar);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayMathMLBar() {
+ MOZ_COUNT_DTOR(nsDisplayMathMLBar);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR)
+private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLBar::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ // paint the bar with the current text color
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ Rect rect =
+ NSRectToNonEmptySnappedRect(mRect + ToReferenceFrame(),
+ mFrame->PresContext()->AppUnitsPerDevPixel(),
+ *drawTarget);
+ ColorPattern color(ToDeviceColor(
+ mFrame->GetVisitedDependentColor(eCSSProperty__webkit_text_fill_color)));
+ drawTarget->FillRect(rect, color);
+}
+
+void
+nsMathMLFrame::DisplayBar(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect,
+ const nsDisplayListSet& aLists) {
+ if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty())
+ return;
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayMathMLBar(aBuilder, aFrame, aRect));
+}
+
+void
+nsMathMLFrame::GetRadicalParameters(nsFontMetrics* aFontMetrics,
+ bool aDisplayStyle,
+ nscoord& aRadicalRuleThickness,
+ nscoord& aRadicalExtraAscender,
+ nscoord& aRadicalVerticalGap)
+{
+ nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel();
+ gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
+
+ // get the radical rulethickness
+ if (mathFont) {
+ aRadicalRuleThickness = mathFont->MathTable()->
+ Constant(gfxMathTable::RadicalRuleThickness, oneDevPixel);
+ } else {
+ GetRuleThickness(aFontMetrics, aRadicalRuleThickness);
+ }
+
+ // get the leading to be left at the top of the resulting frame
+ if (mathFont) {
+ aRadicalExtraAscender = mathFont->MathTable()->
+ Constant(gfxMathTable::RadicalExtraAscender, oneDevPixel);
+ } else {
+ // This seems more reliable than using aFontMetrics->GetLeading() on
+ // suspicious fonts.
+ nscoord em;
+ GetEmHeight(aFontMetrics, em);
+ aRadicalExtraAscender = nscoord(0.2f * em);
+ }
+
+ // get the clearance between rule and content
+ if (mathFont) {
+ aRadicalVerticalGap = mathFont->MathTable()->
+ Constant(aDisplayStyle ?
+ gfxMathTable::RadicalDisplayStyleVerticalGap :
+ gfxMathTable::RadicalVerticalGap,
+ oneDevPixel);
+ } else {
+ // Rule 11, App. G, TeXbook
+ aRadicalVerticalGap = aRadicalRuleThickness +
+ (aDisplayStyle ? aFontMetrics->XHeight() : aRadicalRuleThickness) / 4;
+ }
+}
diff --git a/layout/mathml/nsMathMLFrame.h b/layout/mathml/nsMathMLFrame.h
new file mode 100644
index 000000000..1df3e3a78
--- /dev/null
+++ b/layout/mathml/nsMathMLFrame.h
@@ -0,0 +1,381 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLFrame_h___
+#define nsMathMLFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsFontMetrics.h"
+#include "nsMathMLOperators.h"
+#include "nsIMathMLFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsBoundingMetrics.h"
+#include "nsIFrame.h"
+
+class nsMathMLChar;
+class nsCSSValue;
+class nsDisplayListSet;
+
+// Concrete base class with default methods that derived MathML frames can override
+class nsMathMLFrame : public nsIMathMLFrame {
+public:
+ // nsIMathMLFrame ---
+
+ virtual bool
+ IsSpaceLike() override {
+ return NS_MATHML_IS_SPACE_LIKE(mPresentationData.flags);
+ }
+
+ NS_IMETHOD
+ GetBoundingMetrics(nsBoundingMetrics& aBoundingMetrics) override {
+ aBoundingMetrics = mBoundingMetrics;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ SetBoundingMetrics(const nsBoundingMetrics& aBoundingMetrics) override {
+ mBoundingMetrics = aBoundingMetrics;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ SetReference(const nsPoint& aReference) override {
+ mReference = aReference;
+ return NS_OK;
+ }
+
+ virtual eMathMLFrameType GetMathMLFrameType() override;
+
+ NS_IMETHOD
+ Stretch(mozilla::gfx::DrawTarget* aDrawTarget,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aContainerSize,
+ mozilla::ReflowOutput& aDesiredStretchSize) override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetEmbellishData(nsEmbellishData& aEmbellishData) override {
+ aEmbellishData = mEmbellishData;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetPresentationData(nsPresentationData& aPresentationData) override {
+ aPresentationData = mPresentationData;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ UpdatePresentationData(uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate) override;
+
+ NS_IMETHOD
+ UpdatePresentationDataFromChildAt(int32_t aFirstIndex,
+ int32_t aLastIndex,
+ uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate) override
+ {
+ return NS_OK;
+ }
+
+ uint8_t
+ ScriptIncrement(nsIFrame* aFrame) override
+ {
+ return 0;
+ }
+
+ bool
+ IsMrowLike() override
+ {
+ return false;
+ }
+
+ // helper to give a style context suitable for doing the stretching to the
+ // MathMLChar. Frame classes that use this should make the extra style contexts
+ // accessible to the Style System via Get/Set AdditionalStyleContext.
+ static void
+ ResolveMathMLCharStyle(nsPresContext* aPresContext,
+ nsIContent* aContent,
+ nsStyleContext* aParenStyleContext,
+ nsMathMLChar* aMathMLChar);
+
+ // helper to get the mEmbellishData of a frame
+ // The MathML REC precisely defines an "embellished operator" as:
+ // - an <mo> element;
+ // - or one of the elements <msub>, <msup>, <msubsup>, <munder>, <mover>,
+ // <munderover>, <mmultiscripts>, <mfrac>, or <semantics>, whose first
+ // argument exists and is an embellished operator;
+ //- or one of the elements <mstyle>, <mphantom>, or <mpadded>, such that
+ // an <mrow> containing the same arguments would be an embellished
+ // operator;
+ // - or an <maction> element whose selected subexpression exists and is an
+ // embellished operator;
+ // - or an <mrow> whose arguments consist (in any order) of one embellished
+ // operator and zero or more spacelike elements.
+ static void
+ GetEmbellishDataFrom(nsIFrame* aFrame,
+ nsEmbellishData& aEmbellishData);
+
+ // helper to get the presentation data of a frame. If aClimbTree is
+ // set to true and the frame happens to be surrounded by non-MathML
+ // helper frames needed for its support, we walk up the frame hierarchy
+ // until we reach a MathML ancestor or the <root> math element.
+ static void
+ GetPresentationDataFrom(nsIFrame* aFrame,
+ nsPresentationData& aPresentationData,
+ bool aClimbTree = true);
+
+ // utilities to parse and retrieve numeric values in CSS units
+ // All values are stored in twips.
+ // @pre aLengthValue is the default length value of the attribute.
+ // @post aLengthValue is the length value computed from the attribute.
+ static void ParseNumericValue(const nsString& aString,
+ nscoord* aLengthValue,
+ uint32_t aFlags,
+ nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ float aFontSizeInflation);
+
+ static nscoord
+ CalcLength(nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ const nsCSSValue& aCSSValue,
+ float aFontSizeInflation);
+
+ static eMathMLFrameType
+ GetMathMLFrameTypeFor(nsIFrame* aFrame)
+ {
+ if (aFrame->IsFrameOfType(nsIFrame::eMathML)) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
+ if (mathMLFrame)
+ return mathMLFrame->GetMathMLFrameType();
+ }
+ return eMathMLFrameType_UNKNOWN;
+ }
+
+ // estimate of the italic correction
+ static void
+ GetItalicCorrection(nsBoundingMetrics& aBoundingMetrics,
+ nscoord& aItalicCorrection)
+ {
+ aItalicCorrection = aBoundingMetrics.rightBearing - aBoundingMetrics.width;
+ if (0 > aItalicCorrection) {
+ aItalicCorrection = 0;
+ }
+ }
+
+ static void
+ GetItalicCorrection(nsBoundingMetrics& aBoundingMetrics,
+ nscoord& aLeftItalicCorrection,
+ nscoord& aRightItalicCorrection)
+ {
+ aRightItalicCorrection = aBoundingMetrics.rightBearing - aBoundingMetrics.width;
+ if (0 > aRightItalicCorrection) {
+ aRightItalicCorrection = 0;
+ }
+ aLeftItalicCorrection = -aBoundingMetrics.leftBearing;
+ if (0 > aLeftItalicCorrection) {
+ aLeftItalicCorrection = 0;
+ }
+ }
+
+ // helper methods for getting sup/subdrop's from a child
+ static void
+ GetSubDropFromChild(nsIFrame* aChild,
+ nscoord& aSubDrop,
+ float aFontSizeInflation)
+ {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation);
+ GetSubDrop(fm, aSubDrop);
+ }
+
+ static void
+ GetSupDropFromChild(nsIFrame* aChild,
+ nscoord& aSupDrop,
+ float aFontSizeInflation)
+ {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation);
+ GetSupDrop(fm, aSupDrop);
+ }
+
+ static void
+ GetSkewCorrectionFromChild(nsIFrame* aChild,
+ nscoord& aSkewCorrection)
+ {
+ // default is 0
+ // individual classes should over-ride this method if necessary
+ aSkewCorrection = 0;
+ }
+
+ // 2 levels of subscript shifts
+ static void
+ GetSubScriptShifts(nsFontMetrics* fm,
+ nscoord& aSubScriptShift1,
+ nscoord& aSubScriptShift2)
+ {
+ nscoord xHeight = fm->XHeight();
+ aSubScriptShift1 = NSToCoordRound(150.000f/430.556f * xHeight);
+ aSubScriptShift2 = NSToCoordRound(247.217f/430.556f * xHeight);
+ }
+
+ // 3 levels of superscript shifts
+ static void
+ GetSupScriptShifts(nsFontMetrics* fm,
+ nscoord& aSupScriptShift1,
+ nscoord& aSupScriptShift2,
+ nscoord& aSupScriptShift3)
+ {
+ nscoord xHeight = fm->XHeight();
+ aSupScriptShift1 = NSToCoordRound(412.892f/430.556f * xHeight);
+ aSupScriptShift2 = NSToCoordRound(362.892f/430.556f * xHeight);
+ aSupScriptShift3 = NSToCoordRound(288.889f/430.556f * xHeight);
+ }
+
+ // these are TeX specific params not found in ordinary fonts
+
+ static void
+ GetSubDrop(nsFontMetrics* fm,
+ nscoord& aSubDrop)
+ {
+ nscoord xHeight = fm->XHeight();
+ aSubDrop = NSToCoordRound(50.000f/430.556f * xHeight);
+ }
+
+ static void
+ GetSupDrop(nsFontMetrics* fm,
+ nscoord& aSupDrop)
+ {
+ nscoord xHeight = fm->XHeight();
+ aSupDrop = NSToCoordRound(386.108f/430.556f * xHeight);
+ }
+
+ static void
+ GetNumeratorShifts(nsFontMetrics* fm,
+ nscoord& numShift1,
+ nscoord& numShift2,
+ nscoord& numShift3)
+ {
+ nscoord xHeight = fm->XHeight();
+ numShift1 = NSToCoordRound(676.508f/430.556f * xHeight);
+ numShift2 = NSToCoordRound(393.732f/430.556f * xHeight);
+ numShift3 = NSToCoordRound(443.731f/430.556f * xHeight);
+ }
+
+ static void
+ GetDenominatorShifts(nsFontMetrics* fm,
+ nscoord& denShift1,
+ nscoord& denShift2)
+ {
+ nscoord xHeight = fm->XHeight();
+ denShift1 = NSToCoordRound(685.951f/430.556f * xHeight);
+ denShift2 = NSToCoordRound(344.841f/430.556f * xHeight);
+ }
+
+ static void
+ GetEmHeight(nsFontMetrics* fm,
+ nscoord& emHeight)
+ {
+#if 0
+ // should switch to this API in order to scale with changes of TextZoom
+ emHeight = fm->EmHeight();
+#else
+ emHeight = NSToCoordRound(float(fm->Font().size));
+#endif
+ }
+
+ static void
+ GetAxisHeight (nsFontMetrics* fm,
+ nscoord& axisHeight)
+ {
+ axisHeight = NSToCoordRound(250.000f/430.556f * fm->XHeight());
+ }
+
+ static void
+ GetBigOpSpacings(nsFontMetrics* fm,
+ nscoord& bigOpSpacing1,
+ nscoord& bigOpSpacing2,
+ nscoord& bigOpSpacing3,
+ nscoord& bigOpSpacing4,
+ nscoord& bigOpSpacing5)
+ {
+ nscoord xHeight = fm->XHeight();
+ bigOpSpacing1 = NSToCoordRound(111.111f/430.556f * xHeight);
+ bigOpSpacing2 = NSToCoordRound(166.667f/430.556f * xHeight);
+ bigOpSpacing3 = NSToCoordRound(200.000f/430.556f * xHeight);
+ bigOpSpacing4 = NSToCoordRound(600.000f/430.556f * xHeight);
+ bigOpSpacing5 = NSToCoordRound(100.000f/430.556f * xHeight);
+ }
+
+ static void
+ GetRuleThickness(nsFontMetrics* fm,
+ nscoord& ruleThickness)
+ {
+ nscoord xHeight = fm->XHeight();
+ ruleThickness = NSToCoordRound(40.000f/430.556f * xHeight);
+ }
+
+ // Some parameters are not accurately obtained using the x-height.
+ // Here are some slower variants to obtain the desired metrics
+ // by actually measuring some characters
+ static void
+ GetRuleThickness(mozilla::gfx::DrawTarget* aDrawTarget,
+ nsFontMetrics* aFontMetrics,
+ nscoord& aRuleThickness);
+
+ static void
+ GetAxisHeight(mozilla::gfx::DrawTarget* aDrawTarget,
+ nsFontMetrics* aFontMetrics,
+ nscoord& aAxisHeight);
+
+ static void
+ GetRadicalParameters(nsFontMetrics* aFontMetrics,
+ bool aDisplayStyle,
+ nscoord& aRadicalRuleThickness,
+ nscoord& aRadicalExtraAscender,
+ nscoord& aRadicalVerticalGap);
+
+protected:
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ void DisplayBoundingMetrics(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsPoint& aPt,
+ const nsBoundingMetrics& aMetrics,
+ const nsDisplayListSet& aLists);
+#endif
+
+ /**
+ * Display a solid rectangle in the frame's text color. Used for drawing
+ * fraction separators and root/sqrt overbars.
+ */
+ void DisplayBar(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect,
+ const nsDisplayListSet& aLists);
+
+ // information about the presentation policy of the frame
+ nsPresentationData mPresentationData;
+
+ // information about a container that is an embellished operator
+ nsEmbellishData mEmbellishData;
+
+ // Metrics that _exactly_ enclose the text of the frame
+ nsBoundingMetrics mBoundingMetrics;
+
+ // Reference point of the frame: mReference.y is the baseline
+ nsPoint mReference;
+};
+
+#endif /* nsMathMLFrame_h___ */
diff --git a/layout/mathml/nsMathMLOperators.cpp b/layout/mathml/nsMathMLOperators.cpp
new file mode 100644
index 000000000..bbb03014b
--- /dev/null
+++ b/layout/mathml/nsMathMLOperators.cpp
@@ -0,0 +1,467 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLOperators.h"
+#include "nsCOMPtr.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+
+#include "nsIPersistentProperties2.h"
+#include "nsISimpleEnumerator.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+
+// operator dictionary entry
+struct OperatorData {
+ OperatorData(void)
+ : mFlags(0),
+ mLeadingSpace(0.0f),
+ mTrailingSpace(0.0f)
+ {
+ }
+
+ // member data
+ nsString mStr;
+ nsOperatorFlags mFlags;
+ float mLeadingSpace; // unit is em
+ float mTrailingSpace; // unit is em
+};
+
+static int32_t gTableRefCount = 0;
+static uint32_t gOperatorCount = 0;
+static OperatorData* gOperatorArray = nullptr;
+static nsDataHashtable<nsStringHashKey, OperatorData*>* gOperatorTable = nullptr;
+static bool gGlobalsInitialized = false;
+
+static const char16_t kDashCh = char16_t('#');
+static const char16_t kColonCh = char16_t(':');
+
+static void
+SetBooleanProperty(OperatorData* aOperatorData,
+ nsString aName)
+{
+ if (aName.IsEmpty())
+ return;
+
+ if (aName.EqualsLiteral("stretchy") && (1 == aOperatorData->mStr.Length()))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY;
+ else if (aName.EqualsLiteral("fence"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE;
+ else if (aName.EqualsLiteral("accent"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT;
+ else if (aName.EqualsLiteral("largeop"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP;
+ else if (aName.EqualsLiteral("separator"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_SEPARATOR;
+ else if (aName.EqualsLiteral("movablelimits"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_MOVABLELIMITS;
+ else if (aName.EqualsLiteral("symmetric"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_SYMMETRIC;
+ else if (aName.EqualsLiteral("integral"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_INTEGRAL;
+ else if (aName.EqualsLiteral("mirrorable"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_MIRRORABLE;
+}
+
+static void
+SetProperty(OperatorData* aOperatorData,
+ nsString aName,
+ nsString aValue)
+{
+ if (aName.IsEmpty() || aValue.IsEmpty())
+ return;
+
+ // XXX These ones are not kept in the dictionary
+ // Support for these requires nsString member variables
+ // maxsize (default: infinity)
+ // minsize (default: 1)
+
+ if (aName.EqualsLiteral("direction")) {
+ if (aValue.EqualsLiteral("vertical"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_VERTICAL;
+ else if (aValue.EqualsLiteral("horizontal"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL;
+ else return; // invalid value
+ } else {
+ bool isLeadingSpace;
+ if (aName.EqualsLiteral("lspace"))
+ isLeadingSpace = true;
+ else if (aName.EqualsLiteral("rspace"))
+ isLeadingSpace = false;
+ else return; // input is not applicable
+
+ // aValue is assumed to be a digit from 0 to 7
+ nsresult error = NS_OK;
+ float space = aValue.ToFloat(&error) / 18.0;
+ if (NS_FAILED(error)) return;
+
+ if (isLeadingSpace)
+ aOperatorData->mLeadingSpace = space;
+ else
+ aOperatorData->mTrailingSpace = space;
+ }
+}
+
+static bool
+SetOperator(OperatorData* aOperatorData,
+ nsOperatorFlags aForm,
+ const nsCString& aOperator,
+ nsString& aAttributes)
+
+{
+ static const char16_t kNullCh = char16_t('\0');
+
+ // aOperator is in the expanded format \uNNNN\uNNNN ...
+ // First compress these Unicode points to the internal nsString format
+ int32_t i = 0;
+ nsAutoString name, value;
+ int32_t len = aOperator.Length();
+ char16_t c = aOperator[i++];
+ uint32_t state = 0;
+ char16_t uchar = 0;
+ while (i <= len) {
+ if (0 == state) {
+ if (c != '\\')
+ return false;
+ if (i < len)
+ c = aOperator[i];
+ i++;
+ if (('u' != c) && ('U' != c))
+ return false;
+ if (i < len)
+ c = aOperator[i];
+ i++;
+ state++;
+ }
+ else {
+ if (('0' <= c) && (c <= '9'))
+ uchar = (uchar << 4) | (c - '0');
+ else if (('a' <= c) && (c <= 'f'))
+ uchar = (uchar << 4) | (c - 'a' + 0x0a);
+ else if (('A' <= c) && (c <= 'F'))
+ uchar = (uchar << 4) | (c - 'A' + 0x0a);
+ else return false;
+ if (i < len)
+ c = aOperator[i];
+ i++;
+ state++;
+ if (5 == state) {
+ value.Append(uchar);
+ uchar = 0;
+ state = 0;
+ }
+ }
+ }
+ if (0 != state) return false;
+
+ // Quick return when the caller doesn't care about the attributes and just wants
+ // to know if this is a valid operator (this is the case at the first pass of the
+ // parsing of the dictionary in InitOperators())
+ if (!aForm) return true;
+
+ // Add operator to hash table
+ aOperatorData->mFlags |= aForm;
+ aOperatorData->mStr.Assign(value);
+ value.AppendInt(aForm, 10);
+ gOperatorTable->Put(value, aOperatorData);
+
+#ifdef DEBUG
+ NS_LossyConvertUTF16toASCII str(aAttributes);
+#endif
+ // Loop over the space-delimited list of attributes to get the name:value pairs
+ aAttributes.Append(kNullCh); // put an extra null at the end
+ char16_t* start = aAttributes.BeginWriting();
+ char16_t* end = start;
+ while ((kNullCh != *start) && (kDashCh != *start)) {
+ name.SetLength(0);
+ value.SetLength(0);
+ // skip leading space, the dash amounts to the end of the line
+ while ((kNullCh!=*start) && (kDashCh!=*start) && nsCRT::IsAsciiSpace(*start)) {
+ ++start;
+ }
+ end = start;
+ // look for ':'
+ while ((kNullCh!=*end) && (kDashCh!=*end) && !nsCRT::IsAsciiSpace(*end) &&
+ (kColonCh!=*end)) {
+ ++end;
+ }
+ // If ':' is not found, then it's a boolean property
+ bool IsBooleanProperty = (kColonCh != *end);
+ *end = kNullCh; // end segment here
+ // this segment is the name
+ if (start < end) {
+ name.Assign(start);
+ }
+ if (IsBooleanProperty) {
+ SetBooleanProperty(aOperatorData, name);
+ } else {
+ start = ++end;
+ // look for space or end of line
+ while ((kNullCh!=*end) && (kDashCh!=*end) &&
+ !nsCRT::IsAsciiSpace(*end)) {
+ ++end;
+ }
+ *end = kNullCh; // end segment here
+ if (start < end) {
+ // this segment is the value
+ value.Assign(start);
+ }
+ SetProperty(aOperatorData, name, value);
+ }
+ start = ++end;
+ }
+ return true;
+}
+
+static nsresult
+InitOperators(void)
+{
+ // Load the property file containing the Operator Dictionary
+ nsresult rv;
+ nsCOMPtr<nsIPersistentProperties> mathfontProp;
+ rv = NS_LoadPersistentPropertiesFromURISpec(
+ getter_AddRefs(mathfontProp),
+ NS_LITERAL_CSTRING("resource://gre/res/fonts/mathfont.properties"));
+
+ if (NS_FAILED(rv)) return rv;
+
+ // Parse the Operator Dictionary in two passes.
+ // The first pass is to count the number of operators; the second pass is to
+ // allocate the necessary space for them and to add them in the hash table.
+ for (int32_t pass = 1; pass <= 2; pass++) {
+ OperatorData dummyData;
+ OperatorData* operatorData = &dummyData;
+ nsCOMPtr<nsISimpleEnumerator> iterator;
+ if (NS_SUCCEEDED(mathfontProp->Enumerate(getter_AddRefs(iterator)))) {
+ bool more;
+ uint32_t index = 0;
+ nsAutoCString name;
+ nsAutoString attributes;
+ while ((NS_SUCCEEDED(iterator->HasMoreElements(&more))) && more) {
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIPropertyElement> element;
+ if (NS_SUCCEEDED(iterator->GetNext(getter_AddRefs(supports)))) {
+ element = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(element->GetKey(name)) &&
+ NS_SUCCEEDED(element->GetValue(attributes))) {
+ // expected key: operator.\uNNNN.{infix,postfix,prefix}
+ if ((21 <= name.Length()) && (0 == name.Find("operator.\\u"))) {
+ name.Cut(0, 9); // 9 is the length of "operator.";
+ int32_t len = name.Length();
+ nsOperatorFlags form = 0;
+ if (kNotFound != name.RFind(".infix")) {
+ form = NS_MATHML_OPERATOR_FORM_INFIX;
+ len -= 6; // 6 is the length of ".infix";
+ }
+ else if (kNotFound != name.RFind(".postfix")) {
+ form = NS_MATHML_OPERATOR_FORM_POSTFIX;
+ len -= 8; // 8 is the length of ".postfix";
+ }
+ else if (kNotFound != name.RFind(".prefix")) {
+ form = NS_MATHML_OPERATOR_FORM_PREFIX;
+ len -= 7; // 7 is the length of ".prefix";
+ }
+ else continue; // input is not applicable
+ name.SetLength(len);
+ if (2 == pass) { // allocate space and start the storage
+ if (!gOperatorArray) {
+ if (0 == gOperatorCount) return NS_ERROR_UNEXPECTED;
+ gOperatorArray = new OperatorData[gOperatorCount];
+ if (!gOperatorArray) return NS_ERROR_OUT_OF_MEMORY;
+ }
+ operatorData = &gOperatorArray[index];
+ }
+ else {
+ form = 0; // to quickly return from SetOperator() at pass 1
+ }
+ // See if the operator should be retained
+ if (SetOperator(operatorData, form, name, attributes)) {
+ index++;
+ if (1 == pass) gOperatorCount = index;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+static nsresult
+InitOperatorGlobals()
+{
+ gGlobalsInitialized = true;
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ gOperatorTable = new nsDataHashtable<nsStringHashKey, OperatorData*>();
+ if (gOperatorTable) {
+ rv = InitOperators();
+ }
+ if (NS_FAILED(rv))
+ nsMathMLOperators::CleanUp();
+ return rv;
+}
+
+void
+nsMathMLOperators::CleanUp()
+{
+ if (gOperatorArray) {
+ delete[] gOperatorArray;
+ gOperatorArray = nullptr;
+ }
+ if (gOperatorTable) {
+ delete gOperatorTable;
+ gOperatorTable = nullptr;
+ }
+}
+
+void
+nsMathMLOperators::AddRefTable(void)
+{
+ gTableRefCount++;
+}
+
+void
+nsMathMLOperators::ReleaseTable(void)
+{
+ if (0 == --gTableRefCount) {
+ CleanUp();
+ }
+}
+
+static OperatorData*
+GetOperatorData(const nsString& aOperator, nsOperatorFlags aForm)
+{
+ nsAutoString key(aOperator);
+ key.AppendInt(aForm);
+ return gOperatorTable->Get(key);
+}
+
+bool
+nsMathMLOperators::LookupOperator(const nsString& aOperator,
+ const nsOperatorFlags aForm,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace)
+{
+ if (!gGlobalsInitialized) {
+ InitOperatorGlobals();
+ }
+ if (gOperatorTable) {
+ NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage");
+ NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***");
+
+ // The MathML REC says:
+ // If the operator does not occur in the dictionary with the specified form,
+ // the renderer should use one of the forms which is available there, in the
+ // order of preference: infix, postfix, prefix.
+
+ OperatorData* found;
+ int32_t form = NS_MATHML_OPERATOR_GET_FORM(aForm);
+ if (!(found = GetOperatorData(aOperator, form))) {
+ if (form == NS_MATHML_OPERATOR_FORM_INFIX ||
+ !(found =
+ GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX))) {
+ if (form == NS_MATHML_OPERATOR_FORM_POSTFIX ||
+ !(found =
+ GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX))) {
+ if (form != NS_MATHML_OPERATOR_FORM_PREFIX) {
+ found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
+ }
+ }
+ }
+ }
+ if (found) {
+ NS_ASSERTION(found->mStr.Equals(aOperator), "bad setup");
+ *aLeadingSpace = found->mLeadingSpace;
+ *aTrailingSpace = found->mTrailingSpace;
+ *aFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the form bits
+ *aFlags |= found->mFlags; // just add bits without overwriting
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsMathMLOperators::LookupOperators(const nsString& aOperator,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace)
+{
+ if (!gGlobalsInitialized) {
+ InitOperatorGlobals();
+ }
+
+ aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = 0;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
+
+ aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
+
+ aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = 0;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
+
+ if (gOperatorTable) {
+ OperatorData* found;
+ found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX);
+ if (found) {
+ aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = found->mFlags;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mLeadingSpace;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mTrailingSpace;
+ }
+ found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX);
+ if (found) {
+ aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mFlags;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mLeadingSpace;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mTrailingSpace;
+ }
+ found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
+ if (found) {
+ aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mFlags;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mLeadingSpace;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mTrailingSpace;
+ }
+ }
+}
+
+/* static */ bool
+nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator)
+{
+ // LookupOperator will search infix, postfix and prefix forms of aOperator and
+ // return the first form found. It is assumed that all these forms have same
+ // mirrorability.
+ nsOperatorFlags flags = 0;
+ float dummy;
+ nsMathMLOperators::LookupOperator(aOperator,
+ NS_MATHML_OPERATOR_FORM_INFIX,
+ &flags, &dummy, &dummy);
+ return NS_MATHML_OPERATOR_IS_MIRRORABLE(flags);
+}
+
+/* static */ nsStretchDirection
+nsMathMLOperators::GetStretchyDirection(const nsString& aOperator)
+{
+ // LookupOperator will search infix, postfix and prefix forms of aOperator and
+ // return the first form found. It is assumed that all these forms have same
+ // direction.
+ nsOperatorFlags flags = 0;
+ float dummy;
+ nsMathMLOperators::LookupOperator(aOperator,
+ NS_MATHML_OPERATOR_FORM_INFIX,
+ &flags, &dummy, &dummy);
+
+ if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) {
+ return NS_STRETCH_DIRECTION_VERTICAL;
+ } else if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) {
+ return NS_STRETCH_DIRECTION_HORIZONTAL;
+ } else {
+ return NS_STRETCH_DIRECTION_UNSUPPORTED;
+ }
+}
diff --git a/layout/mathml/nsMathMLOperators.h b/layout/mathml/nsMathMLOperators.h
new file mode 100644
index 000000000..d850c4b9d
--- /dev/null
+++ b/layout/mathml/nsMathMLOperators.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLOperators_h___
+#define nsMathMLOperators_h___
+
+#include <stdint.h>
+#include "nsStringFwd.h"
+
+enum nsStretchDirection {
+ NS_STRETCH_DIRECTION_UNSUPPORTED = -1,
+ NS_STRETCH_DIRECTION_DEFAULT = 0,
+ NS_STRETCH_DIRECTION_HORIZONTAL = 1,
+ NS_STRETCH_DIRECTION_VERTICAL = 2
+};
+
+typedef uint32_t nsOperatorFlags;
+enum {
+ // define the bits used to handle the operator
+ NS_MATHML_OPERATOR_MUTABLE = 1<<30,
+ NS_MATHML_OPERATOR_EMBELLISH_ANCESTOR = 1<<29,
+ NS_MATHML_OPERATOR_EMBELLISH_ISOLATED = 1<<28,
+ NS_MATHML_OPERATOR_CENTERED = 1<<27,
+ NS_MATHML_OPERATOR_INVISIBLE = 1<<26,
+
+ // define the bits used in the Operator Dictionary
+
+ // the very last two bits tell us the form
+ NS_MATHML_OPERATOR_FORM = 0x3,
+ NS_MATHML_OPERATOR_FORM_INFIX = 1,
+ NS_MATHML_OPERATOR_FORM_PREFIX = 2,
+ NS_MATHML_OPERATOR_FORM_POSTFIX = 3,
+
+ // the next 2 bits tell us the direction
+ NS_MATHML_OPERATOR_DIRECTION = 0x3<<2,
+ NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL = 1<<2,
+ NS_MATHML_OPERATOR_DIRECTION_VERTICAL = 2<<2,
+
+ // other bits used in the Operator Dictionary
+ NS_MATHML_OPERATOR_STRETCHY = 1<<4,
+ NS_MATHML_OPERATOR_FENCE = 1<<5,
+ NS_MATHML_OPERATOR_ACCENT = 1<<6,
+ NS_MATHML_OPERATOR_LARGEOP = 1<<7,
+ NS_MATHML_OPERATOR_SEPARATOR = 1<<8,
+ NS_MATHML_OPERATOR_MOVABLELIMITS = 1<<9,
+ NS_MATHML_OPERATOR_SYMMETRIC = 1<<10,
+ NS_MATHML_OPERATOR_INTEGRAL = 1<<11,
+ NS_MATHML_OPERATOR_MIRRORABLE = 1<<12,
+
+ // Additional bits not stored in the dictionary
+ NS_MATHML_OPERATOR_MINSIZE_ABSOLUTE = 1<<13,
+ NS_MATHML_OPERATOR_MAXSIZE_ABSOLUTE = 1<<14,
+ NS_MATHML_OPERATOR_LSPACE_ATTR = 1<<15,
+ NS_MATHML_OPERATOR_RSPACE_ATTR = 1<<16
+};
+
+#define NS_MATHML_OPERATOR_SIZE_INFINITY NS_IEEEPositiveInfinity()
+
+class nsMathMLOperators {
+public:
+ static void AddRefTable(void);
+ static void ReleaseTable(void);
+ static void CleanUp();
+
+ // LookupOperator:
+ // Given the string value of an operator and its form (last two bits of flags),
+ // this method returns true if the operator is found in the Operator Dictionary.
+ // Attributes of the operator are returned in the output parameters.
+ // If the operator is not found under the supplied form but is found under a
+ // different form, the method returns true as well. The caller can test the
+ // output parameter aFlags to know exactly under which form the operator was
+ // found in the Operator Dictionary.
+ static bool
+ LookupOperator(const nsString& aOperator,
+ const nsOperatorFlags aForm,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace);
+
+ // LookupOperators:
+ // Helper to return all the forms under which an operator is listed in the
+ // Operator Dictionary. The caller must pass arrays of size 4, and use
+ // aFlags[NS_MATHML_OPERATOR_FORM_{INFIX|POSTFIX|PREFIX}],
+ // aLeadingSpace[], etc, to access the attributes of the operator under a
+ // particular form. If the operator wasn't found under a form, its entry
+ // aFlags[form] is set to zero.
+ static void
+ LookupOperators(const nsString& aOperator,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace);
+
+ // Helper functions used by the nsMathMLChar class.
+ static bool
+ IsMirrorableOperator(const nsString& aOperator);
+
+ // Helper function used by the nsMathMLChar class.
+ static nsStretchDirection GetStretchyDirection(const nsString& aOperator);
+};
+
+////////////////////////////////////////////////////////////////////////////
+// Macros that retrieve the bits used to handle operators
+
+#define NS_MATHML_OPERATOR_IS_MUTABLE(_flags) \
+ (NS_MATHML_OPERATOR_MUTABLE == ((_flags) & NS_MATHML_OPERATOR_MUTABLE))
+
+#define NS_MATHML_OPERATOR_HAS_EMBELLISH_ANCESTOR(_flags) \
+ (NS_MATHML_OPERATOR_EMBELLISH_ANCESTOR == ((_flags) & NS_MATHML_OPERATOR_EMBELLISH_ANCESTOR))
+
+#define NS_MATHML_OPERATOR_EMBELLISH_IS_ISOLATED(_flags) \
+ (NS_MATHML_OPERATOR_EMBELLISH_ISOLATED == ((_flags) & NS_MATHML_OPERATOR_EMBELLISH_ISOLATED))
+
+#define NS_MATHML_OPERATOR_IS_CENTERED(_flags) \
+ (NS_MATHML_OPERATOR_CENTERED == ((_flags) & NS_MATHML_OPERATOR_CENTERED))
+
+#define NS_MATHML_OPERATOR_IS_INVISIBLE(_flags) \
+ (NS_MATHML_OPERATOR_INVISIBLE == ((_flags) & NS_MATHML_OPERATOR_INVISIBLE))
+
+#define NS_MATHML_OPERATOR_GET_FORM(_flags) \
+ ((_flags) & NS_MATHML_OPERATOR_FORM)
+
+#define NS_MATHML_OPERATOR_GET_DIRECTION(_flags) \
+ ((_flags) & NS_MATHML_OPERATOR_DIRECTION)
+
+#define NS_MATHML_OPERATOR_FORM_IS_INFIX(_flags) \
+ (NS_MATHML_OPERATOR_FORM_INFIX == ((_flags) & NS_MATHML_OPERATOR_FORM))
+
+#define NS_MATHML_OPERATOR_FORM_IS_PREFIX(_flags) \
+ (NS_MATHML_OPERATOR_FORM_PREFIX == ((_flags) & NS_MATHML_OPERATOR_FORM))
+
+#define NS_MATHML_OPERATOR_FORM_IS_POSTFIX(_flags) \
+ (NS_MATHML_OPERATOR_FORM_POSTFIX == ((_flags) & NS_MATHML_OPERATOR_FORM))
+
+#define NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(_flags) \
+ (NS_MATHML_OPERATOR_DIRECTION_VERTICAL == ((_flags) & NS_MATHML_OPERATOR_DIRECTION))
+
+#define NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(_flags) \
+ (NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL == ((_flags) & NS_MATHML_OPERATOR_DIRECTION))
+
+#define NS_MATHML_OPERATOR_IS_STRETCHY(_flags) \
+ (NS_MATHML_OPERATOR_STRETCHY == ((_flags) & NS_MATHML_OPERATOR_STRETCHY))
+
+#define NS_MATHML_OPERATOR_IS_FENCE(_flags) \
+ (NS_MATHML_OPERATOR_FENCE == ((_flags) & NS_MATHML_OPERATOR_FENCE))
+
+#define NS_MATHML_OPERATOR_IS_ACCENT(_flags) \
+ (NS_MATHML_OPERATOR_ACCENT == ((_flags) & NS_MATHML_OPERATOR_ACCENT))
+
+#define NS_MATHML_OPERATOR_IS_LARGEOP(_flags) \
+ (NS_MATHML_OPERATOR_LARGEOP == ((_flags) & NS_MATHML_OPERATOR_LARGEOP))
+
+#define NS_MATHML_OPERATOR_IS_SEPARATOR(_flags) \
+ (NS_MATHML_OPERATOR_SEPARATOR == ((_flags) & NS_MATHML_OPERATOR_SEPARATOR))
+
+#define NS_MATHML_OPERATOR_IS_MOVABLELIMITS(_flags) \
+ (NS_MATHML_OPERATOR_MOVABLELIMITS == ((_flags) & NS_MATHML_OPERATOR_MOVABLELIMITS))
+
+#define NS_MATHML_OPERATOR_IS_SYMMETRIC(_flags) \
+ (NS_MATHML_OPERATOR_SYMMETRIC == ((_flags) & NS_MATHML_OPERATOR_SYMMETRIC))
+
+#define NS_MATHML_OPERATOR_IS_INTEGRAL(_flags) \
+ (NS_MATHML_OPERATOR_INTEGRAL == ((_flags) & NS_MATHML_OPERATOR_INTEGRAL))
+
+#define NS_MATHML_OPERATOR_IS_MIRRORABLE(_flags) \
+ (NS_MATHML_OPERATOR_MIRRORABLE == ((_flags) & NS_MATHML_OPERATOR_MIRRORABLE))
+
+#define NS_MATHML_OPERATOR_MINSIZE_IS_ABSOLUTE(_flags) \
+ (NS_MATHML_OPERATOR_MINSIZE_ABSOLUTE == ((_flags) & NS_MATHML_OPERATOR_MINSIZE_ABSOLUTE))
+
+#define NS_MATHML_OPERATOR_MAXSIZE_IS_ABSOLUTE(_flags) \
+ (NS_MATHML_OPERATOR_MAXSIZE_ABSOLUTE == ((_flags) & NS_MATHML_OPERATOR_MAXSIZE_ABSOLUTE))
+
+#define NS_MATHML_OPERATOR_HAS_LSPACE_ATTR(_flags) \
+ (NS_MATHML_OPERATOR_LSPACE_ATTR == ((_flags) & NS_MATHML_OPERATOR_LSPACE_ATTR))
+
+#define NS_MATHML_OPERATOR_HAS_RSPACE_ATTR(_flags) \
+ (NS_MATHML_OPERATOR_RSPACE_ATTR == ((_flags) & NS_MATHML_OPERATOR_RSPACE_ATTR))
+
+#endif /* nsMathMLOperators_h___ */
diff --git a/layout/mathml/nsMathMLParts.h b/layout/mathml/nsMathMLParts.h
new file mode 100644
index 000000000..5d4285ee9
--- /dev/null
+++ b/layout/mathml/nsMathMLParts.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLParts_h___
+#define nsMathMLParts_h___
+
+#include "nscore.h"
+#include "nsISupports.h"
+
+class nsTableFrame;
+
+// Factory methods for creating MathML objects
+nsIFrame* NS_NewMathMLTokenFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmoFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmrowFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmspaceFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmfencedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmfracFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmsubFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmsupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmsubsupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmunderoverFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmmultiscriptsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsContainerFrame* NS_NewMathMLmtableOuterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsContainerFrame* NS_NewMathMLmtableFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsContainerFrame* NS_NewMathMLmtrFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsContainerFrame* NS_NewMathMLmtdFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsTableFrame* aTableFrame);
+nsContainerFrame* NS_NewMathMLmtdInnerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmsqrtFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmrootFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmactionFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLmencloseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame* NS_NewMathMLsemanticsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+nsContainerFrame* NS_NewMathMLmathBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsContainerFrame* NS_NewMathMLmathInlineFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+#endif /* nsMathMLParts_h___ */
diff --git a/layout/mathml/nsMathMLSelectedFrame.cpp b/layout/mathml/nsMathMLSelectedFrame.cpp
new file mode 100644
index 000000000..2378d0763
--- /dev/null
+++ b/layout/mathml/nsMathMLSelectedFrame.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLSelectedFrame.h"
+#include "nsDisplayList.h"
+
+using namespace mozilla;
+
+nsMathMLSelectedFrame::~nsMathMLSelectedFrame()
+{
+}
+
+void
+nsMathMLSelectedFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ // Init our local attributes
+ mInvalidMarkup = false;
+ mSelectedFrame = nullptr;
+
+ // Let the base class do the rest
+ nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+NS_IMETHODIMP
+nsMathMLSelectedFrame::TransmitAutomaticData()
+{
+ // Note that to determine space-like and embellished op properties:
+ // - <semantics> behaves the same as <maction>
+ // - <annotation-xml> behaves the same as <mrow>
+
+ // The REC defines the following element to be space-like:
+ // * an maction element whose selected sub-expression exists and is
+ // space-like;
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(mSelectedFrame);
+ if (mathMLFrame && mathMLFrame->IsSpaceLike()) {
+ mPresentationData.flags |= NS_MATHML_SPACE_LIKE;
+ } else {
+ mPresentationData.flags &= ~NS_MATHML_SPACE_LIKE;
+ }
+
+ // The REC defines the following element to be an embellished operator:
+ // * an maction element whose selected sub-expression exists and is an
+ // embellished operator;
+ mPresentationData.baseFrame = mSelectedFrame;
+ GetEmbellishDataFrom(mSelectedFrame, mEmbellishData);
+
+ return NS_OK;
+}
+
+nsresult
+nsMathMLSelectedFrame::ChildListChanged(int32_t aModType)
+{
+ GetSelectedFrame();
+ return nsMathMLContainerFrame::ChildListChanged(aModType);
+}
+
+void
+nsMathMLSelectedFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ nsMathMLContainerFrame::SetInitialChildList(aListID, aChildList);
+ // This very first call to GetSelectedFrame() will cause us to be marked as an
+ // embellished operator if the selected child is an embellished operator
+ GetSelectedFrame();
+}
+
+// Only paint the selected child...
+void
+nsMathMLSelectedFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // Report an error if something wrong was found in this frame.
+ // We can't call nsDisplayMathMLError from here,
+ // so ask nsMathMLContainerFrame to do the work for us.
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+ return;
+ }
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ nsIFrame* childFrame = GetSelectedFrame();
+ if (childFrame) {
+ // Put the child's background directly onto the content list
+ nsDisplayListSet set(aLists, aLists.Content());
+ // The children should be in content order
+ BuildDisplayListForChild(aBuilder, childFrame, aDirtyRect, set);
+ }
+
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ // visual debug
+ DisplayBoundingMetrics(aBuilder, this, mReference, mBoundingMetrics, aLists);
+#endif
+}
+
+/* virtual */
+LogicalSize
+nsMathMLSelectedFrame::ComputeSize(nsRenderingContext *aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ nsIFrame* childFrame = GetSelectedFrame();
+ if (childFrame) {
+ // Delegate size computation to the child frame.
+ // Try to account for border/padding/margin on this frame and the child,
+ // though we don't really support them during reflow anyway...
+ nscoord availableISize = aAvailableISize - aBorder.ISize(aWM) -
+ aPadding.ISize(aWM) - aMargin.ISize(aWM);
+ LogicalSize cbSize = aCBSize - aBorder - aPadding - aMargin;
+ SizeComputationInput offsetState(childFrame, aRenderingContext, aWM,
+ availableISize);
+ LogicalSize size =
+ childFrame->ComputeSize(aRenderingContext, aWM, cbSize,
+ availableISize, offsetState.ComputedLogicalMargin().Size(aWM),
+ offsetState.ComputedLogicalBorderPadding().Size(aWM) -
+ offsetState.ComputedLogicalPadding().Size(aWM),
+ offsetState.ComputedLogicalPadding().Size(aWM),
+ aFlags);
+ return size + offsetState.ComputedLogicalBorderPadding().Size(aWM);
+ }
+ return LogicalSize(aWM);
+}
+
+// Only reflow the selected child ...
+void
+nsMathMLSelectedFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ aStatus = NS_FRAME_COMPLETE;
+ aDesiredSize.ClearSize();
+ aDesiredSize.SetBlockStartAscent(0);
+ mBoundingMetrics = nsBoundingMetrics();
+ nsIFrame* childFrame = GetSelectedFrame();
+ if (childFrame) {
+ WritingMode wm = childFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput childReflowInput(aPresContext, aReflowInput,
+ childFrame, availSize);
+ ReflowChild(childFrame, aPresContext, aDesiredSize,
+ childReflowInput, aStatus);
+ SaveReflowAndBoundingMetricsFor(childFrame, aDesiredSize,
+ aDesiredSize.mBoundingMetrics);
+ mBoundingMetrics = aDesiredSize.mBoundingMetrics;
+ }
+ FinalizeReflow(aReflowInput.mRenderingContext->GetDrawTarget(), aDesiredSize);
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+// Only place the selected child ...
+/* virtual */ nsresult
+nsMathMLSelectedFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ nsIFrame* childFrame = GetSelectedFrame();
+
+ if (mInvalidMarkup) {
+ return ReflowError(aDrawTarget, aDesiredSize);
+ }
+
+ aDesiredSize.ClearSize();
+ aDesiredSize.SetBlockStartAscent(0);
+ mBoundingMetrics = nsBoundingMetrics();
+ if (childFrame) {
+ GetReflowAndBoundingMetricsFor(childFrame, aDesiredSize, mBoundingMetrics);
+ if (aPlaceOrigin) {
+ FinishReflowChild(childFrame, PresContext(), aDesiredSize, nullptr, 0, 0, 0);
+ }
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+ }
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+ return NS_OK;
+}
diff --git a/layout/mathml/nsMathMLSelectedFrame.h b/layout/mathml/nsMathMLSelectedFrame.h
new file mode 100644
index 000000000..ad523a594
--- /dev/null
+++ b/layout/mathml/nsMathMLSelectedFrame.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLSelectedFrame_h___
+#define nsMathMLSelectedFrame_h___
+
+#include "nsMathMLContainerFrame.h"
+
+class nsMathMLSelectedFrame : public nsMathMLContainerFrame {
+public:
+ virtual void
+ Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ virtual void
+ SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual nsresult
+ ChildListChanged(int32_t aModType) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual mozilla::LogicalSize
+ ComputeSize(nsRenderingContext *aRenderingContext,
+ mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsQueryFrame::FrameIID GetFrameId() override = 0;
+
+protected:
+ explicit nsMathMLSelectedFrame(nsStyleContext* aContext) :
+ nsMathMLContainerFrame(aContext) {}
+ virtual ~nsMathMLSelectedFrame();
+
+ virtual nsIFrame* GetSelectedFrame() = 0;
+ nsIFrame* mSelectedFrame;
+
+ bool mInvalidMarkup;
+
+private:
+ void* operator new(size_t, nsIPresShell*) MOZ_MUST_OVERRIDE = delete;
+};
+
+#endif /* nsMathMLSelectedFrame_h___ */
diff --git a/layout/mathml/nsMathMLTokenFrame.cpp b/layout/mathml/nsMathMLTokenFrame.cpp
new file mode 100644
index 000000000..7558664f2
--- /dev/null
+++ b/layout/mathml/nsMathMLTokenFrame.cpp
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLTokenFrame.h"
+#include "nsPresContext.h"
+#include "nsContentUtils.h"
+#include "nsTextFrame.h"
+#include "mozilla/RestyleManager.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+nsIFrame*
+NS_NewMathMLTokenFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLTokenFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLTokenFrame)
+
+nsMathMLTokenFrame::~nsMathMLTokenFrame()
+{
+}
+
+NS_IMETHODIMP
+nsMathMLTokenFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ // let the base class get the default from our parent
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ return NS_OK;
+}
+
+eMathMLFrameType
+nsMathMLTokenFrame::GetMathMLFrameType()
+{
+ // treat everything other than <mi> as ordinary...
+ if (!mContent->IsMathMLElement(nsGkAtoms::mi_)) {
+ return eMathMLFrameType_Ordinary;
+ }
+
+ uint8_t mathVariant = StyleFont()->mMathVariant;
+ if ((mathVariant == NS_MATHML_MATHVARIANT_NONE &&
+ (StyleFont()->mFont.style == NS_STYLE_FONT_STYLE_ITALIC ||
+ HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI))) ||
+ mathVariant == NS_MATHML_MATHVARIANT_ITALIC ||
+ mathVariant == NS_MATHML_MATHVARIANT_BOLD_ITALIC ||
+ mathVariant == NS_MATHML_MATHVARIANT_SANS_SERIF_ITALIC ||
+ mathVariant == NS_MATHML_MATHVARIANT_SANS_SERIF_BOLD_ITALIC) {
+ return eMathMLFrameType_ItalicIdentifier;
+ }
+ return eMathMLFrameType_UprightIdentifier;
+}
+
+void
+nsMathMLTokenFrame::MarkTextFramesAsTokenMathML()
+{
+ nsIFrame* child = nullptr;
+ uint32_t childCount = 0;
+
+ // Set flags on child text frames
+ // - to force them to trim their leading and trailing whitespaces.
+ // - Indicate which frames are suitable for mathvariant
+ // - flag single character <mi> frames for special italic treatment
+ for (nsIFrame* childFrame = PrincipalChildList().FirstChild(); childFrame;
+ childFrame = childFrame->GetNextSibling()) {
+ for (nsIFrame* childFrame2 = childFrame->PrincipalChildList().FirstChild();
+ childFrame2; childFrame2 = childFrame2->GetNextSibling()) {
+ if (childFrame2->GetType() == nsGkAtoms::textFrame) {
+ childFrame2->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
+ child = childFrame2;
+ childCount++;
+ }
+ }
+ }
+ if (mContent->IsMathMLElement(nsGkAtoms::mi_) && childCount == 1) {
+ nsAutoString data;
+ nsContentUtils::GetNodeTextContent(mContent, false, data);
+
+ data.CompressWhitespace();
+ int32_t length = data.Length();
+
+ bool isSingleCharacter = length == 1 ||
+ (length == 2 && NS_IS_HIGH_SURROGATE(data[0]));
+
+ if (isSingleCharacter) {
+ child->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
+ AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
+ }
+ }
+}
+
+void
+nsMathMLTokenFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ // First, let the base class do its work
+ nsMathMLContainerFrame::SetInitialChildList(aListID, aChildList);
+ MarkTextFramesAsTokenMathML();
+}
+
+void
+nsMathMLTokenFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ nsMathMLContainerFrame::AppendFrames(aListID, aChildList);
+ MarkTextFramesAsTokenMathML();
+}
+
+void
+nsMathMLTokenFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aChildList)
+{
+ nsMathMLContainerFrame::InsertFrames(aListID, aPrevFrame, aChildList);
+ MarkTextFramesAsTokenMathML();
+}
+
+void
+nsMathMLTokenFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+
+ // initializations needed for empty markup like <mtag></mtag>
+ aDesiredSize.ClearSize();
+ aDesiredSize.SetBlockStartAscent(0);
+ aDesiredSize.mBoundingMetrics = nsBoundingMetrics();
+
+ for (nsIFrame* childFrame : PrincipalChildList()) {
+ // ask our children to compute their bounding metrics
+ ReflowOutput childDesiredSize(aReflowInput.GetWritingMode(),
+ aDesiredSize.mFlags
+ | NS_REFLOW_CALC_BOUNDING_METRICS);
+ WritingMode wm = childFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput childReflowInput(aPresContext, aReflowInput,
+ childFrame, availSize);
+ ReflowChild(childFrame, aPresContext, childDesiredSize,
+ childReflowInput, aStatus);
+ //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
+ SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
+ childDesiredSize.mBoundingMetrics);
+ }
+
+ // place and size children
+ FinalizeReflow(aReflowInput.mRenderingContext->GetDrawTarget(), aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+// For token elements, mBoundingMetrics is computed at the ReflowToken
+// pass, it is not computed here because our children may be text frames
+// that do not implement the GetBoundingMetrics() interface.
+/* virtual */ nsresult
+nsMathMLTokenFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ mBoundingMetrics = nsBoundingMetrics();
+ for (nsIFrame* childFrame :PrincipalChildList()) {
+ ReflowOutput childSize(aDesiredSize.GetWritingMode());
+ GetReflowAndBoundingMetricsFor(childFrame, childSize,
+ childSize.mBoundingMetrics, nullptr);
+ // compute and cache the bounding metrics
+ mBoundingMetrics += childSize.mBoundingMetrics;
+ }
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
+ nscoord ascent = fm->MaxAscent();
+ nscoord descent = fm->MaxDescent();
+
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.SetBlockStartAscent(std::max(mBoundingMetrics.ascent, ascent));
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() +
+ std::max(mBoundingMetrics.descent, descent);
+
+ if (aPlaceOrigin) {
+ nscoord dy, dx = 0;
+ for (nsIFrame* childFrame : PrincipalChildList()) {
+ ReflowOutput childSize(aDesiredSize.GetWritingMode());
+ GetReflowAndBoundingMetricsFor(childFrame, childSize,
+ childSize.mBoundingMetrics);
+
+ // place and size the child; (dx,0) makes the caret happy - bug 188146
+ dy = childSize.Height() == 0 ? 0 : aDesiredSize.BlockStartAscent() - childSize.BlockStartAscent();
+ FinishReflowChild(childFrame, PresContext(), childSize, nullptr, dx, dy, 0);
+ dx += childSize.Width();
+ }
+ }
+
+ SetReference(nsPoint(0, aDesiredSize.BlockStartAscent()));
+
+ return NS_OK;
+}
+
diff --git a/layout/mathml/nsMathMLTokenFrame.h b/layout/mathml/nsMathMLTokenFrame.h
new file mode 100644
index 000000000..dec2e3c13
--- /dev/null
+++ b/layout/mathml/nsMathMLTokenFrame.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLTokenFrame_h___
+#define nsMathMLTokenFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+//
+// Base class to handle token elements
+//
+
+class nsMathMLTokenFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLTokenFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ NS_IMETHOD
+ TransmitAutomaticData() override {
+ // The REC defines the following elements to be space-like:
+ // * an mtext, mspace, maligngroup, or malignmark element;
+ if (mContent->IsMathMLElement(nsGkAtoms::mtext_)) {
+ mPresentationData.flags |= NS_MATHML_SPACE_LIKE;
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ virtual eMathMLFrameType GetMathMLFrameType() override;
+
+ virtual void
+ SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual void
+ AppendFrames(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual void
+ InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aChildList) override;
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+protected:
+ explicit nsMathMLTokenFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext) {}
+ virtual ~nsMathMLTokenFrame();
+
+ void MarkTextFramesAsTokenMathML();
+};
+
+#endif /* nsMathMLTokentFrame_h___ */
diff --git a/layout/mathml/nsMathMLmactionFrame.cpp b/layout/mathml/nsMathMLmactionFrame.cpp
new file mode 100644
index 000000000..1e027ceab
--- /dev/null
+++ b/layout/mathml/nsMathMLmactionFrame.cpp
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmactionFrame.h"
+#include "nsCOMPtr.h"
+#include "nsPresContext.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsTextFragment.h"
+#include "nsIDOMEvent.h"
+#include "mozilla/gfx/2D.h"
+
+//
+// <maction> -- bind actions to a subexpression - implementation
+//
+
+enum nsMactionActionTypes {
+ NS_MATHML_ACTION_TYPE_CLASS_ERROR = 0x10,
+ NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION = 0x20,
+ NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION = 0x40,
+ NS_MATHML_ACTION_TYPE_CLASS_BITMASK = 0xF0,
+
+ NS_MATHML_ACTION_TYPE_NONE = NS_MATHML_ACTION_TYPE_CLASS_ERROR|0x01,
+
+ NS_MATHML_ACTION_TYPE_TOGGLE = NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION|0x01,
+ NS_MATHML_ACTION_TYPE_UNKNOWN = NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION|0x02,
+
+ NS_MATHML_ACTION_TYPE_STATUSLINE = NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION|0x01,
+ NS_MATHML_ACTION_TYPE_TOOLTIP = NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION|0x02
+};
+
+
+// helper function to parse actiontype attribute
+static int32_t
+GetActionType(nsIContent* aContent)
+{
+ nsAutoString value;
+
+ if (aContent) {
+ if (!aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::actiontype_, value))
+ return NS_MATHML_ACTION_TYPE_NONE;
+ }
+
+ if (value.EqualsLiteral("toggle"))
+ return NS_MATHML_ACTION_TYPE_TOGGLE;
+ if (value.EqualsLiteral("statusline"))
+ return NS_MATHML_ACTION_TYPE_STATUSLINE;
+ if (value.EqualsLiteral("tooltip"))
+ return NS_MATHML_ACTION_TYPE_TOOLTIP;
+
+ return NS_MATHML_ACTION_TYPE_UNKNOWN;
+}
+
+nsIFrame*
+NS_NewMathMLmactionFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmactionFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmactionFrame)
+
+nsMathMLmactionFrame::~nsMathMLmactionFrame()
+{
+ // unregister us as a mouse event listener ...
+ // printf("maction:%p unregistering as mouse event listener ...\n", this);
+ if (mListener) {
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("click"), mListener,
+ false);
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseover"), mListener,
+ false);
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"), mListener,
+ false);
+ }
+}
+
+void
+nsMathMLmactionFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ // Init our local attributes
+
+ mChildCount = -1; // these will be updated in GetSelectedFrame()
+ mActionType = GetActionType(aContent);
+
+ // Let the base class do the rest
+ return nsMathMLSelectedFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+nsresult
+nsMathMLmactionFrame::ChildListChanged(int32_t aModType)
+{
+ // update cached values
+ mChildCount = -1;
+ mSelectedFrame = nullptr;
+
+ return nsMathMLSelectedFrame::ChildListChanged(aModType);
+}
+
+// return the frame whose number is given by the attribute selection="number"
+nsIFrame*
+nsMathMLmactionFrame::GetSelectedFrame()
+{
+ nsAutoString value;
+ int32_t selection;
+
+ if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) ==
+ NS_MATHML_ACTION_TYPE_CLASS_ERROR) {
+ mSelection = -1;
+ mInvalidMarkup = true;
+ mSelectedFrame = nullptr;
+ return mSelectedFrame;
+ }
+
+ // Selection is not applied to tooltip and statusline.
+ // Thereby return the first child.
+ if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) ==
+ NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION) {
+ // We don't touch mChildCount here. It's incorrect to assign it 1,
+ // and it's inefficient to count the children. It's fine to leave
+ // it be equal -1 because it's not used with other actiontypes.
+ mSelection = 1;
+ mInvalidMarkup = false;
+ mSelectedFrame = mFrames.FirstChild();
+ return mSelectedFrame;
+ }
+
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::selection_, value);
+ if (!value.IsEmpty()) {
+ nsresult errorCode;
+ selection = value.ToInteger(&errorCode);
+ if (NS_FAILED(errorCode))
+ selection = 1;
+ }
+ else selection = 1; // default is first frame
+
+ if (-1 != mChildCount) { // we have been in this function before...
+ // cater for invalid user-supplied selection
+ if (selection > mChildCount || selection < 1)
+ selection = -1;
+ // quick return if it is identical with our cache
+ if (selection == mSelection)
+ return mSelectedFrame;
+ }
+
+ // get the selected child and cache new values...
+ int32_t count = 0;
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ if (!mSelectedFrame)
+ mSelectedFrame = childFrame; // default is first child
+ if (++count == selection)
+ mSelectedFrame = childFrame;
+
+ childFrame = childFrame->GetNextSibling();
+ }
+ // cater for invalid user-supplied selection
+ if (selection > count || selection < 1)
+ selection = -1;
+
+ mChildCount = count;
+ mSelection = selection;
+ mInvalidMarkup = (mSelection == -1);
+ TransmitAutomaticData();
+
+ return mSelectedFrame;
+}
+
+void
+nsMathMLmactionFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ nsMathMLSelectedFrame::SetInitialChildList(aListID, aChildList);
+
+ if (!mSelectedFrame) {
+ mActionType = NS_MATHML_ACTION_TYPE_NONE;
+ }
+ else {
+ // create mouse event listener and register it
+ mListener = new nsMathMLmactionFrame::MouseListener(this);
+ // printf("maction:%p registering as mouse event listener ...\n", this);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("click"), mListener,
+ false, false);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseover"), mListener,
+ false, false);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), mListener,
+ false, false);
+ }
+}
+
+nsresult
+nsMathMLmactionFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ bool needsReflow = false;
+
+ if (aAttribute == nsGkAtoms::actiontype_) {
+ // updating mActionType ...
+ int32_t oldActionType = mActionType;
+ mActionType = GetActionType(mContent);
+
+ // Initiate a reflow when actiontype classes are different.
+ if ((oldActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) !=
+ (mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK)) {
+ needsReflow = true;
+ }
+ } else if (aAttribute == nsGkAtoms::selection_) {
+ if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) ==
+ NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION) {
+ needsReflow = true;
+ }
+ } else {
+ // let the base class handle other attribute changes
+ return
+ nsMathMLContainerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+ }
+
+ if (needsReflow) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY);
+ }
+
+ return NS_OK;
+}
+
+// ################################################################
+// Event handlers
+// ################################################################
+
+NS_IMPL_ISUPPORTS(nsMathMLmactionFrame::MouseListener,
+ nsIDOMEventListener)
+
+
+// helper to show a msg on the status bar
+// curled from nsPluginFrame.cpp ...
+void
+ShowStatus(nsPresContext* aPresContext, nsString& aStatusMsg)
+{
+ nsCOMPtr<nsIDocShellTreeItem> docShellItem(aPresContext->GetDocShell());
+ if (docShellItem) {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ docShellItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (treeOwner) {
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(treeOwner));
+ if (browserChrome) {
+ browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, aStatusMsg.get());
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsMathMLmactionFrame::MouseListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("mouseover")) {
+ mOwner->MouseOver();
+ }
+ else if (eventType.EqualsLiteral("click")) {
+ mOwner->MouseClick();
+ }
+ else if (eventType.EqualsLiteral("mouseout")) {
+ mOwner->MouseOut();
+ }
+ else {
+ NS_ABORT();
+ }
+
+ return NS_OK;
+}
+
+void
+nsMathMLmactionFrame::MouseOver()
+{
+ // see if we should display a status message
+ if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) {
+ // retrieve content from a second child if it exists
+ nsIFrame* childFrame = mFrames.FrameAt(1);
+ if (!childFrame) return;
+
+ nsIContent* content = childFrame->GetContent();
+ if (!content) return;
+
+ // check whether the content is mtext or not
+ if (content->IsMathMLElement(nsGkAtoms::mtext_)) {
+ // get the text to be displayed
+ content = content->GetFirstChild();
+ if (!content) return;
+
+ const nsTextFragment* textFrg = content->GetText();
+ if (!textFrg) return;
+
+ nsAutoString text;
+ textFrg->AppendTo(text);
+ // collapse whitespaces as listed in REC, section 3.2.6.1
+ text.CompressWhitespace();
+ ShowStatus(PresContext(), text);
+ }
+ }
+}
+
+void
+nsMathMLmactionFrame::MouseOut()
+{
+ // see if we should remove the status message
+ if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) {
+ nsAutoString value;
+ value.SetLength(0);
+ ShowStatus(PresContext(), value);
+ }
+}
+
+void
+nsMathMLmactionFrame::MouseClick()
+{
+ if (NS_MATHML_ACTION_TYPE_TOGGLE == mActionType) {
+ if (mChildCount > 1) {
+ int32_t selection = (mSelection == mChildCount)? 1 : mSelection + 1;
+ nsAutoString value;
+ value.AppendInt(selection);
+ bool notify = false; // don't yet notify the document
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::selection_, value, notify);
+
+ // Now trigger a content-changed reflow...
+ PresContext()->PresShell()->
+ FrameNeedsReflow(mSelectedFrame, nsIPresShell::eTreeChange,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+}
diff --git a/layout/mathml/nsMathMLmactionFrame.h b/layout/mathml/nsMathMLmactionFrame.h
new file mode 100644
index 000000000..9f2a149b8
--- /dev/null
+++ b/layout/mathml/nsMathMLmactionFrame.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmactionFrame_h___
+#define nsMathMLmactionFrame_h___
+
+#include "nsCOMPtr.h"
+#include "nsMathMLSelectedFrame.h"
+#include "nsIDOMEventListener.h"
+#include "mozilla/Attributes.h"
+
+//
+// <maction> -- bind actions to a subexpression
+//
+
+class nsMathMLmactionFrame : public nsMathMLSelectedFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmactionFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual void
+ Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void
+ SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual nsresult
+ ChildListChanged(int32_t aModType) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+private:
+ void MouseClick();
+ void MouseOver();
+ void MouseOut();
+
+ class MouseListener final : public nsIDOMEventListener
+ {
+ private:
+ ~MouseListener() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ explicit MouseListener(nsMathMLmactionFrame* aOwner) : mOwner(aOwner) { }
+
+ nsMathMLmactionFrame* mOwner;
+ };
+
+protected:
+ explicit nsMathMLmactionFrame(nsStyleContext* aContext) :
+ nsMathMLSelectedFrame(aContext) {}
+ virtual ~nsMathMLmactionFrame();
+
+private:
+ int32_t mActionType;
+ int32_t mChildCount;
+ int32_t mSelection;
+ RefPtr<MouseListener> mListener;
+
+ // helper to return the frame for the attribute selection="number"
+ nsIFrame*
+ GetSelectedFrame() override;
+};
+
+#endif /* nsMathMLmactionFrame_h___ */
diff --git a/layout/mathml/nsMathMLmencloseFrame.cpp b/layout/mathml/nsMathMLmencloseFrame.cpp
new file mode 100644
index 000000000..64277a92e
--- /dev/null
+++ b/layout/mathml/nsMathMLmencloseFrame.cpp
@@ -0,0 +1,868 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmencloseFrame.h"
+
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsWhitespaceTokenizer.h"
+
+#include "nsDisplayList.h"
+#include "gfxContext.h"
+#include "nsMathMLChar.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+//
+// <menclose> -- enclose content with a stretching symbol such
+// as a long division sign. - implementation
+
+// longdiv:
+// Unicode 5.1 assigns U+27CC to LONG DIVISION, but a right parenthesis
+// renders better with current font support.
+static const char16_t kLongDivChar = ')';
+
+// radical: 'SQUARE ROOT'
+static const char16_t kRadicalChar = 0x221A;
+
+// updiagonalstrike
+static const uint8_t kArrowHeadSize = 10;
+
+// phasorangle
+static const uint8_t kPhasorangleWidth = 8;
+
+nsIFrame*
+NS_NewMathMLmencloseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmencloseFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmencloseFrame)
+
+nsMathMLmencloseFrame::nsMathMLmencloseFrame(nsStyleContext* aContext) :
+ nsMathMLContainerFrame(aContext), mNotationsToDraw(0),
+ mLongDivCharIndex(-1), mRadicalCharIndex(-1), mContentWidth(0)
+{
+}
+
+nsMathMLmencloseFrame::~nsMathMLmencloseFrame()
+{
+}
+
+nsresult nsMathMLmencloseFrame::AllocateMathMLChar(nsMencloseNotation mask)
+{
+ // Is the char already allocated?
+ if ((mask == NOTATION_LONGDIV && mLongDivCharIndex >= 0) ||
+ (mask == NOTATION_RADICAL && mRadicalCharIndex >= 0))
+ return NS_OK;
+
+ // No need to track the style context given to our MathML chars.
+ // The Style System will use Get/SetAdditionalStyleContext() to keep it
+ // up-to-date if dynamic changes arise.
+ uint32_t i = mMathMLChar.Length();
+ nsAutoString Char;
+
+ if (!mMathMLChar.AppendElement())
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (mask == NOTATION_LONGDIV) {
+ Char.Assign(kLongDivChar);
+ mLongDivCharIndex = i;
+ } else if (mask == NOTATION_RADICAL) {
+ Char.Assign(kRadicalChar);
+ mRadicalCharIndex = i;
+ }
+
+ nsPresContext *presContext = PresContext();
+ mMathMLChar[i].SetData(Char);
+ ResolveMathMLCharStyle(presContext, mContent, mStyleContext, &mMathMLChar[i]);
+
+ return NS_OK;
+}
+
+/*
+ * Add a notation to draw, if the argument is the name of a known notation.
+ * @param aNotation string name of a notation
+ */
+nsresult nsMathMLmencloseFrame::AddNotation(const nsAString& aNotation)
+{
+ nsresult rv;
+
+ if (aNotation.EqualsLiteral("longdiv")) {
+ rv = AllocateMathMLChar(NOTATION_LONGDIV);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mNotationsToDraw |= NOTATION_LONGDIV;
+ } else if (aNotation.EqualsLiteral("actuarial")) {
+ mNotationsToDraw |= (NOTATION_RIGHT | NOTATION_TOP);
+ } else if (aNotation.EqualsLiteral("radical")) {
+ rv = AllocateMathMLChar(NOTATION_RADICAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mNotationsToDraw |= NOTATION_RADICAL;
+ } else if (aNotation.EqualsLiteral("box")) {
+ mNotationsToDraw |= (NOTATION_LEFT | NOTATION_RIGHT |
+ NOTATION_TOP | NOTATION_BOTTOM);
+ } else if (aNotation.EqualsLiteral("roundedbox")) {
+ mNotationsToDraw |= NOTATION_ROUNDEDBOX;
+ } else if (aNotation.EqualsLiteral("circle")) {
+ mNotationsToDraw |= NOTATION_CIRCLE;
+ } else if (aNotation.EqualsLiteral("left")) {
+ mNotationsToDraw |= NOTATION_LEFT;
+ } else if (aNotation.EqualsLiteral("right")) {
+ mNotationsToDraw |= NOTATION_RIGHT;
+ } else if (aNotation.EqualsLiteral("top")) {
+ mNotationsToDraw |= NOTATION_TOP;
+ } else if (aNotation.EqualsLiteral("bottom")) {
+ mNotationsToDraw |= NOTATION_BOTTOM;
+ } else if (aNotation.EqualsLiteral("updiagonalstrike")) {
+ mNotationsToDraw |= NOTATION_UPDIAGONALSTRIKE;
+ } else if (aNotation.EqualsLiteral("updiagonalarrow")) {
+ mNotationsToDraw |= NOTATION_UPDIAGONALARROW;
+ } else if (aNotation.EqualsLiteral("downdiagonalstrike")) {
+ mNotationsToDraw |= NOTATION_DOWNDIAGONALSTRIKE;
+ } else if (aNotation.EqualsLiteral("verticalstrike")) {
+ mNotationsToDraw |= NOTATION_VERTICALSTRIKE;
+ } else if (aNotation.EqualsLiteral("horizontalstrike")) {
+ mNotationsToDraw |= NOTATION_HORIZONTALSTRIKE;
+ } else if (aNotation.EqualsLiteral("madruwb")) {
+ mNotationsToDraw |= (NOTATION_RIGHT | NOTATION_BOTTOM);
+ } else if (aNotation.EqualsLiteral("phasorangle")) {
+ mNotationsToDraw |= (NOTATION_BOTTOM | NOTATION_PHASORANGLE);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Initialize the list of notations to draw
+ */
+void nsMathMLmencloseFrame::InitNotations()
+{
+ mNotationsToDraw = 0;
+ mLongDivCharIndex = mRadicalCharIndex = -1;
+ mMathMLChar.Clear();
+
+ nsAutoString value;
+
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::notation_, value)) {
+ // parse the notation attribute
+ nsWhitespaceTokenizer tokenizer(value);
+
+ while (tokenizer.hasMoreTokens())
+ AddNotation(tokenizer.nextToken());
+
+ if (IsToDraw(NOTATION_UPDIAGONALARROW)) {
+ // For <menclose notation="updiagonalstrike updiagonalarrow">, if
+ // the two notations are drawn then the strike line may cause the point of
+ // the arrow to be too wide. Hence we will only draw the updiagonalarrow
+ // and the arrow shaft may be thought to be the updiagonalstrike.
+ mNotationsToDraw &= ~NOTATION_UPDIAGONALSTRIKE;
+ }
+ } else {
+ // default: longdiv
+ if (NS_FAILED(AllocateMathMLChar(NOTATION_LONGDIV)))
+ return;
+ mNotationsToDraw = NOTATION_LONGDIV;
+ }
+}
+
+NS_IMETHODIMP
+nsMathMLmencloseFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ // let the base class get the default from our parent
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
+
+ InitNotations();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMathMLmencloseFrame::TransmitAutomaticData()
+{
+ if (IsToDraw(NOTATION_RADICAL)) {
+ // The TeXBook (Ch 17. p.141) says that \sqrt is cramped
+ UpdatePresentationDataFromChildAt(0, -1,
+ NS_MATHML_COMPRESSED,
+ NS_MATHML_COMPRESSED);
+ }
+
+ return NS_OK;
+}
+
+void
+nsMathMLmencloseFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ /////////////
+ // paint the menclosed content
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags))
+ return;
+
+ nsRect mencloseRect = nsIFrame::GetRect();
+ mencloseRect.x = mencloseRect.y = 0;
+
+ if (IsToDraw(NOTATION_RADICAL)) {
+ mMathMLChar[mRadicalCharIndex].Display(aBuilder, this, aLists, 0);
+
+ nsRect rect;
+ mMathMLChar[mRadicalCharIndex].GetRect(rect);
+ rect.MoveBy(StyleVisibility()->mDirection ? -mContentWidth : rect.width, 0);
+ rect.SizeTo(mContentWidth, mRadicalRuleThickness);
+ DisplayBar(aBuilder, this, rect, aLists);
+ }
+
+ if (IsToDraw(NOTATION_PHASORANGLE)) {
+ DisplayNotation(aBuilder, this, mencloseRect, aLists,
+ mRuleThickness, NOTATION_PHASORANGLE);
+ }
+
+ if (IsToDraw(NOTATION_LONGDIV)) {
+ mMathMLChar[mLongDivCharIndex].Display(aBuilder, this, aLists, 1);
+
+ nsRect rect;
+ mMathMLChar[mLongDivCharIndex].GetRect(rect);
+ rect.SizeTo(rect.width + mContentWidth, mRuleThickness);
+ DisplayBar(aBuilder, this, rect, aLists);
+ }
+
+ if (IsToDraw(NOTATION_TOP)) {
+ nsRect rect(0, 0, mencloseRect.width, mRuleThickness);
+ DisplayBar(aBuilder, this, rect, aLists);
+ }
+
+ if (IsToDraw(NOTATION_BOTTOM)) {
+ nsRect rect(0, mencloseRect.height - mRuleThickness,
+ mencloseRect.width, mRuleThickness);
+ DisplayBar(aBuilder, this, rect, aLists);
+ }
+
+ if (IsToDraw(NOTATION_LEFT)) {
+ nsRect rect(0, 0, mRuleThickness, mencloseRect.height);
+ DisplayBar(aBuilder, this, rect, aLists);
+ }
+
+ if (IsToDraw(NOTATION_RIGHT)) {
+ nsRect rect(mencloseRect.width - mRuleThickness, 0,
+ mRuleThickness, mencloseRect.height);
+ DisplayBar(aBuilder, this, rect, aLists);
+ }
+
+ if (IsToDraw(NOTATION_ROUNDEDBOX)) {
+ DisplayNotation(aBuilder, this, mencloseRect, aLists,
+ mRuleThickness, NOTATION_ROUNDEDBOX);
+ }
+
+ if (IsToDraw(NOTATION_CIRCLE)) {
+ DisplayNotation(aBuilder, this, mencloseRect, aLists,
+ mRuleThickness, NOTATION_CIRCLE);
+ }
+
+ if (IsToDraw(NOTATION_UPDIAGONALSTRIKE)) {
+ DisplayNotation(aBuilder, this, mencloseRect, aLists,
+ mRuleThickness, NOTATION_UPDIAGONALSTRIKE);
+ }
+
+ if (IsToDraw(NOTATION_UPDIAGONALARROW)) {
+ DisplayNotation(aBuilder, this, mencloseRect, aLists,
+ mRuleThickness, NOTATION_UPDIAGONALARROW);
+ }
+
+ if (IsToDraw(NOTATION_DOWNDIAGONALSTRIKE)) {
+ DisplayNotation(aBuilder, this, mencloseRect, aLists,
+ mRuleThickness, NOTATION_DOWNDIAGONALSTRIKE);
+ }
+
+ if (IsToDraw(NOTATION_HORIZONTALSTRIKE)) {
+ nsRect rect(0, mencloseRect.height / 2 - mRuleThickness / 2,
+ mencloseRect.width, mRuleThickness);
+ DisplayBar(aBuilder, this, rect, aLists);
+ }
+
+ if (IsToDraw(NOTATION_VERTICALSTRIKE)) {
+ nsRect rect(mencloseRect.width / 2 - mRuleThickness / 2, 0,
+ mRuleThickness, mencloseRect.height);
+ DisplayBar(aBuilder, this, rect, aLists);
+ }
+}
+
+/* virtual */ nsresult
+nsMathMLmencloseFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize)
+{
+ return PlaceInternal(aDrawTarget, false, aDesiredSize, true);
+}
+
+/* virtual */ nsresult
+nsMathMLmencloseFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ return PlaceInternal(aDrawTarget, aPlaceOrigin, aDesiredSize, false);
+}
+
+/* virtual */ nsresult
+nsMathMLmencloseFrame::PlaceInternal(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize,
+ bool aWidthOnly)
+{
+ ///////////////
+ // Measure the size of our content using the base class to format like an
+ // inferred mrow.
+ ReflowOutput baseSize(aDesiredSize.GetWritingMode());
+ nsresult rv =
+ nsMathMLContainerFrame::Place(aDrawTarget, false, baseSize);
+
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
+ DidReflowChildren(PrincipalChildList().FirstChild());
+ return rv;
+ }
+
+ nsBoundingMetrics bmBase = baseSize.mBoundingMetrics;
+ nscoord dx_left = 0, dx_right = 0;
+ nsBoundingMetrics bmLongdivChar, bmRadicalChar;
+ nscoord radicalAscent = 0, radicalDescent = 0;
+ nscoord longdivAscent = 0, longdivDescent = 0;
+ nscoord psi = 0;
+ nscoord leading = 0;
+
+ ///////////////
+ // Thickness of bars and font metrics
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+ GetRuleThickness(aDrawTarget, fm, mRuleThickness);
+ if (mRuleThickness < onePixel) {
+ mRuleThickness = onePixel;
+ }
+
+ char16_t one = '1';
+ nsBoundingMetrics bmOne =
+ nsLayoutUtils::AppUnitBoundsOfString(&one, 1, *fm, aDrawTarget);
+
+ ///////////////
+ // General rules: the menclose element takes the size of the enclosed content.
+ // We add a padding when needed.
+
+ // determine padding & psi
+ nscoord padding = 3 * mRuleThickness;
+ nscoord delta = padding % onePixel;
+ if (delta)
+ padding += onePixel - delta; // round up
+
+ if (IsToDraw(NOTATION_LONGDIV) || IsToDraw(NOTATION_RADICAL)) {
+ GetRadicalParameters(fm, StyleFont()->mMathDisplay ==
+ NS_MATHML_DISPLAYSTYLE_BLOCK,
+ mRadicalRuleThickness, leading, psi);
+
+ // make sure that the rule appears on on screen
+ if (mRadicalRuleThickness < onePixel) {
+ mRadicalRuleThickness = onePixel;
+ }
+
+ // adjust clearance psi to get an exact number of pixels -- this
+ // gives a nicer & uniform look on stacked radicals (bug 130282)
+ delta = psi % onePixel;
+ if (delta) {
+ psi += onePixel - delta; // round up
+ }
+ }
+
+ // Set horizontal parameters
+ if (IsToDraw(NOTATION_ROUNDEDBOX) ||
+ IsToDraw(NOTATION_TOP) ||
+ IsToDraw(NOTATION_LEFT) ||
+ IsToDraw(NOTATION_BOTTOM) ||
+ IsToDraw(NOTATION_CIRCLE))
+ dx_left = padding;
+
+ if (IsToDraw(NOTATION_ROUNDEDBOX) ||
+ IsToDraw(NOTATION_TOP) ||
+ IsToDraw(NOTATION_RIGHT) ||
+ IsToDraw(NOTATION_BOTTOM) ||
+ IsToDraw(NOTATION_CIRCLE))
+ dx_right = padding;
+
+ // Set vertical parameters
+ if (IsToDraw(NOTATION_RIGHT) ||
+ IsToDraw(NOTATION_LEFT) ||
+ IsToDraw(NOTATION_UPDIAGONALSTRIKE) ||
+ IsToDraw(NOTATION_UPDIAGONALARROW) ||
+ IsToDraw(NOTATION_DOWNDIAGONALSTRIKE) ||
+ IsToDraw(NOTATION_VERTICALSTRIKE) ||
+ IsToDraw(NOTATION_CIRCLE) ||
+ IsToDraw(NOTATION_ROUNDEDBOX) ||
+ IsToDraw(NOTATION_RADICAL) ||
+ IsToDraw(NOTATION_LONGDIV) ||
+ IsToDraw(NOTATION_PHASORANGLE)) {
+ // set a minimal value for the base height
+ bmBase.ascent = std::max(bmOne.ascent, bmBase.ascent);
+ bmBase.descent = std::max(0, bmBase.descent);
+ }
+
+ mBoundingMetrics.ascent = bmBase.ascent;
+ mBoundingMetrics.descent = bmBase.descent;
+
+ if (IsToDraw(NOTATION_ROUNDEDBOX) ||
+ IsToDraw(NOTATION_TOP) ||
+ IsToDraw(NOTATION_LEFT) ||
+ IsToDraw(NOTATION_RIGHT) ||
+ IsToDraw(NOTATION_CIRCLE))
+ mBoundingMetrics.ascent += padding;
+
+ if (IsToDraw(NOTATION_ROUNDEDBOX) ||
+ IsToDraw(NOTATION_LEFT) ||
+ IsToDraw(NOTATION_RIGHT) ||
+ IsToDraw(NOTATION_BOTTOM) ||
+ IsToDraw(NOTATION_CIRCLE))
+ mBoundingMetrics.descent += padding;
+
+ ///////////////
+ // phasorangle notation
+ if (IsToDraw(NOTATION_PHASORANGLE)) {
+ nscoord phasorangleWidth = kPhasorangleWidth * mRuleThickness;
+ // Update horizontal parameters
+ dx_left = std::max(dx_left, phasorangleWidth);
+ }
+
+ ///////////////
+ // updiagonal arrow notation. We need enough space at the top right corner to
+ // draw the arrow head.
+ if (IsToDraw(NOTATION_UPDIAGONALARROW)) {
+ // This is an estimate, see nsDisplayNotation::Paint for the exact head size
+ nscoord arrowHeadSize = kArrowHeadSize * mRuleThickness;
+
+ // We want that the arrow shaft strikes the menclose content and that the
+ // arrow head does not overlap with that content. Hence we add some space
+ // on the right. We don't add space on the top but only ensure that the
+ // ascent is large enough.
+ dx_right = std::max(dx_right, arrowHeadSize);
+ mBoundingMetrics.ascent = std::max(mBoundingMetrics.ascent, arrowHeadSize);
+ }
+
+ ///////////////
+ // circle notation: we don't want the ellipse to overlap the enclosed
+ // content. Hence, we need to increase the size of the bounding box by a
+ // factor of at least sqrt(2).
+ if (IsToDraw(NOTATION_CIRCLE)) {
+ double ratio = (sqrt(2.0) - 1.0) / 2.0;
+ nscoord padding2;
+
+ // Update horizontal parameters
+ padding2 = ratio * bmBase.width;
+
+ dx_left = std::max(dx_left, padding2);
+ dx_right = std::max(dx_right, padding2);
+
+ // Update vertical parameters
+ padding2 = ratio * (bmBase.ascent + bmBase.descent);
+
+ mBoundingMetrics.ascent = std::max(mBoundingMetrics.ascent,
+ bmBase.ascent + padding2);
+ mBoundingMetrics.descent = std::max(mBoundingMetrics.descent,
+ bmBase.descent + padding2);
+ }
+
+ ///////////////
+ // longdiv notation:
+ if (IsToDraw(NOTATION_LONGDIV)) {
+ if (aWidthOnly) {
+ nscoord longdiv_width = mMathMLChar[mLongDivCharIndex].
+ GetMaxWidth(PresContext(), aDrawTarget, fontSizeInflation);
+
+ // Update horizontal parameters
+ dx_left = std::max(dx_left, longdiv_width);
+ } else {
+ // Stretch the parenthesis to the appropriate height if it is not
+ // big enough.
+ nsBoundingMetrics contSize = bmBase;
+ contSize.ascent = mRuleThickness;
+ contSize.descent = bmBase.ascent + bmBase.descent + psi;
+
+ // height(longdiv) should be >= height(base) + psi + mRuleThickness
+ mMathMLChar[mLongDivCharIndex].Stretch(PresContext(), aDrawTarget,
+ fontSizeInflation,
+ NS_STRETCH_DIRECTION_VERTICAL,
+ contSize, bmLongdivChar,
+ NS_STRETCH_LARGER, false);
+ mMathMLChar[mLongDivCharIndex].GetBoundingMetrics(bmLongdivChar);
+
+ // Update horizontal parameters
+ dx_left = std::max(dx_left, bmLongdivChar.width);
+
+ // Update vertical parameters
+ longdivAscent = bmBase.ascent + psi + mRuleThickness;
+ longdivDescent = std::max(bmBase.descent,
+ (bmLongdivChar.ascent + bmLongdivChar.descent -
+ longdivAscent));
+
+ mBoundingMetrics.ascent = std::max(mBoundingMetrics.ascent,
+ longdivAscent);
+ mBoundingMetrics.descent = std::max(mBoundingMetrics.descent,
+ longdivDescent);
+ }
+ }
+
+ ///////////////
+ // radical notation:
+ if (IsToDraw(NOTATION_RADICAL)) {
+ nscoord *dx_leading = StyleVisibility()->mDirection ? &dx_right : &dx_left;
+
+ if (aWidthOnly) {
+ nscoord radical_width = mMathMLChar[mRadicalCharIndex].
+ GetMaxWidth(PresContext(), aDrawTarget, fontSizeInflation);
+
+ // Update horizontal parameters
+ *dx_leading = std::max(*dx_leading, radical_width);
+ } else {
+ // Stretch the radical symbol to the appropriate height if it is not
+ // big enough.
+ nsBoundingMetrics contSize = bmBase;
+ contSize.ascent = mRadicalRuleThickness;
+ contSize.descent = bmBase.ascent + bmBase.descent + psi;
+
+ // height(radical) should be >= height(base) + psi + mRadicalRuleThickness
+ mMathMLChar[mRadicalCharIndex].Stretch(PresContext(), aDrawTarget,
+ fontSizeInflation,
+ NS_STRETCH_DIRECTION_VERTICAL,
+ contSize, bmRadicalChar,
+ NS_STRETCH_LARGER,
+ StyleVisibility()->mDirection);
+ mMathMLChar[mRadicalCharIndex].GetBoundingMetrics(bmRadicalChar);
+
+ // Update horizontal parameters
+ *dx_leading = std::max(*dx_leading, bmRadicalChar.width);
+
+ // Update vertical parameters
+ radicalAscent = bmBase.ascent + psi + mRadicalRuleThickness;
+ radicalDescent = std::max(bmBase.descent,
+ (bmRadicalChar.ascent + bmRadicalChar.descent -
+ radicalAscent));
+
+ mBoundingMetrics.ascent = std::max(mBoundingMetrics.ascent,
+ radicalAscent);
+ mBoundingMetrics.descent = std::max(mBoundingMetrics.descent,
+ radicalDescent);
+ }
+ }
+
+ ///////////////
+ //
+ if (IsToDraw(NOTATION_CIRCLE) ||
+ IsToDraw(NOTATION_ROUNDEDBOX) ||
+ (IsToDraw(NOTATION_LEFT) && IsToDraw(NOTATION_RIGHT))) {
+ // center the menclose around the content (horizontally)
+ dx_left = dx_right = std::max(dx_left, dx_right);
+ }
+
+ ///////////////
+ // The maximum size is now computed: set the remaining parameters
+ mBoundingMetrics.width = dx_left + bmBase.width + dx_right;
+
+ mBoundingMetrics.leftBearing = std::min(0, dx_left + bmBase.leftBearing);
+ mBoundingMetrics.rightBearing =
+ std::max(mBoundingMetrics.width, dx_left + bmBase.rightBearing);
+
+ aDesiredSize.Width() = mBoundingMetrics.width;
+
+ aDesiredSize.SetBlockStartAscent(std::max(mBoundingMetrics.ascent,
+ baseSize.BlockStartAscent()));
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() +
+ std::max(mBoundingMetrics.descent,
+ baseSize.Height() - baseSize.BlockStartAscent());
+
+ if (IsToDraw(NOTATION_LONGDIV) || IsToDraw(NOTATION_RADICAL)) {
+ nscoord desiredSizeAscent = aDesiredSize.BlockStartAscent();
+ nscoord desiredSizeDescent = aDesiredSize.Height() -
+ aDesiredSize.BlockStartAscent();
+
+ if (IsToDraw(NOTATION_LONGDIV)) {
+ desiredSizeAscent = std::max(desiredSizeAscent,
+ longdivAscent + leading);
+ desiredSizeDescent = std::max(desiredSizeDescent,
+ longdivDescent + mRuleThickness);
+ }
+
+ if (IsToDraw(NOTATION_RADICAL)) {
+ desiredSizeAscent = std::max(desiredSizeAscent,
+ radicalAscent + leading);
+ desiredSizeDescent = std::max(desiredSizeDescent,
+ radicalDescent + mRadicalRuleThickness);
+ }
+
+ aDesiredSize.SetBlockStartAscent(desiredSizeAscent);
+ aDesiredSize.Height() = desiredSizeAscent + desiredSizeDescent;
+ }
+
+ if (IsToDraw(NOTATION_CIRCLE) ||
+ IsToDraw(NOTATION_ROUNDEDBOX) ||
+ (IsToDraw(NOTATION_TOP) && IsToDraw(NOTATION_BOTTOM))) {
+ // center the menclose around the content (vertically)
+ nscoord dy = std::max(aDesiredSize.BlockStartAscent() - bmBase.ascent,
+ aDesiredSize.Height() -
+ aDesiredSize.BlockStartAscent() - bmBase.descent);
+
+ aDesiredSize.SetBlockStartAscent(bmBase.ascent + dy);
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + bmBase.descent + dy;
+ }
+
+ // Update mBoundingMetrics ascent/descent
+ if (IsToDraw(NOTATION_TOP) ||
+ IsToDraw(NOTATION_RIGHT) ||
+ IsToDraw(NOTATION_LEFT) ||
+ IsToDraw(NOTATION_UPDIAGONALSTRIKE) ||
+ IsToDraw(NOTATION_UPDIAGONALARROW) ||
+ IsToDraw(NOTATION_DOWNDIAGONALSTRIKE) ||
+ IsToDraw(NOTATION_VERTICALSTRIKE) ||
+ IsToDraw(NOTATION_CIRCLE) ||
+ IsToDraw(NOTATION_ROUNDEDBOX))
+ mBoundingMetrics.ascent = aDesiredSize.BlockStartAscent();
+
+ if (IsToDraw(NOTATION_BOTTOM) ||
+ IsToDraw(NOTATION_RIGHT) ||
+ IsToDraw(NOTATION_LEFT) ||
+ IsToDraw(NOTATION_UPDIAGONALSTRIKE) ||
+ IsToDraw(NOTATION_UPDIAGONALARROW) ||
+ IsToDraw(NOTATION_DOWNDIAGONALSTRIKE) ||
+ IsToDraw(NOTATION_VERTICALSTRIKE) ||
+ IsToDraw(NOTATION_CIRCLE) ||
+ IsToDraw(NOTATION_ROUNDEDBOX))
+ mBoundingMetrics.descent = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
+
+ // phasorangle notation:
+ // move up from the bottom by the angled line height
+ if (IsToDraw(NOTATION_PHASORANGLE))
+ mBoundingMetrics.ascent = std::max(mBoundingMetrics.ascent, 2 * kPhasorangleWidth * mRuleThickness - mBoundingMetrics.descent);
+
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ if (aPlaceOrigin) {
+ //////////////////
+ // Set position and size of MathMLChars
+ if (IsToDraw(NOTATION_LONGDIV))
+ mMathMLChar[mLongDivCharIndex].SetRect(nsRect(dx_left -
+ bmLongdivChar.width,
+ aDesiredSize.BlockStartAscent() -
+ longdivAscent,
+ bmLongdivChar.width,
+ bmLongdivChar.ascent +
+ bmLongdivChar.descent));
+
+ if (IsToDraw(NOTATION_RADICAL)) {
+ nscoord dx = (StyleVisibility()->mDirection ?
+ dx_left + bmBase.width : dx_left - bmRadicalChar.width);
+
+ mMathMLChar[mRadicalCharIndex].SetRect(nsRect(dx,
+ aDesiredSize.BlockStartAscent() -
+ radicalAscent,
+ bmRadicalChar.width,
+ bmRadicalChar.ascent +
+ bmRadicalChar.descent));
+ }
+
+ mContentWidth = bmBase.width;
+
+ //////////////////
+ // Finish reflowing child frames
+ PositionRowChildFrames(dx_left, aDesiredSize.BlockStartAscent());
+ }
+
+ return NS_OK;
+}
+
+nscoord
+nsMathMLmencloseFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize)
+{
+ nscoord gap = nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize);
+ if (!gap)
+ return 0;
+
+ // Move the MathML characters
+ nsRect rect;
+ for (uint32_t i = 0; i < mMathMLChar.Length(); i++) {
+ mMathMLChar[i].GetRect(rect);
+ rect.MoveBy(gap, 0);
+ mMathMLChar[i].SetRect(rect);
+ }
+
+ return gap;
+}
+
+nsresult
+nsMathMLmencloseFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aAttribute == nsGkAtoms::notation_) {
+ InitNotations();
+ }
+
+ return nsMathMLContainerFrame::
+ AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+//////////////////
+// the Style System will use these to pass the proper style context to our
+// MathMLChar
+nsStyleContext*
+nsMathMLmencloseFrame::GetAdditionalStyleContext(int32_t aIndex) const
+{
+ int32_t len = mMathMLChar.Length();
+ if (aIndex >= 0 && aIndex < len)
+ return mMathMLChar[aIndex].GetStyleContext();
+ else
+ return nullptr;
+}
+
+void
+nsMathMLmencloseFrame::SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext)
+{
+ int32_t len = mMathMLChar.Length();
+ if (aIndex >= 0 && aIndex < len)
+ mMathMLChar[aIndex].SetStyleContext(aStyleContext);
+}
+
+class nsDisplayNotation : public nsDisplayItem
+{
+public:
+ nsDisplayNotation(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect,
+ nscoord aThickness, nsMencloseNotation aType)
+ : nsDisplayItem(aBuilder, aFrame), mRect(aRect),
+ mThickness(aThickness), mType(aType) {
+ MOZ_COUNT_CTOR(nsDisplayNotation);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayNotation() {
+ MOZ_COUNT_DTOR(nsDisplayNotation);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLMencloseNotation", TYPE_MATHML_MENCLOSE_NOTATION)
+
+private:
+ nsRect mRect;
+ nscoord mThickness;
+ nsMencloseNotation mType;
+};
+
+void nsDisplayNotation::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
+ nsPresContext* presContext = mFrame->PresContext();
+
+ Float strokeWidth = presContext->AppUnitsToGfxUnits(mThickness);
+
+ Rect rect = NSRectToRect(mRect + ToReferenceFrame(),
+ presContext->AppUnitsPerDevPixel());
+ rect.Deflate(strokeWidth / 2.f);
+
+ ColorPattern color(ToDeviceColor(
+ mFrame->GetVisitedDependentColor(eCSSProperty__webkit_text_fill_color)));
+
+ StrokeOptions strokeOptions(strokeWidth);
+
+ switch(mType)
+ {
+ case NOTATION_CIRCLE: {
+ RefPtr<Path> ellipse =
+ MakePathForEllipse(aDrawTarget, rect.Center(), rect.Size());
+ aDrawTarget.Stroke(ellipse, color, strokeOptions);
+ return;
+ }
+ case NOTATION_ROUNDEDBOX: {
+ Float radius = 3 * strokeWidth;
+ RectCornerRadii radii(radius, radius);
+ RefPtr<Path> roundedRect =
+ MakePathForRoundedRect(aDrawTarget, rect, radii, true);
+ aDrawTarget.Stroke(roundedRect, color, strokeOptions);
+ return;
+ }
+ case NOTATION_UPDIAGONALSTRIKE: {
+ aDrawTarget.StrokeLine(rect.BottomLeft(), rect.TopRight(),
+ color, strokeOptions);
+ return;
+ }
+ case NOTATION_DOWNDIAGONALSTRIKE: {
+ aDrawTarget.StrokeLine(rect.TopLeft(), rect.BottomRight(),
+ color, strokeOptions);
+ return;
+ }
+ case NOTATION_UPDIAGONALARROW: {
+ // Compute some parameters to draw the updiagonalarrow. The values below
+ // are taken from MathJax's HTML-CSS output.
+ Float W = rect.Width(); gfxFloat H = rect.Height();
+ Float l = sqrt(W*W + H*H);
+ Float f = Float(kArrowHeadSize) * strokeWidth / l;
+ Float w = W * f; gfxFloat h = H * f;
+
+ // Draw the arrow shaft
+ aDrawTarget.StrokeLine(rect.BottomLeft(),
+ rect.TopRight() + Point(-.7*w, .7*h),
+ color, strokeOptions);
+
+ // Draw the arrow head
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ builder->MoveTo(rect.TopRight());
+ builder->LineTo(rect.TopRight() + Point(-w -.4*h, std::max(-strokeWidth / 2.0, h - .4*w)));
+ builder->LineTo(rect.TopRight() + Point(-.7*w, .7*h));
+ builder->LineTo(rect.TopRight() + Point(std::min(strokeWidth / 2.0, -w + .4*h), h + .4*w));
+ builder->Close();
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget.Fill(path, color);
+ return;
+ }
+ case NOTATION_PHASORANGLE: {
+ // Compute some parameters to draw the angled line,
+ // that uses a slope of 2 (angle = tan^-1(2)).
+ // H = w * tan(angle) = w * 2
+ Float w = Float(kPhasorangleWidth) * strokeWidth;
+ Float H = 2 * w;
+
+ // Draw the angled line
+ aDrawTarget.StrokeLine(rect.BottomLeft(),
+ rect.BottomLeft() + Point(w, -H),
+ color, strokeOptions);
+ return;
+ }
+ default:
+ NS_NOTREACHED("This notation can not be drawn using nsDisplayNotation");
+ }
+}
+
+void
+nsMathMLmencloseFrame::DisplayNotation(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect,
+ const nsDisplayListSet& aLists,
+ nscoord aThickness,
+ nsMencloseNotation aType)
+{
+ if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty() ||
+ aThickness <= 0)
+ return;
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayNotation(aBuilder, aFrame, aRect, aThickness, aType));
+}
diff --git a/layout/mathml/nsMathMLmencloseFrame.h b/layout/mathml/nsMathMLmencloseFrame.h
new file mode 100644
index 000000000..82d73b2cf
--- /dev/null
+++ b/layout/mathml/nsMathMLmencloseFrame.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsMathMLmencloseFrame_h___
+#define nsMathMLmencloseFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+//
+// <menclose> -- enclose content with a stretching symbol such
+// as a long division sign.
+//
+
+/*
+ The MathML REC describes:
+
+ The menclose element renders its content inside the enclosing notation
+ specified by its notation attribute. menclose accepts any number of arguments;
+ if this number is not 1, its contents are treated as a single "inferred mrow"
+ containing its arguments, as described in Section 3.1.3 Required Arguments.
+*/
+
+enum nsMencloseNotation
+ {
+ NOTATION_LONGDIV = 0x1,
+ NOTATION_RADICAL = 0x2,
+ NOTATION_ROUNDEDBOX = 0x4,
+ NOTATION_CIRCLE = 0x8,
+ NOTATION_LEFT = 0x10,
+ NOTATION_RIGHT = 0x20,
+ NOTATION_TOP = 0x40,
+ NOTATION_BOTTOM = 0x80,
+ NOTATION_UPDIAGONALSTRIKE = 0x100,
+ NOTATION_DOWNDIAGONALSTRIKE = 0x200,
+ NOTATION_VERTICALSTRIKE = 0x400,
+ NOTATION_HORIZONTALSTRIKE = 0x800,
+ NOTATION_UPDIAGONALARROW = 0x1000,
+ NOTATION_PHASORANGLE = 0x2000
+ };
+
+class nsMathMLmencloseFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmencloseFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual nsresult
+ MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void
+ SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext) override;
+ virtual nsStyleContext*
+ GetAdditionalStyleContext(int32_t aIndex) const override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ virtual nscoord
+ FixInterFrameSpacing(ReflowOutput& aDesiredSize) override;
+
+ bool
+ IsMrowLike() override {
+ return mFrames.FirstChild() != mFrames.LastChild() ||
+ !mFrames.FirstChild();
+ }
+
+protected:
+ explicit nsMathMLmencloseFrame(nsStyleContext* aContext);
+ virtual ~nsMathMLmencloseFrame();
+
+ nsresult PlaceInternal(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize,
+ bool aWidthOnly);
+
+ // functions to parse the "notation" attribute.
+ nsresult AddNotation(const nsAString& aNotation);
+ void InitNotations();
+
+ // Description of the notations to draw
+ uint32_t mNotationsToDraw;
+ bool IsToDraw(nsMencloseNotation mask)
+ {
+ return mask & mNotationsToDraw;
+ }
+
+ nscoord mRuleThickness;
+ nscoord mRadicalRuleThickness;
+ nsTArray<nsMathMLChar> mMathMLChar;
+ int8_t mLongDivCharIndex, mRadicalCharIndex;
+ nscoord mContentWidth;
+ nsresult AllocateMathMLChar(nsMencloseNotation mask);
+
+ // Display a frame of the specified type.
+ // @param aType Type of frame to display
+ void DisplayNotation(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect,
+ const nsDisplayListSet& aLists,
+ nscoord aThickness, nsMencloseNotation aType);
+};
+
+#endif /* nsMathMLmencloseFrame_h___ */
diff --git a/layout/mathml/nsMathMLmfencedFrame.cpp b/layout/mathml/nsMathMLmfencedFrame.cpp
new file mode 100644
index 000000000..ca780e649
--- /dev/null
+++ b/layout/mathml/nsMathMLmfencedFrame.cpp
@@ -0,0 +1,743 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsMathMLmfencedFrame.h"
+#include "nsRenderingContext.h"
+#include "nsMathMLChar.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+//
+// <mfenced> -- surround content with a pair of fences
+//
+
+nsIFrame*
+NS_NewMathMLmfencedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmfencedFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfencedFrame)
+
+nsMathMLmfencedFrame::~nsMathMLmfencedFrame()
+{
+ RemoveFencesAndSeparators();
+}
+
+NS_IMETHODIMP
+nsMathMLmfencedFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ // let the base class get the default from our parent
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
+
+ RemoveFencesAndSeparators();
+ CreateFencesAndSeparators(PresContext());
+
+ return NS_OK;
+}
+
+void
+nsMathMLmfencedFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ // First, let the base class do its work
+ nsMathMLContainerFrame::SetInitialChildList(aListID, aChildList);
+
+ // InheritAutomaticData will not get called if our parent is not a mathml
+ // frame, so initialize NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY for
+ // GetPreferredStretchSize() from Reflow().
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
+ // No need to track the style contexts given to our MathML chars.
+ // The Style System will use Get/SetAdditionalStyleContext() to keep them
+ // up-to-date if dynamic changes arise.
+ CreateFencesAndSeparators(PresContext());
+}
+
+nsresult
+nsMathMLmfencedFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ RemoveFencesAndSeparators();
+ CreateFencesAndSeparators(PresContext());
+
+ return nsMathMLContainerFrame::
+ AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+nsresult
+nsMathMLmfencedFrame::ChildListChanged(int32_t aModType)
+{
+ RemoveFencesAndSeparators();
+ CreateFencesAndSeparators(PresContext());
+
+ return nsMathMLContainerFrame::ChildListChanged(aModType);
+}
+
+void
+nsMathMLmfencedFrame::RemoveFencesAndSeparators()
+{
+ delete mOpenChar;
+ delete mCloseChar;
+ if (mSeparatorsChar) delete[] mSeparatorsChar;
+
+ mOpenChar = nullptr;
+ mCloseChar = nullptr;
+ mSeparatorsChar = nullptr;
+ mSeparatorsCount = 0;
+}
+
+void
+nsMathMLmfencedFrame::CreateFencesAndSeparators(nsPresContext* aPresContext)
+{
+ nsAutoString value;
+
+ //////////////
+ // see if the opening fence is there ...
+ if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::open, value)) {
+ value = char16_t('('); // default as per the MathML REC
+ } else {
+ value.CompressWhitespace();
+ }
+
+ if (!value.IsEmpty()) {
+ mOpenChar = new nsMathMLChar;
+ mOpenChar->SetData(value);
+ ResolveMathMLCharStyle(aPresContext, mContent, mStyleContext, mOpenChar);
+ }
+
+ //////////////
+ // see if the closing fence is there ...
+ if(!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::close, value)) {
+ value = char16_t(')'); // default as per the MathML REC
+ } else {
+ value.CompressWhitespace();
+ }
+
+ if (!value.IsEmpty()) {
+ mCloseChar = new nsMathMLChar;
+ mCloseChar->SetData(value);
+ ResolveMathMLCharStyle(aPresContext, mContent, mStyleContext, mCloseChar);
+ }
+
+ //////////////
+ // see if separators are there ...
+ if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::separators_, value)) {
+ value = char16_t(','); // default as per the MathML REC
+ } else {
+ value.StripWhitespace();
+ }
+
+ mSeparatorsCount = value.Length();
+ if (0 < mSeparatorsCount) {
+ int32_t sepCount = mFrames.GetLength() - 1;
+ if (0 < sepCount) {
+ mSeparatorsChar = new nsMathMLChar[sepCount];
+ nsAutoString sepChar;
+ for (int32_t i = 0; i < sepCount; i++) {
+ if (i < mSeparatorsCount) {
+ sepChar = value[i];
+ }
+ else {
+ sepChar = value[mSeparatorsCount-1];
+ }
+ mSeparatorsChar[i].SetData(sepChar);
+ ResolveMathMLCharStyle(aPresContext, mContent, mStyleContext, &mSeparatorsChar[i]);
+ }
+ mSeparatorsCount = sepCount;
+ } else {
+ // No separators. Note that sepCount can be -1 here, so don't
+ // set mSeparatorsCount to it.
+ mSeparatorsCount = 0;
+ }
+ }
+}
+
+void
+nsMathMLmfencedFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ /////////////
+ // display the content
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ ////////////
+ // display fences and separators
+ uint32_t count = 0;
+ if (mOpenChar) {
+ mOpenChar->Display(aBuilder, this, aLists, count++);
+ }
+
+ if (mCloseChar) {
+ mCloseChar->Display(aBuilder, this, aLists, count++);
+ }
+
+ for (int32_t i = 0; i < mSeparatorsCount; i++) {
+ mSeparatorsChar[i].Display(aBuilder, this, aLists, count++);
+ }
+}
+
+/* @param aMetrics is an IN/OUT. Provide the current metrics for the mFenced
+ frame and it will be enlarged as necessary.
+For simplicity the width of the container is always incremented by the width
+of the nsMathMLChar. As we only stretch fences and separators in the vertical
+direction, this has no impact on overall appearance.
+*/
+static void
+ApplyUnstretchedMetrics(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ nsMathMLChar* aMathMLChar,
+ nsBoundingMetrics& aMetrics,
+ bool aIsRTL)
+{
+ if (aMathMLChar && 0 < aMathMLChar->Length()) {
+ nsBoundingMetrics charSize;
+ aMathMLChar->Stretch(aPresContext, aDrawTarget, aFontSizeInflation,
+ NS_STRETCH_DIRECTION_DEFAULT,
+ aMetrics, // size is unimportant as we aren't stretching
+ charSize, NS_STRETCH_NONE, aIsRTL);
+ aMetrics += charSize;
+ }
+}
+
+void
+nsMathMLmfencedFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ aDesiredSize.ClearSize();
+ aDesiredSize.SetBlockStartAscent(0);
+ aDesiredSize.mBoundingMetrics = nsBoundingMetrics();
+
+ int32_t i;
+ const nsStyleFont* font = StyleFont();
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+ nscoord axisHeight, em;
+ GetAxisHeight(aReflowInput.mRenderingContext->GetDrawTarget(), fm, axisHeight);
+ GetEmHeight(fm, em);
+ // leading to be left at the top and the bottom of stretched chars
+ nscoord leading = NSToCoordRound(0.2f * em);
+
+ /////////////
+ // Reflow children
+ // Asking each child to cache its bounding metrics
+
+ // Note that we don't use the base method nsMathMLContainerFrame::Reflow()
+ // because we want to stretch our fences, separators and stretchy frames using
+ // the *same* initial aDesiredSize.mBoundingMetrics. If we were to use the base
+ // method here, our stretchy frames will be stretched and placed, and we may
+ // end up stretching our fences/separators with a different aDesiredSize.
+ // XXX The above decision was revisited in bug 121748 and this code can be
+ // refactored to use nsMathMLContainerFrame::Reflow() at some stage.
+
+ nsReflowStatus childStatus;
+ nsIFrame* firstChild = PrincipalChildList().FirstChild();
+ nsIFrame* childFrame = firstChild;
+ nscoord ascent = 0, descent = 0;
+ if (firstChild || mOpenChar || mCloseChar || mSeparatorsCount > 0) {
+ // We use the ASCII metrics to get our minimum height. This way,
+ // if we have borders or a background, they will fit better with
+ // other elements on the line.
+ ascent = fm->MaxAscent();
+ descent = fm->MaxDescent();
+ }
+ while (childFrame) {
+ ReflowOutput childDesiredSize(aReflowInput,
+ aDesiredSize.mFlags
+ | NS_REFLOW_CALC_BOUNDING_METRICS);
+ WritingMode wm = childFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput childReflowInput(aPresContext, aReflowInput,
+ childFrame, availSize);
+ ReflowChild(childFrame, aPresContext, childDesiredSize,
+ childReflowInput, childStatus);
+ //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status");
+ SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
+ childDesiredSize.mBoundingMetrics);
+
+ mozilla::WritingMode outerWM = aReflowInput.GetWritingMode();
+ nscoord childDescent = childDesiredSize.BSize(outerWM) -
+ childDesiredSize.BlockStartAscent();
+ if (descent < childDescent)
+ descent = childDescent;
+ if (ascent < childDesiredSize.BlockStartAscent())
+ ascent = childDesiredSize.BlockStartAscent();
+
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ /////////////
+ // Ask stretchy children to stretch themselves
+
+ nsBoundingMetrics containerSize;
+ nsStretchDirection stretchDir = NS_STRETCH_DIRECTION_VERTICAL;
+
+ DrawTarget* drawTarget = aReflowInput.mRenderingContext->GetDrawTarget();
+
+ GetPreferredStretchSize(drawTarget,
+ 0, /* i.e., without embellishments */
+ stretchDir, containerSize);
+ childFrame = firstChild;
+ while (childFrame) {
+ nsIMathMLFrame* mathmlChild = do_QueryFrame(childFrame);
+ if (mathmlChild) {
+ ReflowOutput childDesiredSize(aReflowInput);
+ // retrieve the metrics that was stored at the previous pass
+ GetReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
+ childDesiredSize.mBoundingMetrics);
+
+ mathmlChild->Stretch(drawTarget,
+ stretchDir, containerSize, childDesiredSize);
+ // store the updated metrics
+ SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
+ childDesiredSize.mBoundingMetrics);
+
+ nscoord childDescent = childDesiredSize.Height() - childDesiredSize.BlockStartAscent();
+ if (descent < childDescent)
+ descent = childDescent;
+ if (ascent < childDesiredSize.BlockStartAscent())
+ ascent = childDesiredSize.BlockStartAscent();
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ // bug 121748: for surrounding fences & separators, use a size that covers everything
+ GetPreferredStretchSize(drawTarget, STRETCH_CONSIDER_EMBELLISHMENTS,
+ stretchDir, containerSize);
+
+ bool isRTL = StyleVisibility()->mDirection;
+
+ // To achieve a minimum size of "1", the container should be enlarged by the
+ // unstretched metrics of the fences and separators.
+ ApplyUnstretchedMetrics(aPresContext, drawTarget,
+ fontSizeInflation, mOpenChar,
+ containerSize, isRTL);
+ for (i = 0; i < mSeparatorsCount; i++) {
+ ApplyUnstretchedMetrics(aPresContext, drawTarget,
+ fontSizeInflation, &mSeparatorsChar[i],
+ containerSize, isRTL);
+ }
+ ApplyUnstretchedMetrics(aPresContext, drawTarget,
+ fontSizeInflation, mCloseChar,
+ containerSize, isRTL);
+
+ //////////////////////////////////////////
+ // Prepare the opening fence, separators, and closing fence, and
+ // adjust the origin of children.
+
+ // we need to center around the axis
+ nscoord delta = std::max(containerSize.ascent - axisHeight,
+ containerSize.descent + axisHeight);
+ containerSize.ascent = delta + axisHeight;
+ containerSize.descent = delta - axisHeight;
+
+ /////////////////
+ // opening fence ...
+ ReflowChar(aPresContext, drawTarget, *fm,
+ fontSizeInflation, mOpenChar,
+ NS_MATHML_OPERATOR_FORM_PREFIX, font->mScriptLevel,
+ axisHeight, leading, em, containerSize, ascent, descent, isRTL);
+ /////////////////
+ // separators ...
+ for (i = 0; i < mSeparatorsCount; i++) {
+ ReflowChar(aPresContext, drawTarget, *fm,
+ fontSizeInflation, &mSeparatorsChar[i],
+ NS_MATHML_OPERATOR_FORM_INFIX, font->mScriptLevel,
+ axisHeight, leading, em, containerSize, ascent, descent, isRTL);
+ }
+ /////////////////
+ // closing fence ...
+ ReflowChar(aPresContext, drawTarget, *fm,
+ fontSizeInflation, mCloseChar,
+ NS_MATHML_OPERATOR_FORM_POSTFIX, font->mScriptLevel,
+ axisHeight, leading, em, containerSize, ascent, descent, isRTL);
+
+ //////////////////
+ // Adjust the origins of each child.
+ // and update our bounding metrics
+
+ i = 0;
+ nscoord dx = 0;
+ nsBoundingMetrics bm;
+ bool firstTime = true;
+ nsMathMLChar *leftChar, *rightChar;
+ if (isRTL) {
+ leftChar = mCloseChar;
+ rightChar = mOpenChar;
+ } else {
+ leftChar = mOpenChar;
+ rightChar = mCloseChar;
+ }
+
+ if (leftChar) {
+ PlaceChar(leftChar, ascent, bm, dx);
+ aDesiredSize.mBoundingMetrics = bm;
+ firstTime = false;
+ }
+
+ if (isRTL) {
+ childFrame = this->GetChildList(nsIFrame::kPrincipalList).LastChild();
+ } else {
+ childFrame = firstChild;
+ }
+ while (childFrame) {
+ ReflowOutput childSize(aReflowInput);
+ GetReflowAndBoundingMetricsFor(childFrame, childSize, bm);
+ if (firstTime) {
+ firstTime = false;
+ aDesiredSize.mBoundingMetrics = bm;
+ }
+ else
+ aDesiredSize.mBoundingMetrics += bm;
+
+ FinishReflowChild(childFrame, aPresContext, childSize, nullptr,
+ dx, ascent - childSize.BlockStartAscent(), 0);
+ dx += childSize.Width();
+
+ if (i < mSeparatorsCount) {
+ PlaceChar(&mSeparatorsChar[isRTL ? mSeparatorsCount - 1 - i : i],
+ ascent, bm, dx);
+ aDesiredSize.mBoundingMetrics += bm;
+ }
+ i++;
+
+ if (isRTL) {
+ childFrame = childFrame->GetPrevSibling();
+ } else {
+ childFrame = childFrame->GetNextSibling();
+ }
+ }
+
+ if (rightChar) {
+ PlaceChar(rightChar, ascent, bm, dx);
+ if (firstTime)
+ aDesiredSize.mBoundingMetrics = bm;
+ else
+ aDesiredSize.mBoundingMetrics += bm;
+ }
+
+ aDesiredSize.Width() = aDesiredSize.mBoundingMetrics.width;
+ aDesiredSize.Height() = ascent + descent;
+ aDesiredSize.SetBlockStartAscent(ascent);
+
+ SetBoundingMetrics(aDesiredSize.mBoundingMetrics);
+ SetReference(nsPoint(0, aDesiredSize.BlockStartAscent()));
+
+ // see if we should fix the spacing
+ FixInterFrameSpacing(aDesiredSize);
+
+ // Finished with these:
+ ClearSavedChildMetrics();
+
+ // Set our overflow area
+ GatherAndStoreOverflow(&aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+static void
+GetCharSpacing(nsMathMLChar* aMathMLChar,
+ nsOperatorFlags aForm,
+ int32_t aScriptLevel,
+ nscoord em,
+ nscoord& aLeftSpace,
+ nscoord& aRightSpace)
+{
+ nsAutoString data;
+ aMathMLChar->GetData(data);
+ nsOperatorFlags flags = 0;
+ float lspace = 0.0f;
+ float rspace = 0.0f;
+ bool found = nsMathMLOperators::LookupOperator(data, aForm,
+ &flags, &lspace, &rspace);
+
+ // We don't want extra space when we are a script
+ if (found && aScriptLevel > 0) {
+ lspace /= 2.0f;
+ rspace /= 2.0f;
+ }
+
+ aLeftSpace = NSToCoordRound(lspace * em);
+ aRightSpace = NSToCoordRound(rspace * em);
+}
+
+// helper functions to perform the common task of formatting our chars
+/*static*/ nsresult
+nsMathMLmfencedFrame::ReflowChar(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ nsFontMetrics& aFontMetrics,
+ float aFontSizeInflation,
+ nsMathMLChar* aMathMLChar,
+ nsOperatorFlags aForm,
+ int32_t aScriptLevel,
+ nscoord axisHeight,
+ nscoord leading,
+ nscoord em,
+ nsBoundingMetrics& aContainerSize,
+ nscoord& aAscent,
+ nscoord& aDescent,
+ bool aRTL)
+{
+ if (aMathMLChar && 0 < aMathMLChar->Length()) {
+ nscoord leftSpace;
+ nscoord rightSpace;
+ GetCharSpacing(aMathMLChar, aForm, aScriptLevel, em, leftSpace, rightSpace);
+
+ // stretch the char to the appropriate height if it is not big enough.
+ nsBoundingMetrics charSize;
+ nsresult res = aMathMLChar->Stretch(aPresContext, aDrawTarget,
+ aFontSizeInflation,
+ NS_STRETCH_DIRECTION_VERTICAL,
+ aContainerSize, charSize,
+ NS_STRETCH_NORMAL, aRTL);
+
+ if (NS_STRETCH_DIRECTION_UNSUPPORTED != aMathMLChar->GetStretchDirection()) {
+ // has changed... so center the char around the axis
+ nscoord height = charSize.ascent + charSize.descent;
+ charSize.ascent = height/2 + axisHeight;
+ charSize.descent = height - charSize.ascent;
+ }
+ else {
+ // either it hasn't changed or stretching the char failed (i.e.,
+ // nsLayoutUtils::AppUnitBoundsOfString failed)
+ leading = 0;
+ if (NS_FAILED(res)) {
+ nsAutoString data;
+ aMathMLChar->GetData(data);
+ nsBoundingMetrics metrics =
+ nsLayoutUtils::AppUnitBoundsOfString(data.get(), data.Length(),
+ aFontMetrics, aDrawTarget);
+ charSize.ascent = metrics.ascent;
+ charSize.descent = metrics.descent;
+ charSize.width = metrics.width;
+ // Set this as the bounding metrics of the MathMLChar to leave
+ // the necessary room to paint the char.
+ aMathMLChar->SetBoundingMetrics(charSize);
+ }
+ }
+
+ if (aAscent < charSize.ascent + leading)
+ aAscent = charSize.ascent + leading;
+ if (aDescent < charSize.descent + leading)
+ aDescent = charSize.descent + leading;
+
+ // account the spacing
+ charSize.width += leftSpace + rightSpace;
+
+ // x-origin is used to store lspace ...
+ // y-origin is used to stored the ascent ...
+ aMathMLChar->SetRect(nsRect(leftSpace,
+ charSize.ascent, charSize.width,
+ charSize.ascent + charSize.descent));
+ }
+ return NS_OK;
+}
+
+/*static*/ void
+nsMathMLmfencedFrame::PlaceChar(nsMathMLChar* aMathMLChar,
+ nscoord aDesiredAscent,
+ nsBoundingMetrics& bm,
+ nscoord& dx)
+{
+ aMathMLChar->GetBoundingMetrics(bm);
+
+ // the char's x-origin was used to store lspace ...
+ // the char's y-origin was used to store the ascent ...
+ // the char's width was used to store the advance with (with spacing) ...
+ nsRect rect;
+ aMathMLChar->GetRect(rect);
+
+ nscoord dy = aDesiredAscent - rect.y;
+ if (aMathMLChar->GetStretchDirection() != NS_STRETCH_DIRECTION_UNSUPPORTED) {
+ // the stretchy char will be centered around the axis
+ // so we adjust the returned bounding metrics accordingly
+ bm.descent = (bm.ascent + bm.descent) - rect.y;
+ bm.ascent = rect.y;
+ }
+
+ aMathMLChar->SetRect(nsRect(dx + rect.x, dy, bm.width, rect.height));
+
+ bm.leftBearing += rect.x;
+ bm.rightBearing += rect.x;
+
+ // return rect.width since it includes lspace and rspace
+ bm.width = rect.width;
+ dx += rect.width;
+}
+
+static nscoord
+GetMaxCharWidth(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ nsMathMLChar* aMathMLChar,
+ nsOperatorFlags aForm,
+ int32_t aScriptLevel,
+ nscoord em)
+{
+ nscoord width = aMathMLChar->GetMaxWidth(aPresContext, aDrawTarget,
+ aFontSizeInflation);
+
+ if (0 < aMathMLChar->Length()) {
+ nscoord leftSpace;
+ nscoord rightSpace;
+ GetCharSpacing(aMathMLChar, aForm, aScriptLevel, em, leftSpace, rightSpace);
+
+ width += leftSpace + rightSpace;
+ }
+
+ return width;
+}
+
+/* virtual */ void
+nsMathMLmfencedFrame::GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext, ReflowOutput& aDesiredSize)
+{
+ nscoord width = 0;
+
+ nsPresContext* presContext = PresContext();
+ const nsStyleFont* font = StyleFont();
+ float fontSizeInflation = nsLayoutUtils:: FontSizeInflationFor(this);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+ nscoord em;
+ GetEmHeight(fm, em);
+
+ if (mOpenChar) {
+ width +=
+ GetMaxCharWidth(presContext, aRenderingContext->GetDrawTarget(),
+ fontSizeInflation, mOpenChar,
+ NS_MATHML_OPERATOR_FORM_PREFIX, font->mScriptLevel, em);
+ }
+
+ int32_t i = 0;
+ for (nsIFrame* childFrame : PrincipalChildList()) {
+ // XXX This includes margin while Reflow currently doesn't consider
+ // margin, so we may end up with too much space, but, with stretchy
+ // characters, this is an approximation anyway.
+ width += nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame,
+ nsLayoutUtils::PREF_ISIZE);
+
+ if (i < mSeparatorsCount) {
+ width +=
+ GetMaxCharWidth(presContext, aRenderingContext->GetDrawTarget(),
+ fontSizeInflation, &mSeparatorsChar[i],
+ NS_MATHML_OPERATOR_FORM_INFIX, font->mScriptLevel, em);
+ }
+ i++;
+ }
+
+ if (mCloseChar) {
+ width +=
+ GetMaxCharWidth(presContext, aRenderingContext->GetDrawTarget(),
+ fontSizeInflation, mCloseChar,
+ NS_MATHML_OPERATOR_FORM_POSTFIX, font->mScriptLevel, em);
+ }
+
+ aDesiredSize.Width() = width;
+ aDesiredSize.mBoundingMetrics.width = width;
+ aDesiredSize.mBoundingMetrics.leftBearing = 0;
+ aDesiredSize.mBoundingMetrics.rightBearing = width;
+}
+
+nscoord
+nsMathMLmfencedFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize)
+{
+ nscoord gap = nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize);
+ if (!gap) return 0;
+
+ nsRect rect;
+ if (mOpenChar) {
+ mOpenChar->GetRect(rect);
+ rect.MoveBy(gap, 0);
+ mOpenChar->SetRect(rect);
+ }
+ if (mCloseChar) {
+ mCloseChar->GetRect(rect);
+ rect.MoveBy(gap, 0);
+ mCloseChar->SetRect(rect);
+ }
+ for (int32_t i = 0; i < mSeparatorsCount; i++) {
+ mSeparatorsChar[i].GetRect(rect);
+ rect.MoveBy(gap, 0);
+ mSeparatorsChar[i].SetRect(rect);
+ }
+ return gap;
+}
+
+// ----------------------
+// the Style System will use these to pass the proper style context to our MathMLChar
+nsStyleContext*
+nsMathMLmfencedFrame::GetAdditionalStyleContext(int32_t aIndex) const
+{
+ int32_t openIndex = -1;
+ int32_t closeIndex = -1;
+ int32_t lastIndex = mSeparatorsCount-1;
+
+ if (mOpenChar) {
+ lastIndex++;
+ openIndex = lastIndex;
+ }
+ if (mCloseChar) {
+ lastIndex++;
+ closeIndex = lastIndex;
+ }
+ if (aIndex < 0 || aIndex > lastIndex) {
+ return nullptr;
+ }
+
+ if (aIndex < mSeparatorsCount) {
+ return mSeparatorsChar[aIndex].GetStyleContext();
+ }
+ else if (aIndex == openIndex) {
+ return mOpenChar->GetStyleContext();
+ }
+ else if (aIndex == closeIndex) {
+ return mCloseChar->GetStyleContext();
+ }
+ return nullptr;
+}
+
+void
+nsMathMLmfencedFrame::SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext)
+{
+ int32_t openIndex = -1;
+ int32_t closeIndex = -1;
+ int32_t lastIndex = mSeparatorsCount-1;
+
+ if (mOpenChar) {
+ lastIndex++;
+ openIndex = lastIndex;
+ }
+ if (mCloseChar) {
+ lastIndex++;
+ closeIndex = lastIndex;
+ }
+ if (aIndex < 0 || aIndex > lastIndex) {
+ return;
+ }
+
+ if (aIndex < mSeparatorsCount) {
+ mSeparatorsChar[aIndex].SetStyleContext(aStyleContext);
+ }
+ else if (aIndex == openIndex) {
+ mOpenChar->SetStyleContext(aStyleContext);
+ }
+ else if (aIndex == closeIndex) {
+ mCloseChar->SetStyleContext(aStyleContext);
+ }
+}
diff --git a/layout/mathml/nsMathMLmfencedFrame.h b/layout/mathml/nsMathMLmfencedFrame.h
new file mode 100644
index 000000000..2d7535d0c
--- /dev/null
+++ b/layout/mathml/nsMathMLmfencedFrame.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmfencedFrame_h
+#define nsMathMLmfencedFrame_h
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+class nsFontMetrics;
+
+//
+// <mfenced> -- surround content with a pair of fences
+//
+
+class nsMathMLmfencedFrame final : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmfencedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual void
+ SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext) override;
+ virtual nsStyleContext*
+ GetAdditionalStyleContext(int32_t aIndex) const override;
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ virtual void
+ SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void
+ GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ // override the base method because we must keep separators in sync
+ virtual nsresult
+ ChildListChanged(int32_t aModType) override;
+
+ // override the base method so that we can deal with fences and separators
+ virtual nscoord
+ FixInterFrameSpacing(ReflowOutput& aDesiredSize) override;
+
+ // helper routines to format the MathMLChars involved here
+ static nsresult
+ ReflowChar(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ nsFontMetrics& aFontMetrics,
+ float aFontSizeInflation,
+ nsMathMLChar* aMathMLChar,
+ nsOperatorFlags aForm,
+ int32_t aScriptLevel,
+ nscoord axisHeight,
+ nscoord leading,
+ nscoord em,
+ nsBoundingMetrics& aContainerSize,
+ nscoord& aAscent,
+ nscoord& aDescent,
+ bool aRTL);
+
+ static void
+ PlaceChar(nsMathMLChar* aMathMLChar,
+ nscoord aDesiredSize,
+ nsBoundingMetrics& bm,
+ nscoord& dx);
+
+ virtual bool
+ IsMrowLike() override
+ {
+ // Always treated as an mrow with > 1 child as
+ // <mfenced> <mo>%</mo> </mfenced>
+ // renders equivalently to
+ // <mrow> <mo> ( </mo> <mo>%</mo> <mo> ) </mo> </mrow>
+ // This also holds with multiple children. (MathML3 3.3.8.1)
+ return true;
+ }
+
+protected:
+ explicit nsMathMLmfencedFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext) {}
+ virtual ~nsMathMLmfencedFrame();
+
+ nsMathMLChar* mOpenChar;
+ nsMathMLChar* mCloseChar;
+ nsMathMLChar* mSeparatorsChar;
+ int32_t mSeparatorsCount;
+
+ // clean up
+ void
+ RemoveFencesAndSeparators();
+
+ // add fences and separators when all child frames are known
+ void
+ CreateFencesAndSeparators(nsPresContext* aPresContext);
+};
+
+#endif /* nsMathMLmfencedFrame_h */
diff --git a/layout/mathml/nsMathMLmfracFrame.cpp b/layout/mathml/nsMathMLmfracFrame.cpp
new file mode 100644
index 000000000..4e74faea2
--- /dev/null
+++ b/layout/mathml/nsMathMLmfracFrame.cpp
@@ -0,0 +1,663 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsMathMLmfracFrame.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsDisplayList.h"
+#include "gfxContext.h"
+#include "nsMathMLElement.h"
+#include <algorithm>
+#include "gfxMathTable.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+//
+// <mfrac> -- form a fraction from two subexpressions - implementation
+//
+
+// various fraction line thicknesses (multiplicative values of the default rule thickness)
+
+#define THIN_FRACTION_LINE 0.5f
+#define THIN_FRACTION_LINE_MINIMUM_PIXELS 1 // minimum of 1 pixel
+
+#define THICK_FRACTION_LINE 2.0f
+#define THICK_FRACTION_LINE_MINIMUM_PIXELS 2 // minimum of 2 pixels
+
+nsIFrame*
+NS_NewMathMLmfracFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmfracFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfracFrame)
+
+nsMathMLmfracFrame::~nsMathMLmfracFrame()
+{
+}
+
+eMathMLFrameType
+nsMathMLmfracFrame::GetMathMLFrameType()
+{
+ // frac is "inner" in TeXBook, Appendix G, rule 15e. See also page 170.
+ return eMathMLFrameType_Inner;
+}
+
+uint8_t
+nsMathMLmfracFrame::ScriptIncrement(nsIFrame* aFrame)
+{
+ if (!StyleFont()->mMathDisplay &&
+ aFrame && (mFrames.FirstChild() == aFrame ||
+ mFrames.LastChild() == aFrame)) {
+ return 1;
+ }
+ return 0;
+}
+
+NS_IMETHODIMP
+nsMathMLmfracFrame::TransmitAutomaticData()
+{
+ // The TeXbook (Ch 17. p.141) says the numerator inherits the compression
+ // while the denominator is compressed
+ UpdatePresentationDataFromChildAt(1, 1,
+ NS_MATHML_COMPRESSED,
+ NS_MATHML_COMPRESSED);
+
+ // If displaystyle is false, then scriptlevel is incremented, so notify the
+ // children of this.
+ if (!StyleFont()->mMathDisplay) {
+ PropagateFrameFlagFor(mFrames.FirstChild(),
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ PropagateFrameFlagFor(mFrames.LastChild(),
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+
+ // if our numerator is an embellished operator, let its state bubble to us
+ GetEmbellishDataFrom(mFrames.FirstChild(), mEmbellishData);
+ if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) {
+ // even when embellished, we need to record that <mfrac> won't fire
+ // Stretch() on its embellished child
+ mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ }
+
+ return NS_OK;
+}
+
+nscoord
+nsMathMLmfracFrame::CalcLineThickness(nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ nsString& aThicknessAttribute,
+ nscoord onePixel,
+ nscoord aDefaultRuleThickness,
+ float aFontSizeInflation)
+{
+ nscoord defaultThickness = aDefaultRuleThickness;
+ nscoord lineThickness = aDefaultRuleThickness;
+ nscoord minimumThickness = onePixel;
+
+ // linethickness
+ //
+ // "Specifies the thickness of the horizontal 'fraction bar', or 'rule'. The
+ // default value is 'medium', 'thin' is thinner, but visible, 'thick' is
+ // thicker; the exact thickness of these is left up to the rendering agent."
+ //
+ // values: length | "thin" | "medium" | "thick"
+ // default: medium
+ //
+ if (!aThicknessAttribute.IsEmpty()) {
+ if (aThicknessAttribute.EqualsLiteral("thin")) {
+ lineThickness = NSToCoordFloor(defaultThickness * THIN_FRACTION_LINE);
+ minimumThickness = onePixel * THIN_FRACTION_LINE_MINIMUM_PIXELS;
+ // should visually decrease by at least one pixel, if default is not a pixel
+ if (defaultThickness > onePixel && lineThickness > defaultThickness - onePixel)
+ lineThickness = defaultThickness - onePixel;
+ }
+ else if (aThicknessAttribute.EqualsLiteral("medium")) {
+ // medium is default
+ }
+ else if (aThicknessAttribute.EqualsLiteral("thick")) {
+ lineThickness = NSToCoordCeil(defaultThickness * THICK_FRACTION_LINE);
+ minimumThickness = onePixel * THICK_FRACTION_LINE_MINIMUM_PIXELS;
+ // should visually increase by at least one pixel
+ if (lineThickness < defaultThickness + onePixel)
+ lineThickness = defaultThickness + onePixel;
+ }
+ else {
+ // length value
+ lineThickness = defaultThickness;
+ ParseNumericValue(aThicknessAttribute, &lineThickness,
+ nsMathMLElement::PARSE_ALLOW_UNITLESS,
+ aPresContext, aStyleContext, aFontSizeInflation);
+ }
+ }
+
+ // use minimum if the lineThickness is a non-zero value less than minimun
+ if (lineThickness && lineThickness < minimumThickness)
+ lineThickness = minimumThickness;
+
+ return lineThickness;
+}
+
+void
+nsMathMLmfracFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ /////////////
+ // paint the numerator and denominator
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ /////////////
+ // paint the fraction line
+ if (mIsBevelled) {
+ DisplaySlash(aBuilder, this, mLineRect, mLineThickness, aLists);
+ } else {
+ DisplayBar(aBuilder, this, mLineRect, aLists);
+ }
+}
+
+
+nsresult
+nsMathMLmfracFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (nsGkAtoms::linethickness_ == aAttribute) {
+ InvalidateFrame();
+ }
+ return
+ nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+/* virtual */ nsresult
+nsMathMLmfracFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize)
+{
+ return PlaceInternal(aDrawTarget, false, aDesiredSize, true);
+}
+
+nscoord
+nsMathMLmfracFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize)
+{
+ nscoord gap = nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize);
+ if (!gap) return 0;
+
+ mLineRect.MoveBy(gap, 0);
+ return gap;
+}
+
+/* virtual */ nsresult
+nsMathMLmfracFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ return PlaceInternal(aDrawTarget, aPlaceOrigin, aDesiredSize, false);
+}
+
+nsresult
+nsMathMLmfracFrame::PlaceInternal(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize,
+ bool aWidthOnly)
+{
+ ////////////////////////////////////
+ // Get the children's desired sizes
+ nsBoundingMetrics bmNum, bmDen;
+ ReflowOutput sizeNum(aDesiredSize.GetWritingMode());
+ ReflowOutput sizeDen(aDesiredSize.GetWritingMode());
+ nsIFrame* frameDen = nullptr;
+ nsIFrame* frameNum = mFrames.FirstChild();
+ if (frameNum)
+ frameDen = frameNum->GetNextSibling();
+ if (!frameNum || !frameDen || frameDen->GetNextSibling()) {
+ // report an error, encourage people to get their markups in order
+ if (aPlaceOrigin) {
+ ReportChildCountError();
+ }
+ return ReflowError(aDrawTarget, aDesiredSize);
+ }
+ GetReflowAndBoundingMetricsFor(frameNum, sizeNum, bmNum);
+ GetReflowAndBoundingMetricsFor(frameDen, sizeDen, bmDen);
+
+ nsPresContext* presContext = PresContext();
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+
+ nscoord defaultRuleThickness, axisHeight;
+ nscoord oneDevPixel = fm->AppUnitsPerDevPixel();
+ gfxFont* mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
+ if (mathFont) {
+ defaultRuleThickness = mathFont->MathTable()->
+ Constant(gfxMathTable::FractionRuleThickness, oneDevPixel);
+ } else {
+ GetRuleThickness(aDrawTarget, fm, defaultRuleThickness);
+ }
+ GetAxisHeight(aDrawTarget, fm, axisHeight);
+
+ bool outermostEmbellished = false;
+ if (mEmbellishData.coreFrame) {
+ nsEmbellishData parentData;
+ GetEmbellishDataFrom(GetParent(), parentData);
+ outermostEmbellished = parentData.coreFrame != mEmbellishData.coreFrame;
+ }
+
+ // see if the linethickness attribute is there
+ nsAutoString value;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::linethickness_, value);
+ mLineThickness = CalcLineThickness(presContext, mStyleContext, value,
+ onePixel, defaultRuleThickness,
+ fontSizeInflation);
+
+ // bevelled attribute
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::bevelled_, value);
+ mIsBevelled = value.EqualsLiteral("true");
+
+ bool displayStyle = StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_BLOCK;
+
+ if (!mIsBevelled) {
+ mLineRect.height = mLineThickness;
+
+ // by default, leave at least one-pixel padding at either end, and add
+ // lspace & rspace that may come from <mo> if we are an outermost
+ // embellished container (we fetch values from the core since they may use
+ // units that depend on style data, and style changes could have occurred
+ // in the core since our last visit there)
+ nscoord leftSpace = onePixel;
+ nscoord rightSpace = onePixel;
+ if (outermostEmbellished) {
+ nsEmbellishData coreData;
+ GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
+ leftSpace += StyleVisibility()->mDirection ?
+ coreData.trailingSpace : coreData.leadingSpace;
+ rightSpace += StyleVisibility()->mDirection ?
+ coreData.leadingSpace : coreData.trailingSpace;
+ }
+
+ nscoord actualRuleThickness = mLineThickness;
+
+ //////////////////
+ // Get shifts
+ nscoord numShift = 0;
+ nscoord denShift = 0;
+
+ // Rule 15b, App. G, TeXbook
+ nscoord numShift1, numShift2, numShift3;
+ nscoord denShift1, denShift2;
+
+ GetNumeratorShifts(fm, numShift1, numShift2, numShift3);
+ GetDenominatorShifts(fm, denShift1, denShift2);
+
+ if (0 == actualRuleThickness) {
+ numShift = displayStyle ? numShift1 : numShift3;
+ denShift = displayStyle ? denShift1 : denShift2;
+ if (mathFont) {
+ numShift = mathFont->
+ MathTable()->Constant(displayStyle ?
+ gfxMathTable::StackTopDisplayStyleShiftUp :
+ gfxMathTable::StackTopShiftUp,
+ oneDevPixel);
+ denShift = mathFont->
+ MathTable()->Constant(displayStyle ?
+ gfxMathTable::StackBottomDisplayStyleShiftDown :
+ gfxMathTable::StackBottomShiftDown,
+ oneDevPixel);
+ }
+ } else {
+ numShift = displayStyle ? numShift1 : numShift2;
+ denShift = displayStyle ? denShift1 : denShift2;
+ if (mathFont) {
+ numShift = mathFont->MathTable()->
+ Constant(displayStyle ?
+ gfxMathTable::FractionNumeratorDisplayStyleShiftUp :
+ gfxMathTable::FractionNumeratorShiftUp,
+ oneDevPixel);
+ denShift = mathFont->MathTable()->
+ Constant(displayStyle ?
+ gfxMathTable::FractionDenominatorDisplayStyleShiftDown :
+ gfxMathTable::FractionDenominatorShiftDown,
+ oneDevPixel);
+ }
+ }
+
+ if (0 == actualRuleThickness) {
+ // Rule 15c, App. G, TeXbook
+
+ // min clearance between numerator and denominator
+ nscoord minClearance = displayStyle ?
+ 7 * defaultRuleThickness : 3 * defaultRuleThickness;
+ if (mathFont) {
+ minClearance = mathFont->MathTable()->
+ Constant(displayStyle ?
+ gfxMathTable::StackDisplayStyleGapMin :
+ gfxMathTable::StackGapMin,
+ oneDevPixel);
+ }
+ // Factor in axis height
+ // http://www.mathml-association.org/MathMLinHTML5/S3.html#SS3.SSS2
+ numShift += axisHeight;
+ denShift += axisHeight;
+
+ nscoord actualClearance =
+ (numShift - bmNum.descent) - (bmDen.ascent - denShift);
+ // actualClearance should be >= minClearance
+ if (actualClearance < minClearance) {
+ nscoord halfGap = (minClearance - actualClearance)/2;
+ numShift += halfGap;
+ denShift += halfGap;
+ }
+ }
+ else {
+ // Rule 15d, App. G, TeXbook
+
+ // min clearance between numerator or denominator and middle of bar
+
+ // TeX has a different interpretation of the thickness.
+ // Try $a \above10pt b$ to see. Here is what TeX does:
+ // minClearance = displayStyle ?
+ // 3 * actualRuleThickness : actualRuleThickness;
+
+ // we slightly depart from TeX here. We use the defaultRuleThickness instead
+ // of the value coming from the linethickness attribute, i.e., we recover what
+ // TeX does if the user hasn't set linethickness. But when the linethickness
+ // is set, we avoid the wide gap problem.
+ nscoord minClearanceNum = displayStyle ?
+ 3 * defaultRuleThickness : defaultRuleThickness + onePixel;
+ nscoord minClearanceDen = minClearanceNum;
+ if (mathFont) {
+ minClearanceNum = mathFont->
+ MathTable()->Constant(displayStyle ?
+ gfxMathTable::FractionNumDisplayStyleGapMin :
+ gfxMathTable::FractionNumeratorGapMin,
+ oneDevPixel);
+ minClearanceDen = mathFont->
+ MathTable()->Constant(displayStyle ?
+ gfxMathTable::FractionDenomDisplayStyleGapMin :
+ gfxMathTable::FractionDenominatorGapMin,
+ oneDevPixel);
+ }
+
+ // adjust numShift to maintain minClearanceNum if needed
+ nscoord actualClearanceNum =
+ (numShift - bmNum.descent) - (axisHeight + actualRuleThickness/2);
+ if (actualClearanceNum < minClearanceNum) {
+ numShift += (minClearanceNum - actualClearanceNum);
+ }
+ // adjust denShift to maintain minClearanceDen if needed
+ nscoord actualClearanceDen =
+ (axisHeight - actualRuleThickness/2) - (bmDen.ascent - denShift);
+ if (actualClearanceDen < minClearanceDen) {
+ denShift += (minClearanceDen - actualClearanceDen);
+ }
+ }
+
+ //////////////////
+ // Place Children
+
+ // XXX Need revisiting the width. TeX uses the exact width
+ // e.g. in $$\huge\frac{\displaystyle\int}{i}$$
+ nscoord width = std::max(bmNum.width, bmDen.width);
+ nscoord dxNum = leftSpace + (width - sizeNum.Width())/2;
+ nscoord dxDen = leftSpace + (width - sizeDen.Width())/2;
+ width += leftSpace + rightSpace;
+
+ // see if the numalign attribute is there
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::numalign_, value);
+ if (value.EqualsLiteral("left"))
+ dxNum = leftSpace;
+ else if (value.EqualsLiteral("right"))
+ dxNum = width - rightSpace - sizeNum.Width();
+
+ // see if the denomalign attribute is there
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::denomalign_, value);
+ if (value.EqualsLiteral("left"))
+ dxDen = leftSpace;
+ else if (value.EqualsLiteral("right"))
+ dxDen = width - rightSpace - sizeDen.Width();
+
+ mBoundingMetrics.rightBearing =
+ std::max(dxNum + bmNum.rightBearing, dxDen + bmDen.rightBearing);
+ if (mBoundingMetrics.rightBearing < width - rightSpace)
+ mBoundingMetrics.rightBearing = width - rightSpace;
+ mBoundingMetrics.leftBearing =
+ std::min(dxNum + bmNum.leftBearing, dxDen + bmDen.leftBearing);
+ if (mBoundingMetrics.leftBearing > leftSpace)
+ mBoundingMetrics.leftBearing = leftSpace;
+ mBoundingMetrics.ascent = bmNum.ascent + numShift;
+ mBoundingMetrics.descent = bmDen.descent + denShift;
+ mBoundingMetrics.width = width;
+
+ aDesiredSize.SetBlockStartAscent(sizeNum.BlockStartAscent() + numShift);
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() +
+ sizeDen.Height() - sizeDen.BlockStartAscent() + denShift;
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ if (aPlaceOrigin) {
+ nscoord dy;
+ // place numerator
+ dy = 0;
+ FinishReflowChild(frameNum, presContext, sizeNum, nullptr, dxNum, dy, 0);
+ // place denominator
+ dy = aDesiredSize.Height() - sizeDen.Height();
+ FinishReflowChild(frameDen, presContext, sizeDen, nullptr, dxDen, dy, 0);
+ // place the fraction bar - dy is top of bar
+ dy = aDesiredSize.BlockStartAscent() - (axisHeight + actualRuleThickness/2);
+ mLineRect.SetRect(leftSpace, dy, width - (leftSpace + rightSpace),
+ actualRuleThickness);
+ }
+ } else {
+ nscoord numShift = 0.0;
+ nscoord denShift = 0.0;
+ nscoord padding = 3 * defaultRuleThickness;
+ nscoord slashRatio = 3;
+
+ // Define the constant used in the expression of the maximum width
+ nscoord em = fm->EmHeight();
+ nscoord slashMaxWidthConstant = 2 * em;
+
+ // For large line thicknesses the minimum slash height is limited to the
+ // largest expected height of a fraction
+ nscoord slashMinHeight = slashRatio *
+ std::min(2 * mLineThickness, slashMaxWidthConstant);
+
+ nscoord leadingSpace = padding;
+ nscoord trailingSpace = padding;
+ if (outermostEmbellished) {
+ nsEmbellishData coreData;
+ GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
+ leadingSpace += coreData.leadingSpace;
+ trailingSpace += coreData.trailingSpace;
+ }
+ nscoord delta;
+
+ // ___________
+ // | | /
+ // {|-NUMERATOR-| /
+ // {|___________| S
+ // { L
+ // numShift{ A
+ // ------------------------------------------------------- baseline
+ // S _____________ } denShift
+ // H | |}
+ // / |-DENOMINATOR-|}
+ // / |_____________|
+ //
+
+ // first, ensure that the top of the numerator is at least as high as the
+ // top of the denominator (and the reverse for the bottoms)
+ delta = std::max(bmDen.ascent - bmNum.ascent,
+ bmNum.descent - bmDen.descent) / 2;
+ if (delta > 0) {
+ numShift += delta;
+ denShift += delta;
+ }
+
+ if (StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_BLOCK) {
+ delta = std::min(bmDen.ascent + bmDen.descent,
+ bmNum.ascent + bmNum.descent) / 2;
+ numShift += delta;
+ denShift += delta;
+ } else {
+ nscoord xHeight = fm->XHeight();
+ numShift += xHeight / 2;
+ denShift += xHeight / 4;
+ }
+
+ // Set the ascent/descent of our BoundingMetrics.
+ mBoundingMetrics.ascent = bmNum.ascent + numShift;
+ mBoundingMetrics.descent = bmDen.descent + denShift;
+
+ // At this point the height of the slash is
+ // mBoundingMetrics.ascent + mBoundingMetrics.descent
+ // Ensure that it is greater than slashMinHeight
+ delta = (slashMinHeight -
+ (mBoundingMetrics.ascent + mBoundingMetrics.descent)) / 2;
+ if (delta > 0) {
+ mBoundingMetrics.ascent += delta;
+ mBoundingMetrics.descent += delta;
+ }
+
+ // Set the width of the slash
+ if (aWidthOnly) {
+ mLineRect.width = mLineThickness + slashMaxWidthConstant;
+ } else {
+ mLineRect.width = mLineThickness +
+ std::min(slashMaxWidthConstant,
+ (mBoundingMetrics.ascent + mBoundingMetrics.descent) /
+ slashRatio);
+ }
+
+ // Set horizontal bounding metrics
+ if (StyleVisibility()->mDirection) {
+ mBoundingMetrics.leftBearing = trailingSpace + bmDen.leftBearing;
+ mBoundingMetrics.rightBearing = trailingSpace + bmDen.width + mLineRect.width + bmNum.rightBearing;
+ } else {
+ mBoundingMetrics.leftBearing = leadingSpace + bmNum.leftBearing;
+ mBoundingMetrics.rightBearing = leadingSpace + bmNum.width + mLineRect.width + bmDen.rightBearing;
+ }
+ mBoundingMetrics.width =
+ leadingSpace + bmNum.width + mLineRect.width + bmDen.width +
+ trailingSpace;
+
+ // Set aDesiredSize
+ aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + padding);
+ aDesiredSize.Height() =
+ mBoundingMetrics.ascent + mBoundingMetrics.descent + 2 * padding;
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ if (aPlaceOrigin) {
+ nscoord dx, dy;
+
+ // place numerator
+ dx = MirrorIfRTL(aDesiredSize.Width(), sizeNum.Width(),
+ leadingSpace);
+ dy = aDesiredSize.BlockStartAscent() - numShift - sizeNum.BlockStartAscent();
+ FinishReflowChild(frameNum, presContext, sizeNum, nullptr, dx, dy, 0);
+
+ // place the fraction bar
+ dx = MirrorIfRTL(aDesiredSize.Width(), mLineRect.width,
+ leadingSpace + bmNum.width);
+ dy = aDesiredSize.BlockStartAscent() - mBoundingMetrics.ascent;
+ mLineRect.SetRect(dx, dy,
+ mLineRect.width, aDesiredSize.Height() - 2 * padding);
+
+ // place denominator
+ dx = MirrorIfRTL(aDesiredSize.Width(), sizeDen.Width(),
+ leadingSpace + bmNum.width + mLineRect.width);
+ dy = aDesiredSize.BlockStartAscent() + denShift - sizeDen.BlockStartAscent();
+ FinishReflowChild(frameDen, presContext, sizeDen, nullptr, dx, dy, 0);
+ }
+
+ }
+
+ return NS_OK;
+}
+
+class nsDisplayMathMLSlash : public nsDisplayItem {
+public:
+ nsDisplayMathMLSlash(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect,
+ nscoord aThickness, bool aRTL)
+ : nsDisplayItem(aBuilder, aFrame), mRect(aRect), mThickness(aThickness),
+ mRTL(aRTL) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLSlash);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayMathMLSlash() {
+ MOZ_COUNT_DTOR(nsDisplayMathMLSlash);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLSlash", TYPE_MATHML_SLASH)
+
+private:
+ nsRect mRect;
+ nscoord mThickness;
+ bool mRTL;
+};
+
+void nsDisplayMathMLSlash::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
+
+ // get the gfxRect
+ nsPresContext* presContext = mFrame->PresContext();
+ Rect rect = NSRectToRect(mRect + ToReferenceFrame(),
+ presContext->AppUnitsPerDevPixel());
+
+ ColorPattern color(ToDeviceColor(
+ mFrame->GetVisitedDependentColor(eCSSProperty__webkit_text_fill_color)));
+
+ // draw the slash as a parallelogram
+ Point delta = Point(presContext->AppUnitsToGfxUnits(mThickness), 0);
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ if (mRTL) {
+ builder->MoveTo(rect.TopLeft());
+ builder->LineTo(rect.TopLeft() + delta);
+ builder->LineTo(rect.BottomRight());
+ builder->LineTo(rect.BottomRight() - delta);
+ } else {
+ builder->MoveTo(rect.BottomLeft());
+ builder->LineTo(rect.BottomLeft() + delta);
+ builder->LineTo(rect.TopRight());
+ builder->LineTo(rect.TopRight() - delta);
+ }
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget.Fill(path, color);
+}
+
+void
+nsMathMLmfracFrame::DisplaySlash(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect,
+ nscoord aThickness,
+ const nsDisplayListSet& aLists) {
+ if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty())
+ return;
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayMathMLSlash(aBuilder, aFrame, aRect, aThickness,
+ StyleVisibility()->mDirection));
+}
diff --git a/layout/mathml/nsMathMLmfracFrame.h b/layout/mathml/nsMathMLmfracFrame.h
new file mode 100644
index 000000000..330a31180
--- /dev/null
+++ b/layout/mathml/nsMathMLmfracFrame.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmfracFrame_h___
+#define nsMathMLmfracFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+//
+// <mfrac> -- form a fraction from two subexpressions
+//
+
+/*
+The MathML REC describes:
+
+The <mfrac> element is used for fractions. It can also be used to mark up
+fraction-like objects such as binomial coefficients and Legendre symbols.
+The syntax for <mfrac> is:
+ <mfrac> numerator denominator </mfrac>
+
+Attributes of <mfrac>:
+ Name values default
+ linethickness number [ v-unit ] | thin | medium | thick 1
+
+E.g.,
+linethickness=2 actually means that linethickness=2*DEFAULT_THICKNESS
+(DEFAULT_THICKNESS is not specified by MathML, see below.)
+
+The linethickness attribute indicates the thickness of the horizontal
+"fraction bar", or "rule", typically used to render fractions. A fraction
+with linethickness="0" renders without the bar, and might be used within
+binomial coefficients. A linethickness greater than one might be used with
+nested fractions.
+
+In general, the value of linethickness can be a number, as a multiplier
+of the default thickness of the fraction bar (the default thickness is
+not specified by MathML), or a number with a unit of vertical length (see
+Section 2.3.3), or one of the keywords medium (same as 1), thin (thinner
+than 1, otherwise up to the renderer), or thick (thicker than 1, otherwise
+up to the renderer).
+
+The <mfrac> element sets displaystyle to "false", or if it was already
+false increments scriptlevel by 1, within numerator and denominator.
+These attributes are inherited by every element from its rendering
+environment, but can be set explicitly only on the <mstyle>
+element.
+*/
+
+class nsMathMLmfracFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmfracFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual eMathMLFrameType GetMathMLFrameType() override;
+
+ virtual nsresult
+ MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ // override the base method so that we can deal with the fraction line
+ virtual nscoord
+ FixInterFrameSpacing(ReflowOutput& aDesiredSize) override;
+
+ // helper to translate the thickness attribute into a usable form
+ static nscoord
+ CalcLineThickness(nsPresContext* aPresContext,
+ nsStyleContext* aStyleContext,
+ nsString& aThicknessAttribute,
+ nscoord onePixel,
+ nscoord aDefaultRuleThickness,
+ float aFontSizeInflation);
+
+ uint8_t
+ ScriptIncrement(nsIFrame* aFrame) override;
+
+protected:
+ explicit nsMathMLmfracFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext) {}
+ virtual ~nsMathMLmfracFrame();
+
+ nsresult PlaceInternal(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize,
+ bool aWidthOnly);
+
+ // Display a slash
+ void DisplaySlash(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect,
+ nscoord aThickness,
+ const nsDisplayListSet& aLists);
+
+ nsRect mLineRect;
+ nsMathMLChar* mSlashChar;
+ nscoord mLineThickness;
+ bool mIsBevelled;
+};
+
+#endif /* nsMathMLmfracFrame_h___ */
diff --git a/layout/mathml/nsMathMLmmultiscriptsFrame.cpp b/layout/mathml/nsMathMLmmultiscriptsFrame.cpp
new file mode 100644
index 000000000..3504bdafd
--- /dev/null
+++ b/layout/mathml/nsMathMLmmultiscriptsFrame.cpp
@@ -0,0 +1,704 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsMathMLmmultiscriptsFrame.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include <algorithm>
+#include "gfxMathTable.h"
+
+using mozilla::WritingMode;
+
+//
+// <mmultiscripts> -- attach prescripts and tensor indices to a base - implementation
+// <msub> -- attach a subscript to a base - implementation
+// <msubsup> -- attach a subscript-superscript pair to a base - implementation
+// <msup> -- attach a superscript to a base - implementation
+//
+
+nsIFrame*
+NS_NewMathMLmmultiscriptsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmmultiscriptsFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmmultiscriptsFrame)
+
+nsMathMLmmultiscriptsFrame::~nsMathMLmmultiscriptsFrame()
+{
+}
+
+uint8_t
+nsMathMLmmultiscriptsFrame::ScriptIncrement(nsIFrame* aFrame)
+{
+ if (!aFrame)
+ return 0;
+ if (mFrames.ContainsFrame(aFrame)) {
+ if (mFrames.FirstChild() == aFrame ||
+ aFrame->GetContent()->IsMathMLElement(nsGkAtoms::mprescripts_)) {
+ return 0; // No script increment for base frames or prescript markers
+ }
+ return 1;
+ }
+ return 0; //not a child
+}
+
+NS_IMETHODIMP
+nsMathMLmmultiscriptsFrame::TransmitAutomaticData()
+{
+ // if our base is an embellished operator, let its state bubble to us
+ mPresentationData.baseFrame = mFrames.FirstChild();
+ GetEmbellishDataFrom(mPresentationData.baseFrame, mEmbellishData);
+
+ // The TeXbook (Ch 17. p.141) says the superscript inherits the compression
+ // while the subscript is compressed. So here we collect subscripts and set
+ // the compression flag in them.
+
+ int32_t count = 0;
+ bool isSubScript = !mContent->IsMathMLElement(nsGkAtoms::msup_);
+
+ AutoTArray<nsIFrame*, 8> subScriptFrames;
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ if (childFrame->GetContent()->IsMathMLElement(nsGkAtoms::mprescripts_)) {
+ // mprescripts frame
+ } else if (0 == count) {
+ // base frame
+ } else {
+ // super/subscript block
+ if (isSubScript) {
+ // subscript
+ subScriptFrames.AppendElement(childFrame);
+ } else {
+ // superscript
+ }
+ PropagateFrameFlagFor(childFrame, NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ isSubScript = !isSubScript;
+ }
+ count++;
+ childFrame = childFrame->GetNextSibling();
+ }
+ for (int32_t i = subScriptFrames.Length() - 1; i >= 0; i--) {
+ childFrame = subScriptFrames[i];
+ PropagatePresentationDataFor(childFrame,
+ NS_MATHML_COMPRESSED, NS_MATHML_COMPRESSED);
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsresult
+nsMathMLmmultiscriptsFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ nscoord subScriptShift = 0;
+ nscoord supScriptShift = 0;
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+
+ // subscriptshift
+ //
+ // "Specifies the minimum amount to shift the baseline of subscript down; the
+ // default is for the rendering agent to use its own positioning rules."
+ //
+ // values: length
+ // default: automatic
+ //
+ // We use 0 as the default value so unitless values can be ignored.
+ // As a minimum, negative values can be ignored.
+ //
+ nsAutoString value;
+ if (!mContent->IsMathMLElement(nsGkAtoms::msup_)) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::subscriptshift_, value);
+ if (!value.IsEmpty()) {
+ ParseNumericValue(value, &subScriptShift, 0, PresContext(),
+ mStyleContext, fontSizeInflation);
+ }
+ }
+ // superscriptshift
+ //
+ // "Specifies the minimum amount to shift the baseline of superscript up; the
+ // default is for the rendering agent to use its own positioning rules."
+ //
+ // values: length
+ // default: automatic
+ //
+ // We use 0 as the default value so unitless values can be ignored.
+ // As a minimum, negative values can be ignored.
+ //
+ if (!mContent->IsMathMLElement(nsGkAtoms::msub_)) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::superscriptshift_, value);
+ if (!value.IsEmpty()) {
+ ParseNumericValue(value, &supScriptShift, 0, PresContext(),
+ mStyleContext, fontSizeInflation);
+ }
+ }
+ return PlaceMultiScript(PresContext(), aDrawTarget, aPlaceOrigin,
+ aDesiredSize, this, subScriptShift, supScriptShift,
+ fontSizeInflation);
+}
+
+// exported routine that both munderover and mmultiscripts share.
+// munderover uses this when movablelimits is set.
+nsresult
+nsMathMLmmultiscriptsFrame::PlaceMultiScript(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize,
+ nsMathMLContainerFrame* aFrame,
+ nscoord aUserSubScriptShift,
+ nscoord aUserSupScriptShift,
+ float aFontSizeInflation)
+{
+ nsIAtom* tag = aFrame->GetContent()->NodeInfo()->NameAtom();
+
+ // This function deals with both munderover etc. as well as msubsup etc.
+ // As the former behaves identically to the later, we treat it as such
+ // to avoid additional checks later.
+ if (aFrame->GetContent()->IsMathMLElement(nsGkAtoms::mover_))
+ tag = nsGkAtoms::msup_;
+ else if (aFrame->GetContent()->IsMathMLElement(nsGkAtoms::munder_))
+ tag = nsGkAtoms::msub_;
+ else if (aFrame->GetContent()->IsMathMLElement(nsGkAtoms::munderover_))
+ tag = nsGkAtoms::msubsup_;
+
+ nsBoundingMetrics bmFrame;
+
+ nscoord minShiftFromXHeight, subDrop, supDrop;
+
+ ////////////////////////////////////////
+ // Initialize super/sub shifts that
+ // depend only on the current font
+ ////////////////////////////////////////
+
+ nsIFrame* baseFrame = aFrame->PrincipalChildList().FirstChild();
+
+ if (!baseFrame) {
+ if (tag == nsGkAtoms::mmultiscripts_)
+ aFrame->ReportErrorToConsole("NoBase");
+ else
+ aFrame->ReportChildCountError();
+ return aFrame->ReflowError(aDrawTarget, aDesiredSize);
+ }
+
+ // get x-height (an ex)
+ const nsStyleFont* font = aFrame->StyleFont();
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(baseFrame, aFontSizeInflation);
+
+ nscoord xHeight = fm->XHeight();
+
+ nscoord oneDevPixel = fm->AppUnitsPerDevPixel();
+ gfxFont* mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
+ // scriptspace from TeX for extra spacing after sup/subscript
+ nscoord scriptSpace;
+ if (mathFont) {
+ scriptSpace = mathFont->MathTable()->
+ Constant(gfxMathTable::SpaceAfterScript, oneDevPixel);
+ } else {
+ // (0.5pt in plain TeX)
+ scriptSpace = nsPresContext::CSSPointsToAppUnits(0.5f);
+ }
+
+ // Try and read sub and sup drops from the MATH table.
+ if (mathFont) {
+ subDrop = mathFont->MathTable()->
+ Constant(gfxMathTable::SubscriptBaselineDropMin, oneDevPixel);
+ supDrop = mathFont->MathTable()->
+ Constant(gfxMathTable::SuperscriptBaselineDropMax, oneDevPixel);
+ }
+
+ // force the scriptSpace to be at least 1 pixel
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+ scriptSpace = std::max(onePixel, scriptSpace);
+
+ /////////////////////////////////////
+ // first the shift for the subscript
+
+ nscoord subScriptShift;
+ if (mathFont) {
+ // Try and get the sub script shift from the MATH table. Note that contrary
+ // to TeX we only have one parameter.
+ subScriptShift = mathFont->MathTable()->
+ Constant(gfxMathTable::SubscriptShiftDown, oneDevPixel);
+ } else {
+ // subScriptShift{1,2}
+ // = minimum amount to shift the subscript down
+ // = sub{1,2} in TeXbook
+ // subScriptShift1 = subscriptshift attribute * x-height
+ nscoord subScriptShift1, subScriptShift2;
+ // Get subScriptShift{1,2} default from font
+ GetSubScriptShifts (fm, subScriptShift1, subScriptShift2);
+ if (tag == nsGkAtoms::msub_) {
+ subScriptShift = subScriptShift1;
+ } else {
+ subScriptShift = std::max(subScriptShift1, subScriptShift2);
+ }
+ }
+
+ if (0 < aUserSubScriptShift) {
+ // the user has set the subscriptshift attribute
+ subScriptShift = std::max(subScriptShift, aUserSubScriptShift);
+ }
+
+ /////////////////////////////////////
+ // next the shift for the superscript
+
+ nscoord supScriptShift;
+ nsPresentationData presentationData;
+ aFrame->GetPresentationData(presentationData);
+ if (mathFont) {
+ // Try and get the super script shift from the MATH table. Note that
+ // contrary to TeX we only have two parameters.
+ supScriptShift = mathFont->
+ MathTable()->Constant(NS_MATHML_IS_COMPRESSED(presentationData.flags) ?
+ gfxMathTable::SuperscriptShiftUpCramped :
+ gfxMathTable::SuperscriptShiftUp,
+ oneDevPixel);
+ } else {
+ // supScriptShift{1,2,3}
+ // = minimum amount to shift the supscript up
+ // = sup{1,2,3} in TeX
+ // supScriptShift1 = superscriptshift attribute * x-height
+ // Note that there are THREE values for supscript shifts depending
+ // on the current style
+ nscoord supScriptShift1, supScriptShift2, supScriptShift3;
+ // Set supScriptShift{1,2,3} default from font
+ GetSupScriptShifts (fm, supScriptShift1, supScriptShift2, supScriptShift3);
+
+ // get sup script shift depending on current script level and display style
+ // Rule 18c, App. G, TeXbook
+ if (font->mScriptLevel == 0 &&
+ font->mMathDisplay == NS_MATHML_DISPLAYSTYLE_BLOCK &&
+ !NS_MATHML_IS_COMPRESSED(presentationData.flags)) {
+ // Style D in TeXbook
+ supScriptShift = supScriptShift1;
+ } else if (NS_MATHML_IS_COMPRESSED(presentationData.flags)) {
+ // Style C' in TeXbook = D',T',S',SS'
+ supScriptShift = supScriptShift3;
+ } else {
+ // everything else = T,S,SS
+ supScriptShift = supScriptShift2;
+ }
+ }
+
+ if (0 < aUserSupScriptShift) {
+ // the user has set the supscriptshift attribute
+ supScriptShift = std::max(supScriptShift, aUserSupScriptShift);
+ }
+
+ ////////////////////////////////////
+ // Get the children's sizes
+ ////////////////////////////////////
+
+ const WritingMode wm(aDesiredSize.GetWritingMode());
+ nscoord width = 0, prescriptsWidth = 0, rightBearing = 0;
+ nscoord minSubScriptShift = 0, minSupScriptShift = 0;
+ nscoord trySubScriptShift = subScriptShift;
+ nscoord trySupScriptShift = supScriptShift;
+ nscoord maxSubScriptShift = subScriptShift;
+ nscoord maxSupScriptShift = supScriptShift;
+ ReflowOutput baseSize(wm);
+ ReflowOutput subScriptSize(wm);
+ ReflowOutput supScriptSize(wm);
+ ReflowOutput multiSubSize(wm), multiSupSize(wm);
+ baseFrame = nullptr;
+ nsIFrame* subScriptFrame = nullptr;
+ nsIFrame* supScriptFrame = nullptr;
+ nsIFrame* prescriptsFrame = nullptr; // frame of <mprescripts/>, if there.
+
+ bool firstPrescriptsPair = false;
+ nsBoundingMetrics bmBase, bmSubScript, bmSupScript, bmMultiSub, bmMultiSup;
+ multiSubSize.SetBlockStartAscent(-0x7FFFFFFF);
+ multiSupSize.SetBlockStartAscent(-0x7FFFFFFF);
+ bmMultiSub.ascent = bmMultiSup.ascent = -0x7FFFFFFF;
+ bmMultiSub.descent = bmMultiSup.descent = -0x7FFFFFFF;
+ nscoord italicCorrection = 0;
+
+ nsBoundingMetrics boundingMetrics;
+ boundingMetrics.width = 0;
+ boundingMetrics.ascent = boundingMetrics.descent = -0x7FFFFFFF;
+ aDesiredSize.Width() = aDesiredSize.Height() = 0;
+
+ int32_t count = 0;
+ bool foundNoneTag = false;
+
+ // Boolean to determine whether the current child is a subscript.
+ // Note that only msup starts with a superscript.
+ bool isSubScript = (tag != nsGkAtoms::msup_);
+
+ nsIFrame* childFrame = aFrame->PrincipalChildList().FirstChild();
+ while (childFrame) {
+ if (childFrame->GetContent()->IsMathMLElement(nsGkAtoms::mprescripts_)) {
+ if (tag != nsGkAtoms::mmultiscripts_) {
+ if (aPlaceOrigin) {
+ aFrame->ReportInvalidChildError(nsGkAtoms::mprescripts_);
+ }
+ return aFrame->ReflowError(aDrawTarget, aDesiredSize);
+ }
+ if (prescriptsFrame) {
+ // duplicate <mprescripts/> found
+ // report an error, encourage people to get their markups in order
+ if (aPlaceOrigin) {
+ aFrame->ReportErrorToConsole("DuplicateMprescripts");
+ }
+ return aFrame->ReflowError(aDrawTarget, aDesiredSize);
+ }
+ if (!isSubScript) {
+ if (aPlaceOrigin) {
+ aFrame->ReportErrorToConsole("SubSupMismatch");
+ }
+ return aFrame->ReflowError(aDrawTarget, aDesiredSize);
+ }
+
+ prescriptsFrame = childFrame;
+ firstPrescriptsPair = true;
+ } else if (0 == count) {
+ // base
+
+ if (childFrame->GetContent()->IsMathMLElement(nsGkAtoms::none)) {
+ if (tag == nsGkAtoms::mmultiscripts_) {
+ if (aPlaceOrigin) {
+ aFrame->ReportErrorToConsole("NoBase");
+ }
+ return aFrame->ReflowError(aDrawTarget, aDesiredSize);
+ } else {
+ //A different error message is triggered later for the other tags
+ foundNoneTag = true;
+ }
+ }
+ baseFrame = childFrame;
+ GetReflowAndBoundingMetricsFor(baseFrame, baseSize, bmBase);
+
+ if (tag != nsGkAtoms::msub_) {
+ // Apply italics correction if there is the potential for a
+ // postsupscript.
+ GetItalicCorrection(bmBase, italicCorrection);
+ // If italics correction is applied, we always add "a little to spare"
+ // (see TeXbook Ch.11, p.64), as we estimate the italic creation
+ // ourselves and it isn't the same as TeX.
+ italicCorrection += onePixel;
+ }
+
+ // we update boundingMetrics.{ascent,descent} with that
+ // of the baseFrame only after processing all the sup/sub pairs
+ boundingMetrics.width = bmBase.width;
+ boundingMetrics.rightBearing = bmBase.rightBearing;
+ boundingMetrics.leftBearing = bmBase.leftBearing; // until overwritten
+ } else {
+ // super/subscript block
+ if (childFrame->GetContent()->IsMathMLElement(nsGkAtoms::none)) {
+ foundNoneTag = true;
+ }
+
+ if (isSubScript) {
+ // subscript
+ subScriptFrame = childFrame;
+ GetReflowAndBoundingMetricsFor(subScriptFrame, subScriptSize, bmSubScript);
+ if (!mathFont) {
+ // get the subdrop from the subscript font
+ GetSubDropFromChild (subScriptFrame, subDrop, aFontSizeInflation);
+ }
+
+ // parameter v, Rule 18a, App. G, TeXbook
+ minSubScriptShift = bmBase.descent + subDrop;
+ trySubScriptShift = std::max(minSubScriptShift,subScriptShift);
+ multiSubSize.SetBlockStartAscent(
+ std::max(multiSubSize.BlockStartAscent(),
+ subScriptSize.BlockStartAscent()));
+ bmMultiSub.ascent = std::max(bmMultiSub.ascent, bmSubScript.ascent);
+ bmMultiSub.descent = std::max(bmMultiSub.descent, bmSubScript.descent);
+ multiSubSize.Height() =
+ std::max(multiSubSize.Height(),
+ subScriptSize.Height() - subScriptSize.BlockStartAscent());
+ if (bmSubScript.width)
+ width = bmSubScript.width + scriptSpace;
+ rightBearing = bmSubScript.rightBearing;
+
+ if (tag == nsGkAtoms::msub_) {
+ boundingMetrics.rightBearing = boundingMetrics.width + rightBearing;
+ boundingMetrics.width += width;
+
+ nscoord subscriptTopMax;
+ if (mathFont) {
+ subscriptTopMax =
+ mathFont->MathTable()->Constant(gfxMathTable::SubscriptTopMax,
+ oneDevPixel);
+ } else {
+ // get min subscript shift limit from x-height
+ // = h(x) - 4/5 * sigma_5, Rule 18b, App. G, TeXbook
+ subscriptTopMax = NSToCoordRound((4.0f/5.0f) * xHeight);
+ }
+ nscoord minShiftFromXHeight = bmSubScript.ascent - subscriptTopMax;
+ maxSubScriptShift = std::max(trySubScriptShift,minShiftFromXHeight);
+
+ maxSubScriptShift = std::max(maxSubScriptShift, trySubScriptShift);
+ trySubScriptShift = subScriptShift;
+ }
+ } else {
+ // supscript
+ supScriptFrame = childFrame;
+ GetReflowAndBoundingMetricsFor(supScriptFrame, supScriptSize, bmSupScript);
+ if (!mathFont) {
+ // get the supdrop from the supscript font
+ GetSupDropFromChild (supScriptFrame, supDrop, aFontSizeInflation);
+ }
+ // parameter u, Rule 18a, App. G, TeXbook
+ minSupScriptShift = bmBase.ascent - supDrop;
+ nscoord superscriptBottomMin;
+ if (mathFont) {
+ superscriptBottomMin =
+ mathFont->MathTable()->Constant(gfxMathTable::SuperscriptBottomMin,
+ oneDevPixel);
+ } else {
+ // get min supscript shift limit from x-height
+ // = d(x) + 1/4 * sigma_5, Rule 18c, App. G, TeXbook
+ superscriptBottomMin = NSToCoordRound((1.0f / 4.0f) * xHeight);
+ }
+ minShiftFromXHeight = bmSupScript.descent + superscriptBottomMin;
+ trySupScriptShift = std::max(minSupScriptShift,
+ std::max(minShiftFromXHeight,
+ supScriptShift));
+ multiSupSize.SetBlockStartAscent(
+ std::max(multiSupSize.BlockStartAscent(),
+ supScriptSize.BlockStartAscent()));
+ bmMultiSup.ascent = std::max(bmMultiSup.ascent, bmSupScript.ascent);
+ bmMultiSup.descent = std::max(bmMultiSup.descent, bmSupScript.descent);
+ multiSupSize.Height() =
+ std::max(multiSupSize.Height(),
+ supScriptSize.Height() - supScriptSize.BlockStartAscent());
+
+ if (bmSupScript.width)
+ width = std::max(width, bmSupScript.width + scriptSpace);
+
+ if (!prescriptsFrame) { // we are still looping over base & postscripts
+ rightBearing = std::max(rightBearing,
+ italicCorrection + bmSupScript.rightBearing);
+ boundingMetrics.rightBearing = boundingMetrics.width + rightBearing;
+ boundingMetrics.width += width;
+ } else {
+ prescriptsWidth += width;
+ if (firstPrescriptsPair) {
+ firstPrescriptsPair = false;
+ boundingMetrics.leftBearing =
+ std::min(bmSubScript.leftBearing, bmSupScript.leftBearing);
+ }
+ }
+ width = rightBearing = 0;
+
+ // negotiate between the various shifts so that
+ // there is enough gap between the sup and subscripts
+ // Rule 18e, App. G, TeXbook
+ if (tag == nsGkAtoms::mmultiscripts_ ||
+ tag == nsGkAtoms::msubsup_) {
+ nscoord subSuperscriptGapMin;
+ if (mathFont) {
+ subSuperscriptGapMin = mathFont->MathTable()->
+ Constant(gfxMathTable::SubSuperscriptGapMin, oneDevPixel);
+ } else {
+ nscoord ruleSize;
+ GetRuleThickness(aDrawTarget, fm, ruleSize);
+ subSuperscriptGapMin = 4 * ruleSize;
+ }
+ nscoord gap =
+ (trySupScriptShift - bmSupScript.descent) -
+ (bmSubScript.ascent - trySubScriptShift);
+ if (gap < subSuperscriptGapMin) {
+ // adjust trySubScriptShift to get a gap of subSuperscriptGapMin
+ trySubScriptShift += subSuperscriptGapMin - gap;
+ }
+
+ // next we want to ensure that the bottom of the superscript
+ // will be > superscriptBottomMaxWithSubscript
+ nscoord superscriptBottomMaxWithSubscript;
+ if (mathFont) {
+ superscriptBottomMaxWithSubscript = mathFont->MathTable()->
+ Constant(gfxMathTable::SuperscriptBottomMaxWithSubscript,
+ oneDevPixel);
+ } else {
+ superscriptBottomMaxWithSubscript =
+ NSToCoordRound((4.0f / 5.0f) * xHeight);
+ }
+ gap = superscriptBottomMaxWithSubscript -
+ (trySupScriptShift - bmSupScript.descent);
+ if (gap > 0) {
+ trySupScriptShift += gap;
+ trySubScriptShift -= gap;
+ }
+ }
+
+ maxSubScriptShift = std::max(maxSubScriptShift, trySubScriptShift);
+ maxSupScriptShift = std::max(maxSupScriptShift, trySupScriptShift);
+
+ trySubScriptShift = subScriptShift;
+ trySupScriptShift = supScriptShift;
+ }
+
+ isSubScript = !isSubScript;
+ }
+ count++;
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ //NoBase error may also have been reported above
+ if ((count != 2 && (tag == nsGkAtoms::msup_ || tag == nsGkAtoms::msub_)) ||
+ (count != 3 && tag == nsGkAtoms::msubsup_) || !baseFrame ||
+ (foundNoneTag && tag != nsGkAtoms::mmultiscripts_) ||
+ (!isSubScript && tag == nsGkAtoms::mmultiscripts_)) {
+ // report an error, encourage people to get their markups in order
+ if (aPlaceOrigin) {
+ if ((count != 2 && (tag == nsGkAtoms::msup_ ||
+ tag == nsGkAtoms::msub_)) ||
+ (count != 3 && tag == nsGkAtoms::msubsup_ )) {
+ aFrame->ReportChildCountError();
+ } else if (foundNoneTag && tag != nsGkAtoms::mmultiscripts_) {
+ aFrame->ReportInvalidChildError(nsGkAtoms::none);
+ } else if (!baseFrame) {
+ aFrame->ReportErrorToConsole("NoBase");
+ } else {
+ aFrame->ReportErrorToConsole("SubSupMismatch");
+ }
+ }
+ return aFrame->ReflowError(aDrawTarget, aDesiredSize);
+ }
+
+ // we left out the width of prescripts, so ...
+ boundingMetrics.rightBearing += prescriptsWidth;
+ boundingMetrics.width += prescriptsWidth;
+
+ // Zero out the shifts in where a frame isn't present to avoid the potential
+ // for overflow.
+ if (!subScriptFrame)
+ maxSubScriptShift = 0;
+ if (!supScriptFrame)
+ maxSupScriptShift = 0;
+
+ // we left out the base during our bounding box updates, so ...
+ if (tag == nsGkAtoms::msub_) {
+ boundingMetrics.ascent = std::max(bmBase.ascent,
+ bmMultiSub.ascent - maxSubScriptShift);
+ } else {
+ boundingMetrics.ascent =
+ std::max(bmBase.ascent, (bmMultiSup.ascent + maxSupScriptShift));
+ }
+ if (tag == nsGkAtoms::msup_) {
+ boundingMetrics.descent = std::max(bmBase.descent,
+ bmMultiSup.descent - maxSupScriptShift);
+ } else {
+ boundingMetrics.descent =
+ std::max(bmBase.descent, (bmMultiSub.descent + maxSubScriptShift));
+ }
+ aFrame->SetBoundingMetrics(boundingMetrics);
+
+ // get the reflow metrics ...
+ aDesiredSize.SetBlockStartAscent(
+ std::max(baseSize.BlockStartAscent(),
+ std::max(multiSubSize.BlockStartAscent() - maxSubScriptShift,
+ multiSupSize.BlockStartAscent() + maxSupScriptShift)));
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() +
+ std::max(baseSize.Height() - baseSize.BlockStartAscent(),
+ std::max(multiSubSize.Height() + maxSubScriptShift,
+ multiSupSize.Height() - maxSupScriptShift));
+ aDesiredSize.Width() = boundingMetrics.width;
+ aDesiredSize.mBoundingMetrics = boundingMetrics;
+
+ aFrame->SetReference(nsPoint(0, aDesiredSize.BlockStartAscent()));
+
+ //////////////////
+ // Place Children
+
+ // Place prescripts, followed by base, and then postscripts.
+ // The list of frames is in the order: {base} {postscripts} {prescripts}
+ // We go over the list in a circular manner, starting at <prescripts/>
+
+ if (aPlaceOrigin) {
+ nscoord dx = 0, dy = 0;
+
+ // With msub and msup there is only one element and
+ // subscriptFrame/supScriptFrame have already been set above where
+ // relevant. In these cases we skip to the reflow part.
+ if (tag == nsGkAtoms::msub_ || tag == nsGkAtoms::msup_)
+ count = 1;
+ else
+ count = 0;
+ childFrame = prescriptsFrame;
+ bool isPreScript = true;
+ do {
+ if (!childFrame) { // end of prescripts,
+ isPreScript = false;
+ // place the base ...
+ childFrame = baseFrame;
+ dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent();
+ FinishReflowChild (baseFrame, aPresContext, baseSize, nullptr,
+ aFrame->MirrorIfRTL(aDesiredSize.Width(),
+ baseSize.Width(),
+ dx),
+ dy, 0);
+ dx += bmBase.width;
+ } else if (prescriptsFrame == childFrame) {
+ // Clear reflow flags of prescripts frame.
+ prescriptsFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED);
+ } else {
+ // process each sup/sub pair
+ if (0 == count) {
+ subScriptFrame = childFrame;
+ count = 1;
+ } else if (1 == count) {
+ if (tag != nsGkAtoms::msub_)
+ supScriptFrame = childFrame;
+ count = 0;
+
+ // get the ascent/descent of sup/subscripts stored in their rects
+ // rect.x = descent, rect.y = ascent
+ if (subScriptFrame)
+ GetReflowAndBoundingMetricsFor(subScriptFrame, subScriptSize, bmSubScript);
+ if (supScriptFrame)
+ GetReflowAndBoundingMetricsFor(supScriptFrame, supScriptSize, bmSupScript);
+
+ width = std::max(subScriptSize.Width(), supScriptSize.Width());
+
+ if (subScriptFrame) {
+ nscoord x = dx;
+ // prescripts should be right aligned
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=928675
+ if (isPreScript)
+ x += width - subScriptSize.Width();
+ dy = aDesiredSize.BlockStartAscent() - subScriptSize.BlockStartAscent() +
+ maxSubScriptShift;
+ FinishReflowChild (subScriptFrame, aPresContext, subScriptSize,
+ nullptr,
+ aFrame->MirrorIfRTL(aDesiredSize.Width(),
+ subScriptSize.Width(),
+ x),
+ dy, 0);
+ }
+
+ if (supScriptFrame) {
+ nscoord x = dx;
+ if (isPreScript) {
+ x += width - supScriptSize.Width();
+ } else {
+ // post superscripts are shifted by the italic correction value
+ x += italicCorrection;
+ }
+ dy = aDesiredSize.BlockStartAscent() - supScriptSize.BlockStartAscent() -
+ maxSupScriptShift;
+ FinishReflowChild (supScriptFrame, aPresContext, supScriptSize,
+ nullptr,
+ aFrame->MirrorIfRTL(aDesiredSize.Width(),
+ supScriptSize.Width(),
+ x),
+ dy, 0);
+ }
+ dx += width + scriptSpace;
+ }
+ }
+ childFrame = childFrame->GetNextSibling();
+ } while (prescriptsFrame != childFrame);
+ }
+
+ return NS_OK;
+}
diff --git a/layout/mathml/nsMathMLmmultiscriptsFrame.h b/layout/mathml/nsMathMLmmultiscriptsFrame.h
new file mode 100644
index 000000000..f3907e730
--- /dev/null
+++ b/layout/mathml/nsMathMLmmultiscriptsFrame.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmmultiscriptsFrame_h___
+#define nsMathMLmmultiscriptsFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+//
+// <mmultiscripts> -- attach prescripts and tensor indices to a base
+// <msub> -- attach a subscript to a base
+// <msubsup> -- attach a subscript-superscript pair to a base
+// <msup> -- attach a superscript to a base
+//
+
+class nsMathMLmmultiscriptsFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmmultiscriptsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ static nsresult
+ PlaceMultiScript(nsPresContext* aPresContext,
+ DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize,
+ nsMathMLContainerFrame* aForFrame,
+ nscoord aUserSubScriptShift,
+ nscoord aUserSupScriptShift,
+ float aFontSizeInflation);
+
+ uint8_t
+ ScriptIncrement(nsIFrame* aFrame) override;
+
+protected:
+ explicit nsMathMLmmultiscriptsFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext) {}
+ virtual ~nsMathMLmmultiscriptsFrame();
+
+
+};
+
+#endif /* nsMathMLmmultiscriptsFrame_h___ */
diff --git a/layout/mathml/nsMathMLmoFrame.cpp b/layout/mathml/nsMathMLmoFrame.cpp
new file mode 100644
index 000000000..591b46309
--- /dev/null
+++ b/layout/mathml/nsMathMLmoFrame.cpp
@@ -0,0 +1,1121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmoFrame.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsContentUtils.h"
+#include "nsFrameSelection.h"
+#include "nsMathMLElement.h"
+#include <algorithm>
+
+//
+// <mo> -- operator, fence, or separator - implementation
+//
+
+// additional style context to be used by our MathMLChar.
+#define NS_MATHML_CHAR_STYLE_CONTEXT_INDEX 0
+
+nsIFrame*
+NS_NewMathMLmoFrame(nsIPresShell* aPresShell, nsStyleContext *aContext)
+{
+ return new (aPresShell) nsMathMLmoFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmoFrame)
+
+nsMathMLmoFrame::~nsMathMLmoFrame()
+{
+}
+
+static const char16_t kApplyFunction = char16_t(0x2061);
+static const char16_t kInvisibleTimes = char16_t(0x2062);
+static const char16_t kInvisibleSeparator = char16_t(0x2063);
+static const char16_t kInvisiblePlus = char16_t(0x2064);
+
+eMathMLFrameType
+nsMathMLmoFrame::GetMathMLFrameType()
+{
+ return NS_MATHML_OPERATOR_IS_INVISIBLE(mFlags)
+ ? eMathMLFrameType_OperatorInvisible
+ : eMathMLFrameType_OperatorOrdinary;
+}
+
+// since a mouse click implies selection, we cannot just rely on the
+// frame's state bit in our child text frame. So we will first check
+// its selected state bit, and use this little helper to double check.
+bool
+nsMathMLmoFrame::IsFrameInSelection(nsIFrame* aFrame)
+{
+ NS_ASSERTION(aFrame, "null arg");
+ if (!aFrame || !aFrame->IsSelected())
+ return false;
+
+ const nsFrameSelection* frameSelection = aFrame->GetConstFrameSelection();
+ SelectionDetails* details =
+ frameSelection->LookUpSelection(aFrame->GetContent(), 0, 1, true);
+
+ if (!details)
+ return false;
+
+ while (details) {
+ SelectionDetails* next = details->mNext;
+ delete details;
+ details = next;
+ }
+ return true;
+}
+
+bool
+nsMathMLmoFrame::UseMathMLChar()
+{
+ return (NS_MATHML_OPERATOR_GET_FORM(mFlags) &&
+ NS_MATHML_OPERATOR_IS_MUTABLE(mFlags)) ||
+ NS_MATHML_OPERATOR_IS_CENTERED(mFlags);
+}
+
+void
+nsMathMLmoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ bool useMathMLChar = UseMathMLChar();
+
+ if (!useMathMLChar) {
+ // let the base class do everything
+ nsMathMLTokenFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+ } else {
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ // make our char selected if our inner child text frame is selected
+ bool isSelected = false;
+ nsRect selectedRect;
+ nsIFrame* firstChild = mFrames.FirstChild();
+ if (IsFrameInSelection(firstChild)) {
+ mMathMLChar.GetRect(selectedRect);
+ // add a one pixel border (it renders better for operators like minus)
+ selectedRect.Inflate(nsPresContext::CSSPixelsToAppUnits(1));
+ isSelected = true;
+ }
+ mMathMLChar.Display(aBuilder, this, aLists, 0, isSelected ? &selectedRect : nullptr);
+
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ // for visual debug
+ DisplayBoundingMetrics(aBuilder, this, mReference, mBoundingMetrics, aLists);
+#endif
+ }
+}
+
+// get the text that we enclose and setup our nsMathMLChar
+void
+nsMathMLmoFrame::ProcessTextData()
+{
+ mFlags = 0;
+
+ nsAutoString data;
+ nsContentUtils::GetNodeTextContent(mContent, false, data);
+
+ data.CompressWhitespace();
+ int32_t length = data.Length();
+ char16_t ch = (length == 0) ? char16_t('\0') : data[0];
+
+ if ((length == 1) &&
+ (ch == kApplyFunction ||
+ ch == kInvisibleSeparator ||
+ ch == kInvisiblePlus ||
+ ch == kInvisibleTimes)) {
+ mFlags |= NS_MATHML_OPERATOR_INVISIBLE;
+ }
+
+ // don't bother doing anything special if we don't have a single child
+ nsPresContext* presContext = PresContext();
+ if (mFrames.GetLength() != 1) {
+ data.Truncate(); // empty data to reset the char
+ mMathMLChar.SetData(data);
+ ResolveMathMLCharStyle(presContext, mContent, mStyleContext, &mMathMLChar);
+ return;
+ }
+
+ // special... in math mode, the usual minus sign '-' looks too short, so
+ // what we do here is to remap <mo>-</mo> to the official Unicode minus
+ // sign (U+2212) which looks much better. For background on this, see
+ // http://groups.google.com/groups?hl=en&th=66488daf1ade7635&rnum=1
+ if (1 == length && ch == '-') {
+ ch = 0x2212;
+ data = ch;
+ }
+
+ // cache the special bits: mutable, accent, movablelimits, centered.
+ // we need to do this in anticipation of other requirements, and these
+ // bits don't change. Do not reset these bits unless the text gets changed.
+
+ // lookup all the forms under which the operator is listed in the dictionary,
+ // and record whether the operator has accent="true" or movablelimits="true"
+ nsOperatorFlags flags[4];
+ float lspace[4], rspace[4];
+ nsMathMLOperators::LookupOperators(data, flags, lspace, rspace);
+ nsOperatorFlags allFlags =
+ flags[NS_MATHML_OPERATOR_FORM_INFIX] |
+ flags[NS_MATHML_OPERATOR_FORM_POSTFIX] |
+ flags[NS_MATHML_OPERATOR_FORM_PREFIX];
+
+ mFlags |= allFlags & NS_MATHML_OPERATOR_ACCENT;
+ mFlags |= allFlags & NS_MATHML_OPERATOR_MOVABLELIMITS;
+
+ // see if this is an operator that should be centered to cater for
+ // fonts that are not math-aware
+ if (1 == length) {
+ if ((ch == '+') || (ch == '=') || (ch == '*') ||
+ (ch == 0x2212) || // &minus;
+ (ch == 0x2264) || // &le;
+ (ch == 0x2265) || // &ge;
+ (ch == 0x00D7)) { // &times;
+ mFlags |= NS_MATHML_OPERATOR_CENTERED;
+ }
+ }
+
+ // cache the operator
+ mMathMLChar.SetData(data);
+
+ // cache the native direction -- beware of bug 133429...
+ // mEmbellishData.direction must always retain our native direction, whereas
+ // mMathMLChar.GetStretchDirection() may change later, when Stretch() is called
+ mEmbellishData.direction = mMathMLChar.GetStretchDirection();
+
+ bool isMutable =
+ NS_MATHML_OPERATOR_IS_LARGEOP(allFlags) ||
+ (mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED);
+ if (isMutable)
+ mFlags |= NS_MATHML_OPERATOR_MUTABLE;
+
+ ResolveMathMLCharStyle(presContext, mContent, mStyleContext, &mMathMLChar);
+}
+
+// get our 'form' and lookup in the Operator Dictionary to fetch
+// our default data that may come from there. Then complete our setup
+// using attributes that we may have. To stay in sync, this function is
+// called very often. We depend on many things that may change around us.
+// However, we re-use unchanged values.
+void
+nsMathMLmoFrame::ProcessOperatorData()
+{
+ // if we have been here before, we will just use our cached form
+ nsOperatorFlags form = NS_MATHML_OPERATOR_GET_FORM(mFlags);
+ nsAutoString value;
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+
+ // special bits are always kept in mFlags.
+ // remember the mutable bit from ProcessTextData().
+ // Some chars are listed under different forms in the dictionary,
+ // and there could be a form under which the char is mutable.
+ // If the char is the core of an embellished container, we will keep
+ // it mutable irrespective of the form of the embellished container.
+ // Also remember the other special bits that we want to carry forward.
+ mFlags &= NS_MATHML_OPERATOR_MUTABLE |
+ NS_MATHML_OPERATOR_ACCENT |
+ NS_MATHML_OPERATOR_MOVABLELIMITS |
+ NS_MATHML_OPERATOR_CENTERED |
+ NS_MATHML_OPERATOR_INVISIBLE;
+
+ if (!mEmbellishData.coreFrame) {
+ // i.e., we haven't been here before, the default form is infix
+ form = NS_MATHML_OPERATOR_FORM_INFIX;
+
+ // reset everything so that we don't keep outdated values around
+ // in case of dynamic changes
+ mEmbellishData.flags = 0;
+ mEmbellishData.coreFrame = nullptr;
+ mEmbellishData.leadingSpace = 0;
+ mEmbellishData.trailingSpace = 0;
+ if (mMathMLChar.Length() != 1)
+ mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ // else... retain the native direction obtained in ProcessTextData()
+
+ if (!mFrames.FirstChild()) {
+ return;
+ }
+
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_OPERATOR;
+ mEmbellishData.coreFrame = this;
+
+ // there are two particular things that we also need to record so that if our
+ // parent is <mover>, <munder>, or <munderover>, they will treat us properly:
+ // 1) do we have accent="true"
+ // 2) do we have movablelimits="true"
+
+ // they need the extra information to decide how to treat their scripts/limits
+ // (note: <mover>, <munder>, or <munderover> need not necessarily be our
+ // direct parent -- case of embellished operators)
+
+ // default values from the Operator Dictionary were obtained in ProcessTextData()
+ // and these special bits are always kept in mFlags
+ if (NS_MATHML_OPERATOR_IS_ACCENT(mFlags))
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENT;
+ if (NS_MATHML_OPERATOR_IS_MOVABLELIMITS(mFlags))
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_MOVABLELIMITS;
+
+ // see if the accent attribute is there
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accent_, value);
+ if (value.EqualsLiteral("true"))
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENT;
+ else if (value.EqualsLiteral("false"))
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENT;
+
+ // see if the movablelimits attribute is there
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::movablelimits_, value);
+ if (value.EqualsLiteral("true"))
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_MOVABLELIMITS;
+ else if (value.EqualsLiteral("false"))
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_MOVABLELIMITS;
+
+ // ---------------------------------------------------------------------
+ // we will be called again to re-sync the rest of our state next time...
+ // (nobody needs the other values below at this stage)
+ mFlags |= form;
+ return;
+ }
+
+ nsPresContext* presContext = PresContext();
+
+ // beware of bug 133814 - there is a two-way dependency in the
+ // embellished hierarchy: our embellished ancestors need to set
+ // their flags based on some of our state (set above), and here we
+ // need to re-sync our 'form' depending on our outermost embellished
+ // container. A null form here means that an earlier attempt to stretch
+ // our mMathMLChar failed, in which case we don't bother re-stretching again
+ if (form) {
+ // get our outermost embellished container and its parent.
+ // (we ensure that we are the core, not just a sibling of the core)
+ nsIFrame* embellishAncestor = this;
+ nsEmbellishData embellishData;
+ nsIFrame* parentAncestor = this;
+ do {
+ embellishAncestor = parentAncestor;
+ parentAncestor = embellishAncestor->GetParent();
+ GetEmbellishDataFrom(parentAncestor, embellishData);
+ } while (embellishData.coreFrame == this);
+
+ // flag if we have an embellished ancestor
+ if (embellishAncestor != this)
+ mFlags |= NS_MATHML_OPERATOR_EMBELLISH_ANCESTOR;
+ else
+ mFlags &= ~NS_MATHML_OPERATOR_EMBELLISH_ANCESTOR;
+
+ // find the position of our outermost embellished container w.r.t
+ // its siblings.
+
+ nsIFrame* nextSibling = embellishAncestor->GetNextSibling();
+ nsIFrame* prevSibling = embellishAncestor->GetPrevSibling();
+
+ // flag to distinguish from a real infix. Set for (embellished) operators
+ // that live in (inferred) mrows.
+ nsIMathMLFrame* mathAncestor = do_QueryFrame(parentAncestor);
+ bool zeroSpacing = false;
+ if (mathAncestor) {
+ zeroSpacing = !mathAncestor->IsMrowLike();
+ } else {
+ nsMathMLmathBlockFrame* blockFrame = do_QueryFrame(parentAncestor);
+ if (blockFrame) {
+ zeroSpacing = !blockFrame->IsMrowLike();
+ }
+ }
+ if (zeroSpacing) {
+ mFlags |= NS_MATHML_OPERATOR_EMBELLISH_ISOLATED;
+ } else {
+ mFlags &= ~NS_MATHML_OPERATOR_EMBELLISH_ISOLATED;
+ }
+
+ // find our form
+ form = NS_MATHML_OPERATOR_FORM_INFIX;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::form, value);
+ if (!value.IsEmpty()) {
+ if (value.EqualsLiteral("prefix"))
+ form = NS_MATHML_OPERATOR_FORM_PREFIX;
+ else if (value.EqualsLiteral("postfix"))
+ form = NS_MATHML_OPERATOR_FORM_POSTFIX;
+ }
+ else {
+ // set our form flag depending on the position
+ if (!prevSibling && nextSibling)
+ form = NS_MATHML_OPERATOR_FORM_PREFIX;
+ else if (prevSibling && !nextSibling)
+ form = NS_MATHML_OPERATOR_FORM_POSTFIX;
+ }
+ mFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the old form bits
+ mFlags |= form;
+
+ // Use the default value suggested by the MathML REC.
+ // http://www.w3.org/TR/MathML/chapter3.html#presm.mo.attrs
+ // thickmathspace = 5/18em
+ float lspace = 5.0f/18.0f;
+ float rspace = 5.0f/18.0f;
+ // lookup the operator dictionary
+ nsAutoString data;
+ mMathMLChar.GetData(data);
+ nsMathMLOperators::LookupOperator(data, form, &mFlags, &lspace, &rspace);
+ // Spacing is zero if our outermost embellished operator is not in an
+ // inferred mrow.
+ if (!NS_MATHML_OPERATOR_EMBELLISH_IS_ISOLATED(mFlags) &&
+ (lspace || rspace)) {
+ // Cache the default values of lspace and rspace.
+ // since these values are relative to the 'em' unit, convert to twips now
+ nscoord em;
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+ GetEmHeight(fm, em);
+
+ mEmbellishData.leadingSpace = NSToCoordRound(lspace * em);
+ mEmbellishData.trailingSpace = NSToCoordRound(rspace * em);
+
+ // tuning if we don't want too much extra space when we are a script.
+ // (with its fonts, TeX sets lspace=0 & rspace=0 as soon as scriptlevel>0.
+ // Our fonts can be anything, so...)
+ if (StyleFont()->mScriptLevel > 0 &&
+ !NS_MATHML_OPERATOR_HAS_EMBELLISH_ANCESTOR(mFlags)) {
+ mEmbellishData.leadingSpace /= 2;
+ mEmbellishData.trailingSpace /= 2;
+ }
+ }
+ }
+
+ // If we are an accent without explicit lspace="." or rspace=".",
+ // we will ignore our default leading/trailing space
+
+ // lspace
+ //
+ // "Specifies the leading space appearing before the operator"
+ //
+ // values: length
+ // default: set by dictionary (thickmathspace)
+ //
+ // XXXfredw Support for negative and relative values is not implemented
+ // (bug 805926).
+ // Relative values will give a multiple of the current leading space,
+ // which is not necessarily the default one.
+ //
+ nscoord leadingSpace = mEmbellishData.leadingSpace;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value);
+ if (!value.IsEmpty()) {
+ nsCSSValue cssValue;
+ if (nsMathMLElement::ParseNumericValue(value, cssValue, 0,
+ mContent->OwnerDoc())) {
+ if ((eCSSUnit_Number == cssValue.GetUnit()) && !cssValue.GetFloatValue())
+ leadingSpace = 0;
+ else if (cssValue.IsLengthUnit())
+ leadingSpace = CalcLength(presContext, mStyleContext, cssValue,
+ fontSizeInflation);
+ mFlags |= NS_MATHML_OPERATOR_LSPACE_ATTR;
+ }
+ }
+
+ // rspace
+ //
+ // "Specifies the trailing space appearing after the operator"
+ //
+ // values: length
+ // default: set by dictionary (thickmathspace)
+ //
+ // XXXfredw Support for negative and relative values is not implemented
+ // (bug 805926).
+ // Relative values will give a multiple of the current leading space,
+ // which is not necessarily the default one.
+ //
+ nscoord trailingSpace = mEmbellishData.trailingSpace;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rspace_, value);
+ if (!value.IsEmpty()) {
+ nsCSSValue cssValue;
+ if (nsMathMLElement::ParseNumericValue(value, cssValue, 0,
+ mContent->OwnerDoc())) {
+ if ((eCSSUnit_Number == cssValue.GetUnit()) && !cssValue.GetFloatValue())
+ trailingSpace = 0;
+ else if (cssValue.IsLengthUnit())
+ trailingSpace = CalcLength(presContext, mStyleContext, cssValue,
+ fontSizeInflation);
+ mFlags |= NS_MATHML_OPERATOR_RSPACE_ATTR;
+ }
+ }
+
+ // little extra tuning to round lspace & rspace to at least a pixel so that
+ // operators don't look as if they are colliding with their operands
+ if (leadingSpace || trailingSpace) {
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+ if (leadingSpace && leadingSpace < onePixel)
+ leadingSpace = onePixel;
+ if (trailingSpace && trailingSpace < onePixel)
+ trailingSpace = onePixel;
+ }
+
+ // the values that we get from our attributes override the dictionary
+ mEmbellishData.leadingSpace = leadingSpace;
+ mEmbellishData.trailingSpace = trailingSpace;
+
+ // Now see if there are user-defined attributes that override the dictionary.
+ // XXX Bug 1197771 - forcing an attribute to true when it is false in the
+ // dictionary can cause conflicts in the rest of the stretching algorithms
+ // (e.g. all largeops are assumed to have a vertical direction)
+
+ // For each attribute overriden by the user, turn off its bit flag.
+ // symmetric|movablelimits|separator|largeop|accent|fence|stretchy|form
+ // special: accent and movablelimits are handled above,
+ // don't process them here
+
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::stretchy_, value);
+ if (value.EqualsLiteral("false")) {
+ mFlags &= ~NS_MATHML_OPERATOR_STRETCHY;
+ } else if (value.EqualsLiteral("true")) {
+ mFlags |= NS_MATHML_OPERATOR_STRETCHY;
+ }
+ if (NS_MATHML_OPERATOR_IS_FENCE(mFlags)) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::fence_, value);
+ if (value.EqualsLiteral("false"))
+ mFlags &= ~NS_MATHML_OPERATOR_FENCE;
+ else
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_FENCE;
+ }
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::largeop_, value);
+ if (value.EqualsLiteral("false")) {
+ mFlags &= ~NS_MATHML_OPERATOR_LARGEOP;
+ } else if (value.EqualsLiteral("true")) {
+ mFlags |= NS_MATHML_OPERATOR_LARGEOP;
+ }
+ if (NS_MATHML_OPERATOR_IS_SEPARATOR(mFlags)) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::separator_, value);
+ if (value.EqualsLiteral("false"))
+ mFlags &= ~NS_MATHML_OPERATOR_SEPARATOR;
+ else
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_SEPARATOR;
+ }
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::symmetric_, value);
+ if (value.EqualsLiteral("false"))
+ mFlags &= ~NS_MATHML_OPERATOR_SYMMETRIC;
+ else if (value.EqualsLiteral("true"))
+ mFlags |= NS_MATHML_OPERATOR_SYMMETRIC;
+
+
+ // minsize
+ //
+ // "Specifies the minimum size of the operator when stretchy"
+ //
+ // values: length
+ // default: set by dictionary (1em)
+ //
+ // We don't allow negative values.
+ // Note: Contrary to other "length" values, unitless and percentage do not
+ // give a multiple of the defaut value but a multiple of the operator at
+ // normal size.
+ //
+ mMinSize = 0;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::minsize_, value);
+ if (!value.IsEmpty()) {
+ nsCSSValue cssValue;
+ if (nsMathMLElement::ParseNumericValue(value, cssValue,
+ nsMathMLElement::
+ PARSE_ALLOW_UNITLESS,
+ mContent->OwnerDoc())) {
+ nsCSSUnit unit = cssValue.GetUnit();
+ if (eCSSUnit_Number == unit)
+ mMinSize = cssValue.GetFloatValue();
+ else if (eCSSUnit_Percent == unit)
+ mMinSize = cssValue.GetPercentValue();
+ else if (eCSSUnit_Null != unit) {
+ mMinSize = float(CalcLength(presContext, mStyleContext, cssValue,
+ fontSizeInflation));
+ mFlags |= NS_MATHML_OPERATOR_MINSIZE_ABSOLUTE;
+ }
+ }
+ }
+
+ // maxsize
+ //
+ // "Specifies the maximum size of the operator when stretchy"
+ //
+ // values: length | "infinity"
+ // default: set by dictionary (infinity)
+ //
+ // We don't allow negative values.
+ // Note: Contrary to other "length" values, unitless and percentage do not
+ // give a multiple of the defaut value but a multiple of the operator at
+ // normal size.
+ //
+ mMaxSize = NS_MATHML_OPERATOR_SIZE_INFINITY;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::maxsize_, value);
+ if (!value.IsEmpty()) {
+ nsCSSValue cssValue;
+ if (nsMathMLElement::ParseNumericValue(value, cssValue,
+ nsMathMLElement::
+ PARSE_ALLOW_UNITLESS,
+ mContent->OwnerDoc())) {
+ nsCSSUnit unit = cssValue.GetUnit();
+ if (eCSSUnit_Number == unit)
+ mMaxSize = cssValue.GetFloatValue();
+ else if (eCSSUnit_Percent == unit)
+ mMaxSize = cssValue.GetPercentValue();
+ else if (eCSSUnit_Null != unit) {
+ mMaxSize = float(CalcLength(presContext, mStyleContext, cssValue,
+ fontSizeInflation));
+ mFlags |= NS_MATHML_OPERATOR_MAXSIZE_ABSOLUTE;
+ }
+ }
+ }
+}
+
+static uint32_t
+GetStretchHint(nsOperatorFlags aFlags, nsPresentationData aPresentationData,
+ bool aIsVertical, const nsStyleFont* aStyleFont)
+{
+ uint32_t stretchHint = NS_STRETCH_NONE;
+ // See if it is okay to stretch,
+ // starting from what the Operator Dictionary said
+ if (NS_MATHML_OPERATOR_IS_MUTABLE(aFlags)) {
+ // set the largeop or largeopOnly flags to suitably cover all the
+ // 8 possible cases depending on whether displaystyle, largeop,
+ // stretchy are true or false (see bug 69325).
+ // . largeopOnly is taken if largeop=true and stretchy=false
+ // . largeop is taken if largeop=true and stretchy=true
+ if (aStyleFont->mMathDisplay == NS_MATHML_DISPLAYSTYLE_BLOCK &&
+ NS_MATHML_OPERATOR_IS_LARGEOP(aFlags)) {
+ stretchHint = NS_STRETCH_LARGEOP; // (largeopOnly, not mask!)
+ if (NS_MATHML_OPERATOR_IS_INTEGRAL(aFlags)) {
+ stretchHint |= NS_STRETCH_INTEGRAL;
+ }
+ if (NS_MATHML_OPERATOR_IS_STRETCHY(aFlags)) {
+ stretchHint |= NS_STRETCH_NEARER | NS_STRETCH_LARGER;
+ }
+ }
+ else if(NS_MATHML_OPERATOR_IS_STRETCHY(aFlags)) {
+ if (aIsVertical) {
+ // TeX hint. Can impact some sloppy markups missing <mrow></mrow>
+ stretchHint = NS_STRETCH_NEARER;
+ }
+ else {
+ stretchHint = NS_STRETCH_NORMAL;
+ }
+ }
+ // else if the stretchy and largeop attributes have been disabled,
+ // the operator is not mutable
+ }
+ return stretchHint;
+}
+
+// NOTE: aDesiredStretchSize is an IN/OUT parameter
+// On input - it contains our current size
+// On output - the same size or the new size that we want
+NS_IMETHODIMP
+nsMathMLmoFrame::Stretch(DrawTarget* aDrawTarget,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aContainerSize,
+ ReflowOutput& aDesiredStretchSize)
+{
+ if (NS_MATHML_STRETCH_WAS_DONE(mPresentationData.flags)) {
+ NS_WARNING("it is wrong to fire stretch more than once on a frame");
+ return NS_OK;
+ }
+ mPresentationData.flags |= NS_MATHML_STRETCH_DONE;
+
+ nsIFrame* firstChild = mFrames.FirstChild();
+
+ // get the axis height;
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+ nscoord axisHeight, height;
+ GetAxisHeight(aDrawTarget, fm, axisHeight);
+
+ // get the leading to be left at the top and the bottom of the stretched char
+ // this seems more reliable than using fm->GetLeading() on suspicious fonts
+ nscoord em;
+ GetEmHeight(fm, em);
+ nscoord leading = NSToCoordRound(0.2f * em);
+
+ // Operators that are stretchy, or those that are to be centered
+ // to cater for fonts that are not math-aware, are handled by the MathMLChar
+ // ('form' is reset if stretch fails -- i.e., we don't bother to stretch next time)
+ bool useMathMLChar = UseMathMLChar();
+
+ nsBoundingMetrics charSize;
+ nsBoundingMetrics container = aDesiredStretchSize.mBoundingMetrics;
+ bool isVertical = false;
+
+ if (((aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL) ||
+ (aStretchDirection == NS_STRETCH_DIRECTION_DEFAULT)) &&
+ (mEmbellishData.direction == NS_STRETCH_DIRECTION_VERTICAL)) {
+ isVertical = true;
+ }
+
+ uint32_t stretchHint =
+ GetStretchHint(mFlags, mPresentationData, isVertical, StyleFont());
+
+ if (useMathMLChar) {
+ nsBoundingMetrics initialSize = aDesiredStretchSize.mBoundingMetrics;
+
+ if (stretchHint != NS_STRETCH_NONE) {
+
+ container = aContainerSize;
+
+ // some adjustments if the operator is symmetric and vertical
+
+ if (isVertical && NS_MATHML_OPERATOR_IS_SYMMETRIC(mFlags)) {
+ // we need to center about the axis
+ nscoord delta = std::max(container.ascent - axisHeight,
+ container.descent + axisHeight);
+ container.ascent = delta + axisHeight;
+ container.descent = delta - axisHeight;
+
+ // get ready in case we encounter user-desired min-max size
+ delta = std::max(initialSize.ascent - axisHeight,
+ initialSize.descent + axisHeight);
+ initialSize.ascent = delta + axisHeight;
+ initialSize.descent = delta - axisHeight;
+ }
+
+ // check for user-desired min-max size
+
+ if (mMaxSize != NS_MATHML_OPERATOR_SIZE_INFINITY && mMaxSize > 0.0f) {
+ // if we are here, there is a user defined maxsize ...
+ //XXX Set stretchHint = NS_STRETCH_NORMAL? to honor the maxsize as close as possible?
+ if (NS_MATHML_OPERATOR_MAXSIZE_IS_ABSOLUTE(mFlags)) {
+ // there is an explicit value like maxsize="20pt"
+ // try to maintain the aspect ratio of the char
+ float aspect = mMaxSize / float(initialSize.ascent + initialSize.descent);
+ container.ascent =
+ std::min(container.ascent, nscoord(initialSize.ascent * aspect));
+ container.descent =
+ std::min(container.descent, nscoord(initialSize.descent * aspect));
+ // below we use a type cast instead of a conversion to avoid a VC++ bug
+ // see http://support.microsoft.com/support/kb/articles/Q115/7/05.ASP
+ container.width =
+ std::min(container.width, (nscoord)mMaxSize);
+ }
+ else { // multiplicative value
+ container.ascent =
+ std::min(container.ascent, nscoord(initialSize.ascent * mMaxSize));
+ container.descent =
+ std::min(container.descent, nscoord(initialSize.descent * mMaxSize));
+ container.width =
+ std::min(container.width, nscoord(initialSize.width * mMaxSize));
+ }
+
+ if (isVertical && !NS_MATHML_OPERATOR_IS_SYMMETRIC(mFlags)) {
+ // re-adjust to align the char with the bottom of the initial container
+ height = container.ascent + container.descent;
+ container.descent = aContainerSize.descent;
+ container.ascent = height - container.descent;
+ }
+ }
+
+ if (mMinSize > 0.0f) {
+ // if we are here, there is a user defined minsize ...
+ // always allow the char to stretch in its natural direction,
+ // even if it is different from the caller's direction
+ if (aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT &&
+ aStretchDirection != mEmbellishData.direction) {
+ aStretchDirection = NS_STRETCH_DIRECTION_DEFAULT;
+ // but when we are not honoring the requested direction
+ // we should not use the caller's container size either
+ container = initialSize;
+ }
+ if (NS_MATHML_OPERATOR_MINSIZE_IS_ABSOLUTE(mFlags)) {
+ // there is an explicit value like minsize="20pt"
+ // try to maintain the aspect ratio of the char
+ float aspect = mMinSize / float(initialSize.ascent + initialSize.descent);
+ container.ascent =
+ std::max(container.ascent, nscoord(initialSize.ascent * aspect));
+ container.descent =
+ std::max(container.descent, nscoord(initialSize.descent * aspect));
+ container.width =
+ std::max(container.width, (nscoord)mMinSize);
+ }
+ else { // multiplicative value
+ container.ascent =
+ std::max(container.ascent, nscoord(initialSize.ascent * mMinSize));
+ container.descent =
+ std::max(container.descent, nscoord(initialSize.descent * mMinSize));
+ container.width =
+ std::max(container.width, nscoord(initialSize.width * mMinSize));
+ }
+
+ if (isVertical && !NS_MATHML_OPERATOR_IS_SYMMETRIC(mFlags)) {
+ // re-adjust to align the char with the bottom of the initial container
+ height = container.ascent + container.descent;
+ container.descent = aContainerSize.descent;
+ container.ascent = height - container.descent;
+ }
+ }
+ }
+
+ // let the MathMLChar stretch itself...
+ nsresult res = mMathMLChar.Stretch(PresContext(), aDrawTarget,
+ fontSizeInflation,
+ aStretchDirection, container, charSize,
+ stretchHint,
+ StyleVisibility()->mDirection);
+ if (NS_FAILED(res)) {
+ // gracefully handle cases where stretching the char failed (i.e., GetBoundingMetrics failed)
+ // clear our 'form' to behave as if the operator wasn't in the dictionary
+ mFlags &= ~NS_MATHML_OPERATOR_FORM;
+ useMathMLChar = false;
+ }
+ }
+
+ // Place our children using the default method
+ // This will allow our child text frame to get its DidReflow()
+ nsresult rv = Place(aDrawTarget, true, aDesiredStretchSize);
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
+ // Make sure the child frames get their DidReflow() calls.
+ DidReflowChildren(mFrames.FirstChild());
+ }
+
+ if (useMathMLChar) {
+ // update our bounding metrics... it becomes that of our MathML char
+ mBoundingMetrics = charSize;
+
+ // if the returned direction is 'unsupported', the char didn't actually change.
+ // So we do the centering only if necessary
+ if (mMathMLChar.GetStretchDirection() != NS_STRETCH_DIRECTION_UNSUPPORTED ||
+ NS_MATHML_OPERATOR_IS_CENTERED(mFlags)) {
+
+ bool largeopOnly =
+ (NS_STRETCH_LARGEOP & stretchHint) != 0 &&
+ (NS_STRETCH_VARIABLE_MASK & stretchHint) == 0;
+
+ if (isVertical || NS_MATHML_OPERATOR_IS_CENTERED(mFlags)) {
+ // the desired size returned by mMathMLChar maybe different
+ // from the size of the container.
+ // the mMathMLChar.mRect.y calculation is subtle, watch out!!!
+
+ height = mBoundingMetrics.ascent + mBoundingMetrics.descent;
+ if (NS_MATHML_OPERATOR_IS_SYMMETRIC(mFlags) ||
+ NS_MATHML_OPERATOR_IS_CENTERED(mFlags)) {
+ // For symmetric and vertical operators, or for operators that are always
+ // centered ('+', '*', etc) we want to center about the axis of the container
+ mBoundingMetrics.descent = height/2 - axisHeight;
+ } else if (!largeopOnly) {
+ // Align the center of the char with the center of the container
+ mBoundingMetrics.descent = height/2 +
+ (container.ascent + container.descent)/2 - container.ascent;
+ } // else align the baselines
+ mBoundingMetrics.ascent = height - mBoundingMetrics.descent;
+ }
+ }
+ }
+
+ // Fixup for the final height.
+ // On one hand, our stretchy height can sometimes be shorter than surrounding
+ // ASCII chars, e.g., arrow symbols have |mBoundingMetrics.ascent + leading|
+ // that is smaller than the ASCII's ascent, hence when painting the background
+ // later, it won't look uniform along the line.
+ // On the other hand, sometimes we may leave too much gap when our glyph happens
+ // to come from a font with tall glyphs. For example, since CMEX10 has very tall
+ // glyphs, its natural font metrics are large, even if we pick a small glyph
+ // whose size is comparable to the size of a normal ASCII glyph.
+ // So to avoid uneven spacing in either of these two cases, we use the height
+ // of the ASCII font as a reference and try to match it if possible.
+
+ // special case for accents... keep them short to improve mouse operations...
+ // an accent can only be the non-first child of <mover>, <munder>, <munderover>
+ bool isAccent =
+ NS_MATHML_EMBELLISH_IS_ACCENT(mEmbellishData.flags);
+ if (isAccent) {
+ nsEmbellishData parentData;
+ GetEmbellishDataFrom(GetParent(), parentData);
+ isAccent =
+ (NS_MATHML_EMBELLISH_IS_ACCENTOVER(parentData.flags) ||
+ NS_MATHML_EMBELLISH_IS_ACCENTUNDER(parentData.flags)) &&
+ parentData.coreFrame != this;
+ }
+ if (isAccent && firstChild) {
+ // see bug 188467 for what is going on here
+ nscoord dy = aDesiredStretchSize.BlockStartAscent() -
+ (mBoundingMetrics.ascent + leading);
+ aDesiredStretchSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading);
+ aDesiredStretchSize.Height() = aDesiredStretchSize.BlockStartAscent() +
+ mBoundingMetrics.descent;
+
+ firstChild->SetPosition(firstChild->GetPosition() - nsPoint(0, dy));
+ }
+ else if (useMathMLChar) {
+ nscoord ascent = fm->MaxAscent();
+ nscoord descent = fm->MaxDescent();
+ aDesiredStretchSize.SetBlockStartAscent(std::max(mBoundingMetrics.ascent + leading, ascent));
+ aDesiredStretchSize.Height() = aDesiredStretchSize.BlockStartAscent() +
+ std::max(mBoundingMetrics.descent + leading, descent);
+ }
+ aDesiredStretchSize.Width() = mBoundingMetrics.width;
+ aDesiredStretchSize.mBoundingMetrics = mBoundingMetrics;
+ mReference.x = 0;
+ mReference.y = aDesiredStretchSize.BlockStartAscent();
+ // Place our mMathMLChar, its origin is in our coordinate system
+ if (useMathMLChar) {
+ nscoord dy = aDesiredStretchSize.BlockStartAscent() - mBoundingMetrics.ascent;
+ mMathMLChar.SetRect(nsRect(0, dy, charSize.width, charSize.ascent + charSize.descent));
+ }
+
+ // Before we leave... there is a last item in the check-list:
+ // If our parent is not embellished, it means we are the outermost embellished
+ // container and so we put the spacing, otherwise we don't include the spacing,
+ // the outermost embellished container will take care of it.
+
+ if (!NS_MATHML_OPERATOR_HAS_EMBELLISH_ANCESTOR(mFlags)) {
+
+ // Account the spacing if we are not an accent with explicit attributes
+ nscoord leadingSpace = mEmbellishData.leadingSpace;
+ if (isAccent && !NS_MATHML_OPERATOR_HAS_LSPACE_ATTR(mFlags)) {
+ leadingSpace = 0;
+ }
+ nscoord trailingSpace = mEmbellishData.trailingSpace;
+ if (isAccent && !NS_MATHML_OPERATOR_HAS_RSPACE_ATTR(mFlags)) {
+ trailingSpace = 0;
+ }
+
+ mBoundingMetrics.width += leadingSpace + trailingSpace;
+ aDesiredStretchSize.Width() = mBoundingMetrics.width;
+ aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width;
+
+ nscoord dx = (StyleVisibility()->mDirection ?
+ trailingSpace : leadingSpace);
+ if (dx) {
+ // adjust the offsets
+ mBoundingMetrics.leftBearing += dx;
+ mBoundingMetrics.rightBearing += dx;
+ aDesiredStretchSize.mBoundingMetrics.leftBearing += dx;
+ aDesiredStretchSize.mBoundingMetrics.rightBearing += dx;
+
+ if (useMathMLChar) {
+ nsRect rect;
+ mMathMLChar.GetRect(rect);
+ mMathMLChar.SetRect(nsRect(rect.x + dx, rect.y,
+ rect.width, rect.height));
+ }
+ else {
+ nsIFrame* childFrame = firstChild;
+ while (childFrame) {
+ childFrame->SetPosition(childFrame->GetPosition() +
+ nsPoint(dx, 0));
+ childFrame = childFrame->GetNextSibling();
+ }
+ }
+ }
+ }
+
+ // Finished with these:
+ ClearSavedChildMetrics();
+ // Set our overflow area
+ GatherAndStoreOverflow(&aDesiredStretchSize);
+
+ // There used to be code here to change the height of the child frame to
+ // change the caret height, but the text frame that manages the caret is now
+ // not a direct child but wrapped in a block frame. See also bug 412033.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMathMLmoFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ // retain our native direction, it only changes if our text content changes
+ nsStretchDirection direction = mEmbellishData.direction;
+ nsMathMLTokenFrame::InheritAutomaticData(aParent);
+ ProcessTextData();
+ mEmbellishData.direction = direction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMathMLmoFrame::TransmitAutomaticData()
+{
+ // this will cause us to re-sync our flags from scratch
+ // but our returned 'form' is still not final (bug 133429), it will
+ // be recomputed to its final value during the next call in Reflow()
+ mEmbellishData.coreFrame = nullptr;
+ ProcessOperatorData();
+ return NS_OK;
+}
+
+void
+nsMathMLmoFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ // First, let the parent class do its work
+ nsMathMLTokenFrame::SetInitialChildList(aListID, aChildList);
+ ProcessTextData();
+}
+
+void
+nsMathMLmoFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ // certain values use units that depend on our style context, so
+ // it is safer to just process the whole lot here
+ ProcessOperatorData();
+
+ nsMathMLTokenFrame::Reflow(aPresContext, aDesiredSize,
+ aReflowInput, aStatus);
+}
+
+nsresult
+nsMathMLmoFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ nsresult rv = nsMathMLTokenFrame::Place(aDrawTarget, aPlaceOrigin, aDesiredSize);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ /* Special behaviour for largeops.
+ In MathML "stretchy" and displaystyle "largeop" are different notions,
+ even if we use the same technique to draw them (picking size variants).
+ So largeop display operators should be considered "non-stretchy" and
+ thus their sizes should be taken into account for the stretch size of
+ other elements.
+
+ This is a preliminary stretch - exact sizing/placement is handled by the
+ Stretch() method.
+ */
+
+ if (!aPlaceOrigin &&
+ StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_BLOCK &&
+ NS_MATHML_OPERATOR_IS_LARGEOP(mFlags) && UseMathMLChar()) {
+ nsBoundingMetrics newMetrics;
+ rv = mMathMLChar.Stretch(PresContext(), aDrawTarget,
+ nsLayoutUtils::FontSizeInflationFor(this),
+ NS_STRETCH_DIRECTION_VERTICAL,
+ aDesiredSize.mBoundingMetrics, newMetrics,
+ NS_STRETCH_LARGEOP, StyleVisibility()->mDirection);
+
+ if (NS_FAILED(rv)) {
+ // Just use the initial size
+ return NS_OK;
+ }
+
+ aDesiredSize.mBoundingMetrics = newMetrics;
+ /* Treat the ascent/descent values calculated in the TokenFrame place
+ calculations as the minimum for aDesiredSize calculations, rather
+ than fetching them from font metrics again.
+ */
+ aDesiredSize.SetBlockStartAscent(std::max(mBoundingMetrics.ascent,
+ newMetrics.ascent));
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() +
+ std::max(mBoundingMetrics.descent,
+ newMetrics.descent);
+ aDesiredSize.Width() = newMetrics.width;
+ mBoundingMetrics = newMetrics;
+ }
+ return NS_OK;
+}
+
+/* virtual */ void
+nsMathMLmoFrame::MarkIntrinsicISizesDirty()
+{
+ // if we get this, it may mean that something changed in the text
+ // content. So blow away everything an re-build the automatic data
+ // from the parent of our outermost embellished container (we ensure
+ // that we are the core, not just a sibling of the core)
+
+ ProcessTextData();
+
+ nsIFrame* target = this;
+ nsEmbellishData embellishData;
+ do {
+ target = target->GetParent();
+ GetEmbellishDataFrom(target, embellishData);
+ } while (embellishData.coreFrame == this);
+
+ // we have automatic data to update in the children of the target frame
+ // XXXldb This should really be marking dirty rather than rebuilding
+ // so that we don't rebuild multiple times for the same change.
+ RebuildAutomaticDataForChildren(target);
+
+ nsMathMLContainerFrame::MarkIntrinsicISizesDirty();
+}
+
+/* virtual */ void
+nsMathMLmoFrame::GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext,
+ ReflowOutput& aDesiredSize)
+{
+ ProcessOperatorData();
+ if (UseMathMLChar()) {
+ uint32_t stretchHint = GetStretchHint(mFlags, mPresentationData, true,
+ StyleFont());
+ aDesiredSize.Width() = mMathMLChar.
+ GetMaxWidth(PresContext(), aRenderingContext->GetDrawTarget(),
+ nsLayoutUtils::FontSizeInflationFor(this),
+ stretchHint);
+ }
+ else {
+ nsMathMLTokenFrame::GetIntrinsicISizeMetrics(aRenderingContext,
+ aDesiredSize);
+ }
+
+ // leadingSpace and trailingSpace are actually applied to the outermost
+ // embellished container but for determining total intrinsic width it should
+ // be safe to include it for the core here instead.
+ bool isRTL = StyleVisibility()->mDirection;
+ aDesiredSize.Width() +=
+ mEmbellishData.leadingSpace + mEmbellishData.trailingSpace;
+ aDesiredSize.mBoundingMetrics.width = aDesiredSize.Width();
+ if (isRTL) {
+ aDesiredSize.mBoundingMetrics.leftBearing += mEmbellishData.trailingSpace;
+ aDesiredSize.mBoundingMetrics.rightBearing += mEmbellishData.trailingSpace;
+ } else {
+ aDesiredSize.mBoundingMetrics.leftBearing += mEmbellishData.leadingSpace;
+ aDesiredSize.mBoundingMetrics.rightBearing += mEmbellishData.leadingSpace;
+ }
+}
+
+nsresult
+nsMathMLmoFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // check if this is an attribute that can affect the embellished hierarchy
+ // in a significant way and re-layout the entire hierarchy.
+ if (nsGkAtoms::accent_ == aAttribute ||
+ nsGkAtoms::movablelimits_ == aAttribute) {
+
+ // set the target as the parent of our outermost embellished container
+ // (we ensure that we are the core, not just a sibling of the core)
+ nsIFrame* target = this;
+ nsEmbellishData embellishData;
+ do {
+ target = target->GetParent();
+ GetEmbellishDataFrom(target, embellishData);
+ } while (embellishData.coreFrame == this);
+
+ // we have automatic data to update in the children of the target frame
+ return ReLayoutChildren(target);
+ }
+
+ return nsMathMLTokenFrame::
+ AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+// ----------------------
+// No need to track the style context given to our MathML char.
+// the Style System will use these to pass the proper style context to our MathMLChar
+nsStyleContext*
+nsMathMLmoFrame::GetAdditionalStyleContext(int32_t aIndex) const
+{
+ switch (aIndex) {
+ case NS_MATHML_CHAR_STYLE_CONTEXT_INDEX:
+ return mMathMLChar.GetStyleContext();
+ default:
+ return nullptr;
+ }
+}
+
+void
+nsMathMLmoFrame::SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext)
+{
+ switch (aIndex) {
+ case NS_MATHML_CHAR_STYLE_CONTEXT_INDEX:
+ mMathMLChar.SetStyleContext(aStyleContext);
+ break;
+ }
+}
diff --git a/layout/mathml/nsMathMLmoFrame.h b/layout/mathml/nsMathMLmoFrame.h
new file mode 100644
index 000000000..f25107a20
--- /dev/null
+++ b/layout/mathml/nsMathMLmoFrame.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmoFrame_h___
+#define nsMathMLmoFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLTokenFrame.h"
+#include "nsMathMLChar.h"
+
+//
+// <mo> -- operator, fence, or separator
+//
+
+class nsMathMLmoFrame : public nsMathMLTokenFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmoFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual eMathMLFrameType GetMathMLFrameType() override;
+
+ virtual void
+ SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext) override;
+ virtual nsStyleContext*
+ GetAdditionalStyleContext(int32_t aIndex) const override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ virtual void
+ SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual void MarkIntrinsicISizesDirty() override;
+
+ virtual void
+ GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ // This method is called by the parent frame to ask <mo>
+ // to stretch itself.
+ NS_IMETHOD
+ Stretch(DrawTarget* aDrawTarget,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aContainerSize,
+ ReflowOutput& aDesiredStretchSize) override;
+
+ virtual nsresult
+ ChildListChanged(int32_t aModType) override
+ {
+ ProcessTextData();
+ return nsMathMLContainerFrame::ChildListChanged(aModType);
+ }
+
+protected:
+ explicit nsMathMLmoFrame(nsStyleContext* aContext) : nsMathMLTokenFrame(aContext) {}
+ virtual ~nsMathMLmoFrame();
+
+ nsMathMLChar mMathMLChar; // Here is the MathMLChar that will deal with the operator.
+ nsOperatorFlags mFlags;
+ float mMinSize;
+ float mMaxSize;
+
+ bool UseMathMLChar();
+
+ // overload the base method so that we can setup our nsMathMLChar
+ void ProcessTextData();
+
+ // helper to get our 'form' and lookup in the Operator Dictionary to fetch
+ // our default data that may come from there, and to complete the setup
+ // using attributes that we may have
+ void
+ ProcessOperatorData();
+
+ // helper to double check thar our char should be rendered as a selected char
+ bool
+ IsFrameInSelection(nsIFrame* aFrame);
+};
+
+#endif /* nsMathMLmoFrame_h___ */
diff --git a/layout/mathml/nsMathMLmpaddedFrame.cpp b/layout/mathml/nsMathMLmpaddedFrame.cpp
new file mode 100644
index 000000000..5b2e8282f
--- /dev/null
+++ b/layout/mathml/nsMathMLmpaddedFrame.cpp
@@ -0,0 +1,449 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsMathMLmpaddedFrame.h"
+#include "nsMathMLElement.h"
+#include "mozilla/gfx/2D.h"
+#include <algorithm>
+
+//
+// <mpadded> -- adjust space around content - implementation
+//
+
+#define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there
+#define NS_MATHML_SIGN_UNSPECIFIED 0
+#define NS_MATHML_SIGN_MINUS 1
+#define NS_MATHML_SIGN_PLUS 2
+
+#define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
+#define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special
+#define NS_MATHML_PSEUDO_UNIT_WIDTH 2
+#define NS_MATHML_PSEUDO_UNIT_HEIGHT 3
+#define NS_MATHML_PSEUDO_UNIT_DEPTH 4
+#define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 5
+
+nsIFrame*
+NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmpaddedFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
+
+nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
+{
+}
+
+NS_IMETHODIMP
+nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ // let the base class get the default from our parent
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
+
+ return NS_OK;
+}
+
+void
+nsMathMLmpaddedFrame::ProcessAttributes()
+{
+ /*
+ parse the attributes
+
+ width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
+ height = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
+ depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
+ lspace = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
+ voffset= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
+ */
+
+ nsAutoString value;
+
+ // width
+ mWidthSign = NS_MATHML_SIGN_INVALID;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit)) {
+ ReportParseError(nsGkAtoms::width->GetUTF16String(), value.get());
+ }
+ }
+
+ // height
+ mHeightSign = NS_MATHML_SIGN_INVALID;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit)) {
+ ReportParseError(nsGkAtoms::height->GetUTF16String(), value.get());
+ }
+ }
+
+ // depth
+ mDepthSign = NS_MATHML_SIGN_INVALID;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::depth_, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit)) {
+ ReportParseError(nsGkAtoms::depth_->GetUTF16String(), value.get());
+ }
+ }
+
+ // lspace
+ mLeadingSpaceSign = NS_MATHML_SIGN_INVALID;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mLeadingSpaceSign, mLeadingSpace,
+ mLeadingSpacePseudoUnit)) {
+ ReportParseError(nsGkAtoms::lspace_->GetUTF16String(), value.get());
+ }
+ }
+
+ // voffset
+ mVerticalOffsetSign = NS_MATHML_SIGN_INVALID;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::voffset_, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mVerticalOffsetSign, mVerticalOffset,
+ mVerticalOffsetPseudoUnit)) {
+ ReportParseError(nsGkAtoms::voffset_->GetUTF16String(), value.get());
+ }
+ }
+
+}
+
+// parse an input string in the following format (see bug 148326 for testcases):
+// [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
+bool
+nsMathMLmpaddedFrame::ParseAttribute(nsString& aString,
+ int32_t& aSign,
+ nsCSSValue& aCSSValue,
+ int32_t& aPseudoUnit)
+{
+ aCSSValue.Reset();
+ aSign = NS_MATHML_SIGN_INVALID;
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED;
+ aString.CompressWhitespace(); // aString is not a const in this code
+
+ int32_t stringLength = aString.Length();
+ if (!stringLength)
+ return false;
+
+ nsAutoString number, unit;
+
+ //////////////////////
+ // see if the sign is there
+
+ int32_t i = 0;
+
+ if (aString[0] == '+') {
+ aSign = NS_MATHML_SIGN_PLUS;
+ i++;
+ }
+ else if (aString[0] == '-') {
+ aSign = NS_MATHML_SIGN_MINUS;
+ i++;
+ }
+ else
+ aSign = NS_MATHML_SIGN_UNSPECIFIED;
+
+ // get the number
+ bool gotDot = false, gotPercent = false;
+ for (; i < stringLength; i++) {
+ char16_t c = aString[i];
+ if (gotDot && c == '.') {
+ // error - two dots encountered
+ aSign = NS_MATHML_SIGN_INVALID;
+ return false;
+ }
+
+ if (c == '.')
+ gotDot = true;
+ else if (!nsCRT::IsAsciiDigit(c)) {
+ break;
+ }
+ number.Append(c);
+ }
+
+ // catch error if we didn't enter the loop above... we could simply initialize
+ // floatValue = 1, to cater for cases such as width="height", but that wouldn't
+ // be in line with the spec which requires an explicit number
+ if (number.IsEmpty()) {
+ aSign = NS_MATHML_SIGN_INVALID;
+ return false;
+ }
+
+ nsresult errorCode;
+ float floatValue = number.ToFloat(&errorCode);
+ if (NS_FAILED(errorCode)) {
+ aSign = NS_MATHML_SIGN_INVALID;
+ return false;
+ }
+
+ // see if this is a percentage-based value
+ if (i < stringLength && aString[i] == '%') {
+ i++;
+ gotPercent = true;
+ }
+
+ // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
+ aString.Right(unit, stringLength - i);
+
+ if (unit.IsEmpty()) {
+ if (gotPercent) {
+ // case ["+"|"-"] unsigned-number "%"
+ aCSSValue.SetPercentValue(floatValue / 100.0f);
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
+ return true;
+ } else {
+ // case ["+"|"-"] unsigned-number
+ // XXXfredw: should we allow non-zero unitless values? See bug 757703.
+ if (!floatValue) {
+ aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
+ return true;
+ }
+ }
+ }
+ else if (unit.EqualsLiteral("width")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH;
+ else if (unit.EqualsLiteral("height")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT;
+ else if (unit.EqualsLiteral("depth")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH;
+ else if (!gotPercent) { // percentage can only apply to a pseudo-unit
+
+ // see if the unit is a named-space
+ if (nsMathMLElement::ParseNamedSpaceValue(unit, aCSSValue,
+ nsMathMLElement::
+ PARSE_ALLOW_NEGATIVE)) {
+ // re-scale properly, and we know that the unit of the named-space is 'em'
+ floatValue *= aCSSValue.GetFloatValue();
+ aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM);
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE;
+ return true;
+ }
+
+ // see if the input was just a CSS value
+ // We are not supposed to have a unitless, percent, negative or namedspace
+ // value here.
+ number.Append(unit); // leave the sign out if it was there
+ if (nsMathMLElement::ParseNumericValue(number, aCSSValue,
+ nsMathMLElement::
+ PARSE_SUPPRESS_WARNINGS, nullptr))
+ return true;
+ }
+
+ // if we enter here, we have a number that will act as a multiplier on a pseudo-unit
+ if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
+ if (gotPercent)
+ aCSSValue.SetPercentValue(floatValue / 100.0f);
+ else
+ aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
+
+ return true;
+ }
+
+
+#ifdef DEBUG
+ printf("mpadded: attribute with bad numeric value: %s\n",
+ NS_LossyConvertUTF16toASCII(aString).get());
+#endif
+ // if we reach here, it means we encounter an unexpected input
+ aSign = NS_MATHML_SIGN_INVALID;
+ return false;
+}
+
+void
+nsMathMLmpaddedFrame::UpdateValue(int32_t aSign,
+ int32_t aPseudoUnit,
+ const nsCSSValue& aCSSValue,
+ const ReflowOutput& aDesiredSize,
+ nscoord& aValueToUpdate,
+ float aFontSizeInflation) const
+{
+ nsCSSUnit unit = aCSSValue.GetUnit();
+ if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
+ nscoord scaler = 0, amount = 0;
+
+ if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
+ switch(aPseudoUnit) {
+ case NS_MATHML_PSEUDO_UNIT_WIDTH:
+ scaler = aDesiredSize.Width();
+ break;
+
+ case NS_MATHML_PSEUDO_UNIT_HEIGHT:
+ scaler = aDesiredSize.BlockStartAscent();
+ break;
+
+ case NS_MATHML_PSEUDO_UNIT_DEPTH:
+ scaler = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
+ break;
+
+ default:
+ // if we ever reach here, it would mean something is wrong
+ // somewhere with the setup and/or the caller
+ NS_ERROR("Unexpected Pseudo Unit");
+ return;
+ }
+ }
+
+ if (eCSSUnit_Number == unit)
+ amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
+ else if (eCSSUnit_Percent == unit)
+ amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
+ else
+ amount = CalcLength(PresContext(), mStyleContext, aCSSValue,
+ aFontSizeInflation);
+
+ if (NS_MATHML_SIGN_PLUS == aSign)
+ aValueToUpdate += amount;
+ else if (NS_MATHML_SIGN_MINUS == aSign)
+ aValueToUpdate -= amount;
+ else
+ aValueToUpdate = amount;
+ }
+}
+
+void
+nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ ProcessAttributes();
+
+ ///////////////
+ // Let the base class format our content like an inferred mrow
+ nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize,
+ aReflowInput, aStatus);
+ //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
+}
+
+/* virtual */ nsresult
+nsMathMLmpaddedFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ nsresult rv =
+ nsMathMLContainerFrame::Place(aDrawTarget, false, aDesiredSize);
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
+ DidReflowChildren(PrincipalChildList().FirstChild());
+ return rv;
+ }
+
+ nscoord height = aDesiredSize.BlockStartAscent();
+ nscoord depth = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
+ // The REC says:
+ //
+ // "The lspace attribute ('leading' space) specifies the horizontal location
+ // of the positioning point of the child content with respect to the
+ // positioning point of the mpadded element. By default they coincide, and
+ // therefore absolute values for lspace have the same effect as relative
+ // values."
+ //
+ // "MathML renderers should ensure that, except for the effects of the
+ // attributes, the relative spacing between the contents of the mpadded
+ // element and surrounding MathML elements would not be modified by replacing
+ // an mpadded element with an mrow element with the same content, even if
+ // linebreaking occurs within the mpadded element."
+ //
+ // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
+ //
+ // "In those discussions, the terms leading and trailing are used to specify
+ // a side of an object when which side to use depends on the directionality;
+ // ie. leading means left in LTR but right in RTL."
+ // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
+ nscoord lspace = 0;
+ // In MathML3, "width" will be the bounding box width and "advancewidth" will
+ // refer "to the horizontal distance between the positioning point of the
+ // mpadded and the positioning point for the following content". MathML2
+ // doesn't make the distinction.
+ nscoord width = aDesiredSize.Width();
+ nscoord voffset = 0;
+
+ int32_t pseudoUnit;
+ nscoord initialWidth = width;
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+
+ // update width
+ pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
+ ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit;
+ UpdateValue(mWidthSign, pseudoUnit, mWidth,
+ aDesiredSize, width, fontSizeInflation);
+ width = std::max(0, width);
+
+ // update "height" (this is the ascent in the terminology of the REC)
+ pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
+ ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit;
+ UpdateValue(mHeightSign, pseudoUnit, mHeight,
+ aDesiredSize, height, fontSizeInflation);
+ height = std::max(0, height);
+
+ // update "depth" (this is the descent in the terminology of the REC)
+ pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
+ ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit;
+ UpdateValue(mDepthSign, pseudoUnit, mDepth,
+ aDesiredSize, depth, fontSizeInflation);
+ depth = std::max(0, depth);
+
+ // update lspace
+ if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
+ pseudoUnit = mLeadingSpacePseudoUnit;
+ UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace,
+ aDesiredSize, lspace, fontSizeInflation);
+ }
+
+ // update voffset
+ if (mVerticalOffsetPseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
+ pseudoUnit = mVerticalOffsetPseudoUnit;
+ UpdateValue(mVerticalOffsetSign, pseudoUnit, mVerticalOffset,
+ aDesiredSize, voffset, fontSizeInflation);
+ }
+ // do the padding now that we have everything
+ // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
+ // with no attributes) looks the same as <mrow>...</mrow>. But when there are
+ // attributes, tweak our metrics and move children to achieve the desired visual
+ // effects.
+
+ if ((StyleVisibility()->mDirection ?
+ mWidthSign : mLeadingSpaceSign) != NS_MATHML_SIGN_INVALID) {
+ // there was padding on the left. dismiss the left italic correction now
+ // (so that our parent won't correct us)
+ mBoundingMetrics.leftBearing = 0;
+ }
+
+ if ((StyleVisibility()->mDirection ?
+ mLeadingSpaceSign : mWidthSign) != NS_MATHML_SIGN_INVALID) {
+ // there was padding on the right. dismiss the right italic correction now
+ // (so that our parent won't correct us)
+ mBoundingMetrics.width = width;
+ mBoundingMetrics.rightBearing = mBoundingMetrics.width;
+ }
+
+ nscoord dx = (StyleVisibility()->mDirection ?
+ width - initialWidth - lspace : lspace);
+
+ aDesiredSize.SetBlockStartAscent(height);
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.Height() = depth + aDesiredSize.BlockStartAscent();
+ mBoundingMetrics.ascent = height;
+ mBoundingMetrics.descent = depth;
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ if (aPlaceOrigin) {
+ // Finish reflowing child frames, positioning their origins.
+ PositionRowChildFrames(dx, aDesiredSize.BlockStartAscent() - voffset);
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsresult
+nsMathMLmpaddedFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize)
+{
+ ProcessAttributes();
+ return Place(aDrawTarget, false, aDesiredSize);
+}
diff --git a/layout/mathml/nsMathMLmpaddedFrame.h b/layout/mathml/nsMathMLmpaddedFrame.h
new file mode 100644
index 000000000..ef76a9c7a
--- /dev/null
+++ b/layout/mathml/nsMathMLmpaddedFrame.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmpaddedFrame_h___
+#define nsMathMLmpaddedFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+//
+// <mpadded> -- adjust space around content
+//
+
+class nsMathMLmpaddedFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override {
+ return TransmitAutomaticDataForMrowLikeElement();
+ }
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ bool
+ IsMrowLike() override {
+ return mFrames.FirstChild() != mFrames.LastChild() ||
+ !mFrames.FirstChild();
+ }
+
+protected:
+ explicit nsMathMLmpaddedFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext) {}
+ virtual ~nsMathMLmpaddedFrame();
+
+ virtual nsresult
+ MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) override;
+
+private:
+ nsCSSValue mWidth;
+ nsCSSValue mHeight;
+ nsCSSValue mDepth;
+ nsCSSValue mLeadingSpace;
+ nsCSSValue mVerticalOffset;
+
+ int32_t mWidthSign;
+ int32_t mHeightSign;
+ int32_t mDepthSign;
+ int32_t mLeadingSpaceSign;
+ int32_t mVerticalOffsetSign;
+
+ int32_t mWidthPseudoUnit;
+ int32_t mHeightPseudoUnit;
+ int32_t mDepthPseudoUnit;
+ int32_t mLeadingSpacePseudoUnit;
+ int32_t mVerticalOffsetPseudoUnit;
+
+ // helpers to process the attributes
+ void
+ ProcessAttributes();
+
+ static bool
+ ParseAttribute(nsString& aString,
+ int32_t& aSign,
+ nsCSSValue& aCSSValue,
+ int32_t& aPseudoUnit);
+
+ void
+ UpdateValue(int32_t aSign,
+ int32_t aPseudoUnit,
+ const nsCSSValue& aCSSValue,
+ const ReflowOutput& aDesiredSize,
+ nscoord& aValueToUpdate,
+ float aFontSizeInflation) const;
+};
+
+#endif /* nsMathMLmpaddedFrame_h___ */
diff --git a/layout/mathml/nsMathMLmrootFrame.cpp b/layout/mathml/nsMathMLmrootFrame.cpp
new file mode 100644
index 000000000..4c81bde3d
--- /dev/null
+++ b/layout/mathml/nsMathMLmrootFrame.cpp
@@ -0,0 +1,419 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmrootFrame.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include <algorithm>
+#include "gfxMathTable.h"
+
+using namespace mozilla;
+
+//
+// <mroot> -- form a radical - implementation
+//
+
+// additional style context to be used by our MathMLChar.
+#define NS_SQR_CHAR_STYLE_CONTEXT_INDEX 0
+
+static const char16_t kSqrChar = char16_t(0x221A);
+
+nsIFrame*
+NS_NewMathMLmrootFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmrootFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame)
+
+nsMathMLmrootFrame::nsMathMLmrootFrame(nsStyleContext* aContext) :
+ nsMathMLContainerFrame(aContext),
+ mSqrChar(),
+ mBarRect()
+{
+}
+
+nsMathMLmrootFrame::~nsMathMLmrootFrame()
+{
+}
+
+void
+nsMathMLmrootFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ nsPresContext *presContext = PresContext();
+
+ // No need to track the style context given to our MathML char.
+ // The Style System will use Get/SetAdditionalStyleContext() to keep it
+ // up-to-date if dynamic changes arise.
+ nsAutoString sqrChar; sqrChar.Assign(kSqrChar);
+ mSqrChar.SetData(sqrChar);
+ ResolveMathMLCharStyle(presContext, mContent, mStyleContext, &mSqrChar);
+}
+
+NS_IMETHODIMP
+nsMathMLmrootFrame::TransmitAutomaticData()
+{
+ // 1. The REC says:
+ // The <mroot> element increments scriptlevel by 2, and sets displaystyle to
+ // "false", within index, but leaves both attributes unchanged within base.
+ // 2. The TeXbook (Ch 17. p.141) says \sqrt is compressed
+ UpdatePresentationDataFromChildAt(1, 1,
+ NS_MATHML_COMPRESSED,
+ NS_MATHML_COMPRESSED);
+ UpdatePresentationDataFromChildAt(0, 0,
+ NS_MATHML_COMPRESSED, NS_MATHML_COMPRESSED);
+
+ PropagateFrameFlagFor(mFrames.LastChild(),
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+
+ return NS_OK;
+}
+
+void
+nsMathMLmrootFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ /////////////
+ // paint the content we are square-rooting
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ /////////////
+ // paint the sqrt symbol
+ if (!NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
+ mSqrChar.Display(aBuilder, this, aLists, 0);
+
+ DisplayBar(aBuilder, this, mBarRect, aLists);
+
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ // for visual debug
+ nsRect rect;
+ mSqrChar.GetRect(rect);
+ nsBoundingMetrics bm;
+ mSqrChar.GetBoundingMetrics(bm);
+ DisplayBoundingMetrics(aBuilder, this, rect.TopLeft(), bm, aLists);
+#endif
+ }
+}
+
+void
+nsMathMLmrootFrame::GetRadicalXOffsets(nscoord aIndexWidth, nscoord aSqrWidth,
+ nsFontMetrics* aFontMetrics,
+ nscoord* aIndexOffset,
+ nscoord* aSqrOffset)
+{
+ // The index is tucked in closer to the radical while making sure
+ // that the kern does not make the index and radical collide
+ nscoord dxIndex, dxSqr;
+ nscoord xHeight = aFontMetrics->XHeight();
+ nscoord indexRadicalKern = NSToCoordRound(1.35f * xHeight);
+ nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel();
+ gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
+ if (mathFont) {
+ indexRadicalKern =
+ mathFont->MathTable()->Constant(gfxMathTable::RadicalKernAfterDegree,
+ oneDevPixel);
+ indexRadicalKern = -indexRadicalKern;
+ }
+ if (indexRadicalKern > aIndexWidth) {
+ dxIndex = indexRadicalKern - aIndexWidth;
+ dxSqr = 0;
+ }
+ else {
+ dxIndex = 0;
+ dxSqr = aIndexWidth - indexRadicalKern;
+ }
+
+ if (mathFont) {
+ // add some kern before the radical index
+ nscoord indexRadicalKernBefore = 0;
+ indexRadicalKernBefore =
+ mathFont->MathTable()->Constant(gfxMathTable::RadicalKernBeforeDegree,
+ oneDevPixel);
+ dxIndex += indexRadicalKernBefore;
+ dxSqr += indexRadicalKernBefore;
+ } else {
+ // avoid collision by leaving a minimum space between index and radical
+ nscoord minimumClearance = aSqrWidth / 2;
+ if (dxIndex + aIndexWidth + minimumClearance > dxSqr + aSqrWidth) {
+ if (aIndexWidth + minimumClearance < aSqrWidth) {
+ dxIndex = aSqrWidth - (aIndexWidth + minimumClearance);
+ dxSqr = 0;
+ }
+ else {
+ dxIndex = 0;
+ dxSqr = (aIndexWidth + minimumClearance) - aSqrWidth;
+ }
+ }
+ }
+
+ if (aIndexOffset)
+ *aIndexOffset = dxIndex;
+ if (aSqrOffset)
+ *aSqrOffset = dxSqr;
+}
+
+void
+nsMathMLmrootFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ nsReflowStatus childStatus;
+
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ aDesiredSize.ClearSize();
+ aDesiredSize.SetBlockStartAscent(0);
+
+ nsBoundingMetrics bmSqr, bmBase, bmIndex;
+ DrawTarget* drawTarget = aReflowInput.mRenderingContext->GetDrawTarget();
+
+ //////////////////
+ // Reflow Children
+
+ int32_t count = 0;
+ nsIFrame* baseFrame = nullptr;
+ nsIFrame* indexFrame = nullptr;
+ ReflowOutput baseSize(aReflowInput);
+ ReflowOutput indexSize(aReflowInput);
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ // ask our children to compute their bounding metrics
+ ReflowOutput childDesiredSize(aReflowInput,
+ aDesiredSize.mFlags
+ | NS_REFLOW_CALC_BOUNDING_METRICS);
+ WritingMode wm = childFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput childReflowInput(aPresContext, aReflowInput,
+ childFrame, availSize);
+ ReflowChild(childFrame, aPresContext,
+ childDesiredSize, childReflowInput, childStatus);
+ //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status");
+ if (0 == count) {
+ // base
+ baseFrame = childFrame;
+ baseSize = childDesiredSize;
+ bmBase = childDesiredSize.mBoundingMetrics;
+ }
+ else if (1 == count) {
+ // index
+ indexFrame = childFrame;
+ indexSize = childDesiredSize;
+ bmIndex = childDesiredSize.mBoundingMetrics;
+ }
+ count++;
+ childFrame = childFrame->GetNextSibling();
+ }
+ if (2 != count) {
+ // report an error, encourage people to get their markups in order
+ ReportChildCountError();
+ ReflowError(drawTarget, aDesiredSize);
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+ // Call DidReflow() for the child frames we successfully did reflow.
+ DidReflowChildren(mFrames.FirstChild(), childFrame);
+ return;
+ }
+
+ ////////////
+ // Prepare the radical symbol and the overline bar
+
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+
+ nscoord ruleThickness, leading, psi;
+ GetRadicalParameters(fm, StyleFont()->mMathDisplay ==
+ NS_MATHML_DISPLAYSTYLE_BLOCK,
+ ruleThickness, leading, psi);
+
+ // built-in: adjust clearance psi to emulate \mathstrut using '1' (TexBook, p.131)
+ char16_t one = '1';
+ nsBoundingMetrics bmOne =
+ nsLayoutUtils::AppUnitBoundsOfString(&one, 1, *fm, drawTarget);
+ if (bmOne.ascent > bmBase.ascent)
+ psi += bmOne.ascent - bmBase.ascent;
+
+ // make sure that the rule appears on on screen
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+ if (ruleThickness < onePixel) {
+ ruleThickness = onePixel;
+ }
+
+ // adjust clearance psi to get an exact number of pixels -- this
+ // gives a nicer & uniform look on stacked radicals (bug 130282)
+ nscoord delta = psi % onePixel;
+ if (delta)
+ psi += onePixel - delta; // round up
+
+ // Stretch the radical symbol to the appropriate height if it is not big enough.
+ nsBoundingMetrics contSize = bmBase;
+ contSize.descent = bmBase.ascent + bmBase.descent + psi;
+ contSize.ascent = ruleThickness;
+
+ // height(radical) should be >= height(base) + psi + ruleThickness
+ nsBoundingMetrics radicalSize;
+ mSqrChar.Stretch(aPresContext, drawTarget,
+ fontSizeInflation,
+ NS_STRETCH_DIRECTION_VERTICAL,
+ contSize, radicalSize,
+ NS_STRETCH_LARGER,
+ StyleVisibility()->mDirection);
+ // radicalSize have changed at this point, and should match with
+ // the bounding metrics of the char
+ mSqrChar.GetBoundingMetrics(bmSqr);
+
+ // Update the desired size for the container (like msqrt, index is not yet included)
+ // the baseline will be that of the base.
+ mBoundingMetrics.ascent = bmBase.ascent + psi + ruleThickness;
+ mBoundingMetrics.descent =
+ std::max(bmBase.descent,
+ (bmSqr.ascent + bmSqr.descent - mBoundingMetrics.ascent));
+ mBoundingMetrics.width = bmSqr.width + bmBase.width;
+ mBoundingMetrics.leftBearing = bmSqr.leftBearing;
+ mBoundingMetrics.rightBearing = bmSqr.width +
+ std::max(bmBase.width, bmBase.rightBearing); // take also care of the rule
+
+ aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading);
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() +
+ std::max(baseSize.Height() - baseSize.BlockStartAscent(),
+ mBoundingMetrics.descent + ruleThickness);
+ aDesiredSize.Width() = mBoundingMetrics.width;
+
+ /////////////
+ // Re-adjust the desired size to include the index.
+
+ // the index is raised by some fraction of the height
+ // of the radical, see \mroot macro in App. B, TexBook
+ float raiseIndexPercent = 0.6f;
+ gfxFont* mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
+ if (mathFont) {
+ raiseIndexPercent = mathFont->MathTable()->
+ Constant(gfxMathTable::RadicalDegreeBottomRaisePercent);
+ }
+ nscoord raiseIndexDelta = NSToCoordRound(raiseIndexPercent *
+ (bmSqr.ascent + bmSqr.descent));
+ nscoord indexRaisedAscent = mBoundingMetrics.ascent // top of radical
+ - (bmSqr.ascent + bmSqr.descent) // to bottom of radical
+ + raiseIndexDelta + bmIndex.ascent + bmIndex.descent; // to top of raised index
+
+ nscoord indexClearance = 0;
+ if (mBoundingMetrics.ascent < indexRaisedAscent) {
+ indexClearance =
+ indexRaisedAscent - mBoundingMetrics.ascent; // excess gap introduced by a tall index
+ mBoundingMetrics.ascent = indexRaisedAscent;
+ nscoord descent = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
+ aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading);
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + descent;
+ }
+
+ nscoord dxIndex, dxSqr;
+ GetRadicalXOffsets(bmIndex.width, bmSqr.width, fm, &dxIndex, &dxSqr);
+
+ mBoundingMetrics.width = dxSqr + bmSqr.width + bmBase.width;
+ mBoundingMetrics.leftBearing =
+ std::min(dxIndex + bmIndex.leftBearing, dxSqr + bmSqr.leftBearing);
+ mBoundingMetrics.rightBearing = dxSqr + bmSqr.width +
+ std::max(bmBase.width, bmBase.rightBearing);
+
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+ GatherAndStoreOverflow(&aDesiredSize);
+
+ // place the index
+ nscoord dx = dxIndex;
+ nscoord dy = aDesiredSize.BlockStartAscent() -
+ (indexRaisedAscent + indexSize.BlockStartAscent() - bmIndex.ascent);
+ FinishReflowChild(indexFrame, aPresContext, indexSize, nullptr,
+ MirrorIfRTL(aDesiredSize.Width(), indexSize.Width(), dx),
+ dy, 0);
+
+ // place the radical symbol and the radical bar
+ dx = dxSqr;
+ dy = indexClearance + leading; // leave a leading at the top
+ mSqrChar.SetRect(nsRect(MirrorIfRTL(aDesiredSize.Width(), bmSqr.width, dx),
+ dy, bmSqr.width, bmSqr.ascent + bmSqr.descent));
+ dx += bmSqr.width;
+ mBarRect.SetRect(MirrorIfRTL(aDesiredSize.Width(), bmBase.width, dx),
+ dy, bmBase.width, ruleThickness);
+
+ // place the base
+ dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent();
+ FinishReflowChild(baseFrame, aPresContext, baseSize, nullptr,
+ MirrorIfRTL(aDesiredSize.Width(), baseSize.Width(), dx),
+ dy, 0);
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+/* virtual */ void
+nsMathMLmrootFrame::GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext, ReflowOutput& aDesiredSize)
+{
+ nsIFrame* baseFrame = mFrames.FirstChild();
+ nsIFrame* indexFrame = nullptr;
+ if (baseFrame)
+ indexFrame = baseFrame->GetNextSibling();
+ if (!indexFrame || indexFrame->GetNextSibling()) {
+ ReflowError(aRenderingContext->GetDrawTarget(), aDesiredSize);
+ return;
+ }
+
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ nscoord baseWidth =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext, baseFrame,
+ nsLayoutUtils::PREF_ISIZE);
+ nscoord indexWidth =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext, indexFrame,
+ nsLayoutUtils::PREF_ISIZE);
+ nscoord sqrWidth = mSqrChar.GetMaxWidth(PresContext(),
+ aRenderingContext->GetDrawTarget(),
+ fontSizeInflation);
+
+ nscoord dxSqr;
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+ GetRadicalXOffsets(indexWidth, sqrWidth, fm, nullptr, &dxSqr);
+
+ nscoord width = dxSqr + sqrWidth + baseWidth;
+
+ aDesiredSize.Width() = width;
+ aDesiredSize.mBoundingMetrics.width = width;
+ aDesiredSize.mBoundingMetrics.leftBearing = 0;
+ aDesiredSize.mBoundingMetrics.rightBearing = width;
+}
+
+// ----------------------
+// the Style System will use these to pass the proper style context to our MathMLChar
+nsStyleContext*
+nsMathMLmrootFrame::GetAdditionalStyleContext(int32_t aIndex) const
+{
+ switch (aIndex) {
+ case NS_SQR_CHAR_STYLE_CONTEXT_INDEX:
+ return mSqrChar.GetStyleContext();
+ default:
+ return nullptr;
+ }
+}
+
+void
+nsMathMLmrootFrame::SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext)
+{
+ switch (aIndex) {
+ case NS_SQR_CHAR_STYLE_CONTEXT_INDEX:
+ mSqrChar.SetStyleContext(aStyleContext);
+ break;
+ }
+}
diff --git a/layout/mathml/nsMathMLmrootFrame.h b/layout/mathml/nsMathMLmrootFrame.h
new file mode 100644
index 000000000..0996af26b
--- /dev/null
+++ b/layout/mathml/nsMathMLmrootFrame.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmrootFrame_h___
+#define nsMathMLmrootFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+#include "nsMathMLChar.h"
+
+//
+// <msqrt> and <mroot> -- form a radical
+//
+
+class nsMathMLmrootFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmrootFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual void
+ SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext) override;
+ virtual nsStyleContext*
+ GetAdditionalStyleContext(int32_t aIndex) const override;
+
+ virtual void
+ Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void
+ GetRadicalXOffsets(nscoord aIndexWidth, nscoord aSqrWidth,
+ nsFontMetrics* aFontMetrics,
+ nscoord* aIndexOffset,
+ nscoord* aSqrOffset);
+
+ virtual void
+ GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ uint8_t
+ ScriptIncrement(nsIFrame* aFrame) override
+ {
+ return (aFrame && aFrame == mFrames.LastChild()) ? 2 : 0;
+ }
+
+protected:
+ explicit nsMathMLmrootFrame(nsStyleContext* aContext);
+ virtual ~nsMathMLmrootFrame();
+
+ nsMathMLChar mSqrChar;
+ nsRect mBarRect;
+};
+
+#endif /* nsMathMLmrootFrame_h___ */
diff --git a/layout/mathml/nsMathMLmrowFrame.cpp b/layout/mathml/nsMathMLmrowFrame.cpp
new file mode 100644
index 000000000..4fe3bbafb
--- /dev/null
+++ b/layout/mathml/nsMathMLmrowFrame.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmrowFrame.h"
+#include "mozilla/gfx/2D.h"
+
+//
+// <mrow> -- horizontally group any number of subexpressions - implementation
+//
+
+nsIFrame*
+NS_NewMathMLmrowFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmrowFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrowFrame)
+
+nsMathMLmrowFrame::~nsMathMLmrowFrame()
+{
+}
+
+NS_IMETHODIMP
+nsMathMLmrowFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ // let the base class get the default from our parent
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
+
+ return NS_OK;
+}
+
+nsresult
+nsMathMLmrowFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // Special for <mtable>: In the frame construction code, we also use
+ // this frame class as a wrapper for mtable. Hence, we should pass the
+ // notification to the real mtable
+ if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
+ nsIFrame* frame = mFrames.FirstChild();
+ for ( ; frame; frame = frame->PrincipalChildList().FirstChild()) {
+ // drill down to the real mtable
+ if (frame->GetType() == nsGkAtoms::tableWrapperFrame)
+ return frame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ }
+ NS_NOTREACHED("mtable wrapper without the real table frame");
+ }
+
+ return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+/* virtual */ eMathMLFrameType
+nsMathMLmrowFrame::GetMathMLFrameType()
+{
+ if (!IsMrowLike()) {
+ nsIMathMLFrame* child = do_QueryFrame(mFrames.FirstChild());
+ if (child) {
+ // We only have one child, so we return the frame type of that child as if
+ // we didn't exist.
+ return child->GetMathMLFrameType();
+ }
+ }
+ return nsMathMLFrame::GetMathMLFrameType();
+}
diff --git a/layout/mathml/nsMathMLmrowFrame.h b/layout/mathml/nsMathMLmrowFrame.h
new file mode 100644
index 000000000..5aab800f5
--- /dev/null
+++ b/layout/mathml/nsMathMLmrowFrame.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmrowFrame_h___
+#define nsMathMLmrowFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+//
+// <mrow> -- horizontally group any number of subexpressions
+// <mphantom> -- make content invisible but preserve its size
+// <mstyle> -- make style changes that affect the rendering of its contents
+//
+
+class nsMathMLmrowFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmrowFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override {
+ return TransmitAutomaticDataForMrowLikeElement();
+ }
+
+ virtual eMathMLFrameType
+ GetMathMLFrameType() override;
+
+ bool
+ IsMrowLike() override {
+ // <mrow> elements with a single child are treated identically to the case
+ // where the child wasn't within an mrow, so we pretend the mrow isn't an
+ // mrow in that situation.
+ return mFrames.FirstChild() != mFrames.LastChild() ||
+ !mFrames.FirstChild();
+ }
+
+protected:
+ explicit nsMathMLmrowFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext) {}
+ virtual ~nsMathMLmrowFrame();
+};
+
+#endif /* nsMathMLmrowFrame_h___ */
diff --git a/layout/mathml/nsMathMLmspaceFrame.cpp b/layout/mathml/nsMathMLmspaceFrame.cpp
new file mode 100644
index 000000000..54f2f325a
--- /dev/null
+++ b/layout/mathml/nsMathMLmspaceFrame.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmspaceFrame.h"
+#include "nsMathMLElement.h"
+#include "mozilla/gfx/2D.h"
+#include <algorithm>
+
+
+//
+// <mspace> -- space - implementation
+//
+
+nsIFrame*
+NS_NewMathMLmspaceFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmspaceFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmspaceFrame)
+
+nsMathMLmspaceFrame::~nsMathMLmspaceFrame()
+{
+}
+
+bool
+nsMathMLmspaceFrame::IsLeaf() const
+{
+ return true;
+}
+
+void
+nsMathMLmspaceFrame::ProcessAttributes(nsPresContext* aPresContext)
+{
+ nsAutoString value;
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+
+ // width
+ //
+ // "Specifies the desired width of the space."
+ //
+ // values: length
+ // default: 0em
+ //
+ // The default value is "0em", so unitless values can be ignored.
+ // <mspace/> is listed among MathML elements allowing negative spacing and
+ // the MathML test suite contains "Presentation/TokenElements/mspace/mspace2"
+ // as an example. Hence we allow negative values.
+ //
+ mWidth = 0;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value);
+ if (!value.IsEmpty()) {
+ ParseNumericValue(value, &mWidth,
+ nsMathMLElement::PARSE_ALLOW_NEGATIVE,
+ aPresContext, mStyleContext, fontSizeInflation);
+ }
+
+ // height
+ //
+ // "Specifies the desired height (above the baseline) of the space."
+ //
+ // values: length
+ // default: 0ex
+ //
+ // The default value is "0ex", so unitless values can be ignored.
+ // We do not allow negative values. See bug 716349.
+ //
+ mHeight = 0;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value);
+ if (!value.IsEmpty()) {
+ ParseNumericValue(value, &mHeight, 0,
+ aPresContext, mStyleContext, fontSizeInflation);
+ }
+
+ // depth
+ //
+ // "Specifies the desired depth (below the baseline) of the space."
+ //
+ // values: length
+ // default: 0ex
+ //
+ // The default value is "0ex", so unitless values can be ignored.
+ // We do not allow negative values. See bug 716349.
+ //
+ mDepth = 0;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::depth_, value);
+ if (!value.IsEmpty()) {
+ ParseNumericValue(value, &mDepth, 0,
+ aPresContext, mStyleContext, fontSizeInflation);
+ }
+}
+
+void
+nsMathMLmspaceFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ ProcessAttributes(aPresContext);
+
+ mBoundingMetrics = nsBoundingMetrics();
+ mBoundingMetrics.width = mWidth;
+ mBoundingMetrics.ascent = mHeight;
+ mBoundingMetrics.descent = mDepth;
+ mBoundingMetrics.leftBearing = 0;
+ mBoundingMetrics.rightBearing = mBoundingMetrics.width;
+
+ aDesiredSize.SetBlockStartAscent(mHeight);
+ aDesiredSize.Width() = std::max(0, mBoundingMetrics.width);
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + mDepth;
+ // Also return our bounding metrics
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+/* virtual */ nsresult
+nsMathMLmspaceFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize)
+{
+ ProcessAttributes(PresContext());
+ mBoundingMetrics = nsBoundingMetrics();
+ mBoundingMetrics.width = mWidth;
+ aDesiredSize.Width() = std::max(0, mBoundingMetrics.width);
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+ return NS_OK;
+}
diff --git a/layout/mathml/nsMathMLmspaceFrame.h b/layout/mathml/nsMathMLmspaceFrame.h
new file mode 100644
index 000000000..0dfff3a4c
--- /dev/null
+++ b/layout/mathml/nsMathMLmspaceFrame.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmspaceFrame_h___
+#define nsMathMLmspaceFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+//
+// <mspace> -- space
+//
+
+class nsMathMLmspaceFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmspaceFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ NS_IMETHOD
+ TransmitAutomaticData() override {
+ // The REC defines the following elements to be space-like:
+ // * an mtext, mspace, maligngroup, or malignmark element;
+ mPresentationData.flags |= NS_MATHML_SPACE_LIKE;
+ return NS_OK;
+ }
+
+ virtual bool IsLeaf() const override;
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+protected:
+ explicit nsMathMLmspaceFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext) {}
+ virtual ~nsMathMLmspaceFrame();
+
+ virtual nsresult
+ MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) override;
+
+private:
+ nscoord mWidth;
+ nscoord mHeight;
+ nscoord mDepth;
+
+ // helper method to initialize our member data
+ void
+ ProcessAttributes(nsPresContext* aPresContext);
+};
+
+#endif /* nsMathMLmspaceFrame_h___ */
diff --git a/layout/mathml/nsMathMLmsqrtFrame.cpp b/layout/mathml/nsMathMLmsqrtFrame.cpp
new file mode 100644
index 000000000..096d54769
--- /dev/null
+++ b/layout/mathml/nsMathMLmsqrtFrame.cpp
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmsqrtFrame.h"
+#include "mozilla/gfx/2D.h"
+
+//
+// <msqrt> -- form a radical - implementation
+//
+
+nsIFrame*
+NS_NewMathMLmsqrtFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmsqrtFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmsqrtFrame)
+
+nsMathMLmsqrtFrame::nsMathMLmsqrtFrame(nsStyleContext* aContext) :
+ nsMathMLmencloseFrame(aContext)
+{
+}
+
+nsMathMLmsqrtFrame::~nsMathMLmsqrtFrame()
+{
+}
+
+void
+nsMathMLmsqrtFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
+ AllocateMathMLChar(NOTATION_RADICAL);
+ mNotationsToDraw |= NOTATION_RADICAL;
+}
+
+NS_IMETHODIMP
+nsMathMLmsqrtFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
+
+ return NS_OK;
+}
+
+nsresult
+nsMathMLmsqrtFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ return nsMathMLContainerFrame::
+ AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
diff --git a/layout/mathml/nsMathMLmsqrtFrame.h b/layout/mathml/nsMathMLmsqrtFrame.h
new file mode 100644
index 000000000..0d38c91c3
--- /dev/null
+++ b/layout/mathml/nsMathMLmsqrtFrame.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmsqrtFrame_h___
+#define nsMathMLmsqrtFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLmencloseFrame.h"
+
+//
+// <msqrt> -- form a radical
+//
+
+/*
+The MathML REC describes:
+
+The <msqrt> element is used to display square roots.
+The syntax for <msqrt> is:
+ <msqrt> base </msqrt>
+
+Attributes of <msqrt> and <mroot>:
+
+None (except the attributes allowed for all MathML elements, listed in Section
+2.3.4).
+
+The <mroot> element increments scriptlevel by 2, and sets displaystyle to
+"false", within index, but leaves both attributes unchanged within base. The
+<msqrt> element leaves both attributes unchanged within all its arguments.
+These attributes are inherited by every element from its rendering environment,
+but can be set explicitly only on <mstyle>. (See Section 3.3.4.)
+*/
+
+class nsMathMLmsqrtFrame : public nsMathMLmencloseFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmsqrtFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ virtual void
+ Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool
+ IsMrowLike() override
+ {
+ return mFrames.FirstChild() != mFrames.LastChild() ||
+ !mFrames.FirstChild();
+ }
+
+protected:
+ explicit nsMathMLmsqrtFrame(nsStyleContext* aContext);
+ virtual ~nsMathMLmsqrtFrame();
+};
+
+#endif /* nsMathMLmsqrtFrame_h___ */
+
diff --git a/layout/mathml/nsMathMLmtableFrame.cpp b/layout/mathml/nsMathMLmtableFrame.cpp
new file mode 100644
index 000000000..a706fb483
--- /dev/null
+++ b/layout/mathml/nsMathMLmtableFrame.cpp
@@ -0,0 +1,1363 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmtableFrame.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsNameSpaceManager.h"
+#include "nsRenderingContext.h"
+#include "nsCSSRendering.h"
+#include "nsMathMLElement.h"
+
+#include "nsTArray.h"
+#include "nsTableFrame.h"
+#include "celldata.h"
+
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include <algorithm>
+
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+//
+// <mtable> -- table or matrix - implementation
+//
+
+static int8_t
+ParseStyleValue(nsIAtom* aAttribute, const nsAString& aAttributeValue)
+{
+ if (aAttribute == nsGkAtoms::rowalign_) {
+ if (aAttributeValue.EqualsLiteral("top"))
+ return NS_STYLE_VERTICAL_ALIGN_TOP;
+ else if (aAttributeValue.EqualsLiteral("bottom"))
+ return NS_STYLE_VERTICAL_ALIGN_BOTTOM;
+ else if (aAttributeValue.EqualsLiteral("center"))
+ return NS_STYLE_VERTICAL_ALIGN_MIDDLE;
+ else
+ return NS_STYLE_VERTICAL_ALIGN_BASELINE;
+ } else if (aAttribute == nsGkAtoms::columnalign_) {
+ if (aAttributeValue.EqualsLiteral("left"))
+ return NS_STYLE_TEXT_ALIGN_LEFT;
+ else if (aAttributeValue.EqualsLiteral("right"))
+ return NS_STYLE_TEXT_ALIGN_RIGHT;
+ else
+ return NS_STYLE_TEXT_ALIGN_CENTER;
+ } else if (aAttribute == nsGkAtoms::rowlines_ ||
+ aAttribute == nsGkAtoms::columnlines_) {
+ if (aAttributeValue.EqualsLiteral("solid"))
+ return NS_STYLE_BORDER_STYLE_SOLID;
+ else if (aAttributeValue.EqualsLiteral("dashed"))
+ return NS_STYLE_BORDER_STYLE_DASHED;
+ else
+ return NS_STYLE_BORDER_STYLE_NONE;
+ } else {
+ MOZ_CRASH("Unrecognized attribute.");
+ }
+
+ return -1;
+}
+
+static nsTArray<int8_t>*
+ExtractStyleValues(const nsAString& aString,
+ nsIAtom* aAttribute,
+ bool aAllowMultiValues)
+{
+ nsTArray<int8_t>* styleArray = nullptr;
+
+ const char16_t* start = aString.BeginReading();
+ const char16_t* end = aString.EndReading();
+
+ int32_t startIndex = 0;
+ int32_t count = 0;
+
+ while (start < end) {
+ // Skip leading spaces.
+ while ((start < end) && nsCRT::IsAsciiSpace(*start)) {
+ start++;
+ startIndex++;
+ }
+
+ // Look for the end of the string, or another space.
+ while ((start < end) && !nsCRT::IsAsciiSpace(*start)) {
+ start++;
+ count++;
+ }
+
+ // Grab the value found and process it.
+ if (count > 0) {
+ if (!styleArray)
+ styleArray = new nsTArray<int8_t>();
+
+ // We want to return a null array if an attribute gives multiple values,
+ // but multiple values aren't allowed.
+ if (styleArray->Length() > 1 && !aAllowMultiValues) {
+ delete styleArray;
+ return nullptr;
+ }
+
+ nsDependentSubstring valueString(aString, startIndex, count);
+ int8_t styleValue = ParseStyleValue(aAttribute, valueString);
+ styleArray->AppendElement(styleValue);
+
+ startIndex += count;
+ count = 0;
+ }
+ }
+ return styleArray;
+}
+
+static nsresult
+ReportParseError(nsIFrame* aFrame,
+ const char16_t* aAttribute,
+ const char16_t* aValue)
+{
+ nsIContent* content = aFrame->GetContent();
+
+ const char16_t* params[] =
+ { aValue, aAttribute, content->NodeInfo()->NameAtom()->GetUTF16String() };
+
+ return nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("Layout: MathML"),
+ content->OwnerDoc(),
+ nsContentUtils::eMATHML_PROPERTIES,
+ "AttributeParsingError", params, 3);
+}
+
+// Each rowalign='top bottom' or columnalign='left right center' (from
+// <mtable> or <mtr>) is split once into an nsTArray<int8_t> which is
+// stored in the property table. Row/Cell frames query the property table
+// to see what values apply to them.
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowAlignProperty, nsTArray<int8_t>)
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowLinesProperty, nsTArray<int8_t>)
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(ColumnAlignProperty, nsTArray<int8_t>)
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(ColumnLinesProperty, nsTArray<int8_t>)
+
+static const FramePropertyDescriptor<nsTArray<int8_t>>*
+AttributeToProperty(nsIAtom* aAttribute)
+{
+ if (aAttribute == nsGkAtoms::rowalign_)
+ return RowAlignProperty();
+ if (aAttribute == nsGkAtoms::rowlines_)
+ return RowLinesProperty();
+ if (aAttribute == nsGkAtoms::columnalign_)
+ return ColumnAlignProperty();
+ NS_ASSERTION(aAttribute == nsGkAtoms::columnlines_, "Invalid attribute");
+ return ColumnLinesProperty();
+}
+
+/* This method looks for a property that applies to a cell, but it looks
+ * recursively because some cell properties can come from the cell, a row,
+ * a table, etc. This function searches through the hierarchy for a property
+ * and returns its value. The function stops searching after checking a <mtable>
+ * frame.
+ */
+static nsTArray<int8_t>*
+FindCellProperty(const nsIFrame* aCellFrame,
+ const FramePropertyDescriptor<nsTArray<int8_t>>* aFrameProperty)
+{
+ const nsIFrame* currentFrame = aCellFrame;
+ nsTArray<int8_t>* propertyData = nullptr;
+
+ while (currentFrame) {
+ FrameProperties props = currentFrame->Properties();
+ propertyData = props.Get(aFrameProperty);
+ bool frameIsTable = (currentFrame->GetType() == nsGkAtoms::tableFrame);
+
+ if (propertyData || frameIsTable)
+ currentFrame = nullptr; // A null frame pointer exits the loop
+ else
+ currentFrame = currentFrame->GetParent(); // Go to the parent frame
+ }
+
+ return propertyData;
+}
+
+static void
+ApplyBorderToStyle(const nsMathMLmtdFrame* aFrame,
+ nsStyleBorder& aStyleBorder)
+{
+ int32_t rowIndex;
+ int32_t columnIndex;
+ aFrame->GetRowIndex(rowIndex);
+ aFrame->GetColIndex(columnIndex);
+
+ nscoord borderWidth =
+ aFrame->PresContext()->GetBorderWidthTable()[NS_STYLE_BORDER_WIDTH_THIN];
+
+ nsTArray<int8_t>* rowLinesList =
+ FindCellProperty(aFrame, RowLinesProperty());
+
+ nsTArray<int8_t>* columnLinesList =
+ FindCellProperty(aFrame, ColumnLinesProperty());
+
+ // We don't place a row line on top of the first row
+ if (rowIndex > 0 && rowLinesList) {
+ // If the row number is greater than the number of provided rowline
+ // values, we simply repeat the last value.
+ int32_t listLength = rowLinesList->Length();
+ if (rowIndex < listLength) {
+ aStyleBorder.SetBorderStyle(NS_SIDE_TOP,
+ rowLinesList->ElementAt(rowIndex - 1));
+ } else {
+ aStyleBorder.SetBorderStyle(NS_SIDE_TOP,
+ rowLinesList->ElementAt(listLength - 1));
+ }
+ aStyleBorder.SetBorderWidth(NS_SIDE_TOP, borderWidth);
+ }
+
+ // We don't place a column line on the left of the first column.
+ if (columnIndex > 0 && columnLinesList) {
+ // If the column number is greater than the number of provided columline
+ // values, we simply repeat the last value.
+ int32_t listLength = columnLinesList->Length();
+ if (columnIndex < listLength) {
+ aStyleBorder.SetBorderStyle(NS_SIDE_LEFT,
+ columnLinesList->ElementAt(columnIndex - 1));
+ } else {
+ aStyleBorder.SetBorderStyle(NS_SIDE_LEFT,
+ columnLinesList->ElementAt(listLength - 1));
+ }
+ aStyleBorder.SetBorderWidth(NS_SIDE_LEFT, borderWidth);
+ }
+}
+
+static nsMargin
+ComputeBorderOverflow(nsMathMLmtdFrame* aFrame,
+ const nsStyleBorder& aStyleBorder)
+{
+ nsMargin overflow;
+ int32_t rowIndex;
+ int32_t columnIndex;
+ nsTableFrame* table = aFrame->GetTableFrame();
+ aFrame->GetCellIndexes(rowIndex, columnIndex);
+ if (!columnIndex) {
+ overflow.left = table->GetColSpacing(-1);
+ overflow.right = table->GetColSpacing(0) / 2;
+ } else if (columnIndex == table->GetColCount() - 1) {
+ overflow.left = table->GetColSpacing(columnIndex - 1) / 2;
+ overflow.right = table->GetColSpacing(columnIndex + 1);
+ } else {
+ overflow.left = table->GetColSpacing(columnIndex - 1) / 2;
+ overflow.right = table->GetColSpacing(columnIndex) / 2;
+ }
+ if (!rowIndex) {
+ overflow.top = table->GetRowSpacing(-1);
+ overflow.bottom = table->GetRowSpacing(0) / 2;
+ } else if (rowIndex == table->GetRowCount() - 1) {
+ overflow.top = table->GetRowSpacing(rowIndex - 1) / 2;
+ overflow.bottom = table->GetRowSpacing(rowIndex + 1);
+ } else {
+ overflow.top = table->GetRowSpacing(rowIndex - 1) / 2;
+ overflow.bottom = table->GetRowSpacing(rowIndex) / 2;
+ }
+ return overflow;
+}
+
+/*
+ * A variant of the nsDisplayBorder contains special code to render a border
+ * around a nsMathMLmtdFrame based on the rowline and columnline properties
+ * set on the cell frame.
+ */
+class nsDisplaymtdBorder : public nsDisplayBorder
+{
+public:
+ nsDisplaymtdBorder(nsDisplayListBuilder* aBuilder, nsMathMLmtdFrame* aFrame)
+ : nsDisplayBorder(aBuilder, aFrame)
+ {
+ }
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override
+ {
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+ }
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+ {
+ *aSnap = true;
+ nsStyleBorder styleBorder = *mFrame->StyleBorder();
+ nsMathMLmtdFrame* frame = static_cast<nsMathMLmtdFrame*>(mFrame);
+ ApplyBorderToStyle(frame, styleBorder);
+ nsRect bounds = CalculateBounds(styleBorder);
+ nsMargin overflow = ComputeBorderOverflow(frame, styleBorder);
+ bounds.Inflate(overflow);
+ return bounds;
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override
+ {
+ nsStyleBorder styleBorder = *mFrame->StyleBorder();
+ nsMathMLmtdFrame* frame = static_cast<nsMathMLmtdFrame*>(mFrame);
+ ApplyBorderToStyle(frame, styleBorder);
+
+ nsRect bounds = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ nsMargin overflow = ComputeBorderOverflow(frame, styleBorder);
+ bounds.Inflate(overflow);
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SYNC_DECODE_IMAGES
+ : PaintBorderFlags();
+
+ DrawResult result =
+ nsCSSRendering::PaintBorderWithStyleBorder(mFrame->PresContext(), *aCtx,
+ mFrame, mVisibleRect,
+ bounds,
+ styleBorder,
+ mFrame->StyleContext(),
+ flags,
+ mFrame->GetSkipSides());
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+ }
+};
+
+#ifdef DEBUG
+#define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected) \
+ MOZ_ASSERT(mozilla::StyleDisplay::_expected == _frame->StyleDisplay()->mDisplay, \
+ "internal error");
+#else
+#define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected)
+#endif
+
+static void
+ParseFrameAttribute(nsIFrame* aFrame,
+ nsIAtom* aAttribute,
+ bool aAllowMultiValues)
+{
+ nsAutoString attrValue;
+
+ nsIContent* frameContent = aFrame->GetContent();
+ frameContent->GetAttr(kNameSpaceID_None, aAttribute, attrValue);
+
+ if (!attrValue.IsEmpty()) {
+ nsTArray<int8_t>* valueList =
+ ExtractStyleValues(attrValue, aAttribute, aAllowMultiValues);
+
+ // If valueList is null, that indicates a problem with the attribute value.
+ // Only set properties on a valid attribute value.
+ if (valueList) {
+ // The code reading the property assumes that this list is nonempty.
+ NS_ASSERTION(valueList->Length() >= 1, "valueList should not be empty!");
+ FrameProperties props = aFrame->Properties();
+ props.Set(AttributeToProperty(aAttribute), valueList);
+ } else {
+ ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get());
+ }
+ }
+}
+
+// rowspacing
+//
+// Specifies the distance between successive rows in an mtable. Multiple
+// lengths can be specified, each corresponding to its respective position
+// between rows. For example:
+//
+// [ROW_0]
+// rowspace_0
+// [ROW_1]
+// rowspace_1
+// [ROW_2]
+//
+// If the number of row gaps exceeds the number of lengths specified, the final
+// specified length is repeated. Additional lengths are ignored.
+//
+// values: (length)+
+// default: 1.0ex
+//
+// Unitless values are permitted and provide a multiple of the default value
+// Negative values are forbidden.
+//
+
+// columnspacing
+//
+// Specifies the distance between successive columns in an mtable. Multiple
+// lengths can be specified, each corresponding to its respective position
+// between columns. For example:
+//
+// [COLUMN_0] columnspace_0 [COLUMN_1] columnspace_1 [COLUMN_2]
+//
+// If the number of column gaps exceeds the number of lengths specified, the
+// final specified length is repeated. Additional lengths are ignored.
+//
+// values: (length)+
+// default: 0.8em
+//
+// Unitless values are permitted and provide a multiple of the default value
+// Negative values are forbidden.
+//
+
+// framespacing
+//
+// Specifies the distance between the mtable and its frame (if any). The
+// first value specified provides the spacing between the left and right edge
+// of the table and the frame, the second value determines the spacing between
+// the top and bottom edges and the frame.
+//
+// An error is reported if only one length is passed. Any additional lengths
+// are ignored
+//
+// values: length length
+// default: 0em 0ex If frame attribute is "none" or not specified,
+// 0.4em 0.5ex otherwise
+//
+// Unitless values are permitted and provide a multiple of the default value
+// Negative values are forbidden.
+//
+
+static const float kDefaultRowspacingEx = 1.0f;
+static const float kDefaultColumnspacingEm = 0.8f;
+static const float kDefaultFramespacingArg0Em = 0.4f;
+static const float kDefaultFramespacingArg1Ex = 0.5f;
+
+static void
+ExtractSpacingValues(const nsAString& aString,
+ nsIAtom* aAttribute,
+ nsTArray<nscoord>& aSpacingArray,
+ nsIFrame* aFrame,
+ nscoord aDefaultValue0,
+ nscoord aDefaultValue1,
+ float aFontSizeInflation)
+{
+ nsPresContext* presContext = aFrame->PresContext();
+ nsStyleContext* styleContext = aFrame->StyleContext();
+
+ const char16_t* start = aString.BeginReading();
+ const char16_t* end = aString.EndReading();
+
+ int32_t startIndex = 0;
+ int32_t count = 0;
+ int32_t elementNum = 0;
+
+ while (start < end) {
+ // Skip leading spaces.
+ while ((start < end) && nsCRT::IsAsciiSpace(*start)) {
+ start++;
+ startIndex++;
+ }
+
+ // Look for the end of the string, or another space.
+ while ((start < end) && !nsCRT::IsAsciiSpace(*start)) {
+ start++;
+ count++;
+ }
+
+ // Grab the value found and process it.
+ if (count > 0) {
+ const nsAString& str = Substring(aString, startIndex, count);
+ nsAutoString valueString;
+ valueString.Assign(str);
+ nscoord newValue;
+ if (aAttribute == nsGkAtoms::framespacing_ && elementNum) {
+ newValue = aDefaultValue1;
+ } else {
+ newValue = aDefaultValue0;
+ }
+ nsMathMLFrame::ParseNumericValue(valueString, &newValue,
+ nsMathMLElement::PARSE_ALLOW_UNITLESS,
+ presContext, styleContext,
+ aFontSizeInflation);
+ aSpacingArray.AppendElement(newValue);
+
+ startIndex += count;
+ count = 0;
+ elementNum++;
+ }
+ }
+}
+
+static void
+ParseSpacingAttribute(nsMathMLmtableFrame* aFrame, nsIAtom* aAttribute)
+{
+ NS_ASSERTION(aAttribute == nsGkAtoms::rowspacing_ ||
+ aAttribute == nsGkAtoms::columnspacing_ ||
+ aAttribute == nsGkAtoms::framespacing_,
+ "Non spacing attribute passed");
+
+ nsAutoString attrValue;
+ nsIContent* frameContent = aFrame->GetContent();
+ frameContent->GetAttr(kNameSpaceID_None, aAttribute, attrValue);
+
+ if (nsGkAtoms::framespacing_ == aAttribute) {
+ nsAutoString frame;
+ frameContent->GetAttr(kNameSpaceID_None, nsGkAtoms::frame, frame);
+ if (frame.IsEmpty() || frame.EqualsLiteral("none")) {
+ aFrame->SetFrameSpacing(0, 0);
+ return;
+ }
+ }
+
+ nscoord value;
+ nscoord value2;
+ // Set defaults
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(aFrame);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aFrame, fontSizeInflation);
+ if (nsGkAtoms::rowspacing_ == aAttribute) {
+ value = kDefaultRowspacingEx * fm->XHeight();
+ value2 = 0;
+ } else if (nsGkAtoms::columnspacing_ == aAttribute) {
+ value = kDefaultColumnspacingEm * fm->EmHeight();
+ value2 = 0;
+ } else {
+ value = kDefaultFramespacingArg0Em * fm->EmHeight();
+ value2 = kDefaultFramespacingArg1Ex * fm->XHeight();
+ }
+
+ nsTArray<nscoord> valueList;
+ ExtractSpacingValues(attrValue, aAttribute, valueList, aFrame, value, value2,
+ fontSizeInflation);
+ if (valueList.Length() == 0) {
+ if (frameContent->HasAttr(kNameSpaceID_None, aAttribute)) {
+ ReportParseError(aFrame, aAttribute->GetUTF16String(),
+ attrValue.get());
+ }
+ valueList.AppendElement(value);
+ }
+ if (aAttribute == nsGkAtoms::framespacing_) {
+ if (valueList.Length() == 1) {
+ if(frameContent->HasAttr(kNameSpaceID_None, aAttribute)) {
+ ReportParseError(aFrame, aAttribute->GetUTF16String(),
+ attrValue.get());
+ }
+ valueList.AppendElement(value2);
+ } else if (valueList.Length() != 2) {
+ ReportParseError(aFrame, aAttribute->GetUTF16String(),
+ attrValue.get());
+ }
+ }
+
+ if (aAttribute == nsGkAtoms::rowspacing_) {
+ aFrame->SetRowSpacingArray(valueList);
+ } else if (aAttribute == nsGkAtoms::columnspacing_) {
+ aFrame->SetColSpacingArray(valueList);
+ } else {
+ aFrame->SetFrameSpacing(valueList.ElementAt(0),
+ valueList.ElementAt(1));
+ }
+}
+
+static void ParseSpacingAttributes(nsMathMLmtableFrame* aTableFrame)
+{
+ ParseSpacingAttribute(aTableFrame, nsGkAtoms::rowspacing_);
+ ParseSpacingAttribute(aTableFrame, nsGkAtoms::columnspacing_);
+ ParseSpacingAttribute(aTableFrame, nsGkAtoms::framespacing_);
+ aTableFrame->SetUseCSSSpacing();
+}
+
+// map all attributes within a table -- requires the indices of rows and cells.
+// so it can only happen after they are made ready by the table base class.
+static void
+MapAllAttributesIntoCSS(nsMathMLmtableFrame* aTableFrame)
+{
+ // Map mtable rowalign & rowlines.
+ ParseFrameAttribute(aTableFrame, nsGkAtoms::rowalign_, true);
+ ParseFrameAttribute(aTableFrame, nsGkAtoms::rowlines_, true);
+
+ // Map mtable columnalign & columnlines.
+ ParseFrameAttribute(aTableFrame, nsGkAtoms::columnalign_, true);
+ ParseFrameAttribute(aTableFrame, nsGkAtoms::columnlines_, true);
+
+ // Map mtable rowspacing, columnspacing & framespacing
+ ParseSpacingAttributes(aTableFrame);
+
+ // mtable is simple and only has one (pseudo) row-group
+ nsIFrame* rgFrame = aTableFrame->PrincipalChildList().FirstChild();
+ if (!rgFrame || rgFrame->GetType() != nsGkAtoms::tableRowGroupFrame)
+ return;
+
+ for (nsIFrame* rowFrame : rgFrame->PrincipalChildList()) {
+ DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TableRow);
+ if (rowFrame->GetType() == nsGkAtoms::tableRowFrame) {
+ // Map row rowalign.
+ ParseFrameAttribute(rowFrame, nsGkAtoms::rowalign_, false);
+ // Map row columnalign.
+ ParseFrameAttribute(rowFrame, nsGkAtoms::columnalign_, true);
+
+ for (nsIFrame* cellFrame : rowFrame->PrincipalChildList()) {
+ DEBUG_VERIFY_THAT_FRAME_IS(cellFrame, TableCell);
+ if (IS_TABLE_CELL(cellFrame->GetType())) {
+ // Map cell rowalign.
+ ParseFrameAttribute(cellFrame, nsGkAtoms::rowalign_, false);
+ // Map row columnalign.
+ ParseFrameAttribute(cellFrame, nsGkAtoms::columnalign_, false);
+ }
+ }
+ }
+ }
+}
+
+// the align attribute of mtable can have a row number which indicates
+// from where to anchor the table, e.g., top 5 means anchor the table at
+// the top of the 5th row, axis -1 means anchor the table on the axis of
+// the last row
+
+// The REC says that the syntax is
+// '\s*(top|bottom|center|baseline|axis)(\s+-?[0-9]+)?\s*'
+// the parsing could have been simpler with that syntax
+// but for backward compatibility we make optional
+// the whitespaces between the alignment name and the row number
+
+enum eAlign {
+ eAlign_top,
+ eAlign_bottom,
+ eAlign_center,
+ eAlign_baseline,
+ eAlign_axis
+};
+
+static void
+ParseAlignAttribute(nsString& aValue, eAlign& aAlign, int32_t& aRowIndex)
+{
+ // by default, the table is centered about the axis
+ aRowIndex = 0;
+ aAlign = eAlign_axis;
+ int32_t len = 0;
+
+ // we only have to remove the leading spaces because
+ // ToInteger ignores the whitespaces around the number
+ aValue.CompressWhitespace(true, false);
+
+ if (0 == aValue.Find("top")) {
+ len = 3; // 3 is the length of 'top'
+ aAlign = eAlign_top;
+ }
+ else if (0 == aValue.Find("bottom")) {
+ len = 6; // 6 is the length of 'bottom'
+ aAlign = eAlign_bottom;
+ }
+ else if (0 == aValue.Find("center")) {
+ len = 6; // 6 is the length of 'center'
+ aAlign = eAlign_center;
+ }
+ else if (0 == aValue.Find("baseline")) {
+ len = 8; // 8 is the length of 'baseline'
+ aAlign = eAlign_baseline;
+ }
+ else if (0 == aValue.Find("axis")) {
+ len = 4; // 4 is the length of 'axis'
+ aAlign = eAlign_axis;
+ }
+ if (len) {
+ nsresult error;
+ aValue.Cut(0, len); // aValue is not a const here
+ aRowIndex = aValue.ToInteger(&error);
+ if (NS_FAILED(error))
+ aRowIndex = 0;
+ }
+}
+
+#ifdef DEBUG_rbs_off
+// call ListMathMLTree(mParent) to get the big picture
+static void
+ListMathMLTree(nsIFrame* atLeast)
+{
+ // climb up to <math> or <body> if <math> isn't there
+ nsIFrame* f = atLeast;
+ for ( ; f; f = f->GetParent()) {
+ nsIContent* c = f->GetContent();
+ if (!c || c->IsMathMLElement(nsGkAtoms::math) ||
+ c->NodeInfo()->NameAtom(nsGkAtoms::body)) // XXXbaku which kind of body tag?
+ break;
+ }
+ if (!f) f = atLeast;
+ f->List(stdout, 0);
+}
+#endif
+
+// --------
+// implementation of nsMathMLmtableWrapperFrame
+
+NS_QUERYFRAME_HEAD(nsMathMLmtableWrapperFrame)
+ NS_QUERYFRAME_ENTRY(nsIMathMLFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsTableWrapperFrame)
+
+nsContainerFrame*
+NS_NewMathMLmtableOuterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmtableWrapperFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableWrapperFrame)
+
+nsMathMLmtableWrapperFrame::~nsMathMLmtableWrapperFrame()
+{
+}
+
+nsresult
+nsMathMLmtableWrapperFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // Attributes specific to <mtable>:
+ // frame : in mathml.css
+ // framespacing : here
+ // groupalign : not yet supported
+ // equalrows : not yet supported
+ // equalcolumns : not yet supported
+ // displaystyle : here and in mathml.css
+ // align : in reflow
+ // rowalign : here
+ // rowlines : here
+ // rowspacing : here
+ // columnalign : here
+ // columnlines : here
+ // columnspacing : here
+
+ // mtable is simple and only has one (pseudo) row-group inside our inner-table
+ nsIFrame* tableFrame = mFrames.FirstChild();
+ NS_ASSERTION(tableFrame && tableFrame->GetType() == nsGkAtoms::tableFrame,
+ "should always have an inner table frame");
+ nsIFrame* rgFrame = tableFrame->PrincipalChildList().FirstChild();
+ if (!rgFrame || rgFrame->GetType() != nsGkAtoms::tableRowGroupFrame)
+ return NS_OK;
+
+ // align - just need to issue a dirty (resize) reflow command
+ if (aAttribute == nsGkAtoms::align) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+ return NS_OK;
+ }
+
+ // displaystyle - may seem innocuous, but it is actually very harsh --
+ // like changing an unit. Blow away and recompute all our automatic
+ // presentational data, and issue a style-changed reflow request
+ if (aAttribute == nsGkAtoms::displaystyle_) {
+ nsMathMLContainerFrame::RebuildAutomaticDataForChildren(GetParent());
+ // Need to reflow the parent, not us, because this can actually
+ // affect siblings.
+ PresContext()->PresShell()->
+ FrameNeedsReflow(GetParent(), nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ return NS_OK;
+ }
+
+ // ...and the other attributes affect rows or columns in one way or another
+
+ nsPresContext* presContext = tableFrame->PresContext();
+ if (aAttribute == nsGkAtoms::rowspacing_ ||
+ aAttribute == nsGkAtoms::columnspacing_ ||
+ aAttribute == nsGkAtoms::framespacing_ ) {
+ nsMathMLmtableFrame* mathMLmtableFrame = do_QueryFrame(tableFrame);
+ if (mathMLmtableFrame) {
+ ParseSpacingAttribute(mathMLmtableFrame, aAttribute);
+ mathMLmtableFrame->SetUseCSSSpacing();
+ }
+ } else if (aAttribute == nsGkAtoms::rowalign_ ||
+ aAttribute == nsGkAtoms::rowlines_ ||
+ aAttribute == nsGkAtoms::columnalign_ ||
+ aAttribute == nsGkAtoms::columnlines_) {
+ // clear any cached property list for this table
+ presContext->PropertyTable()->
+ Delete(tableFrame, AttributeToProperty(aAttribute));
+ // Reparse the new attribute on the table.
+ ParseFrameAttribute(tableFrame, aAttribute, true);
+ } else {
+ // Ignore attributes that do not affect layout.
+ return NS_OK;
+ }
+
+ // Explicitly request a reflow in our subtree to pick up any changes
+ presContext->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+
+ return NS_OK;
+}
+
+nsIFrame*
+nsMathMLmtableWrapperFrame::GetRowFrameAt(int32_t aRowIndex)
+{
+ int32_t rowCount = GetRowCount();
+
+ // Negative indices mean to find upwards from the end.
+ if (aRowIndex < 0) {
+ aRowIndex = rowCount + aRowIndex;
+ } else {
+ // aRowIndex is 1-based, so convert it to a 0-based index
+ --aRowIndex;
+ }
+
+ // if our inner table says that the index is valid, find the row now
+ if (0 <= aRowIndex && aRowIndex <= rowCount) {
+ nsIFrame* tableFrame = mFrames.FirstChild();
+ NS_ASSERTION(tableFrame && tableFrame->GetType() == nsGkAtoms::tableFrame,
+ "should always have an inner table frame");
+ nsIFrame* rgFrame = tableFrame->PrincipalChildList().FirstChild();
+ if (!rgFrame || rgFrame->GetType() != nsGkAtoms::tableRowGroupFrame)
+ return nullptr;
+ for (nsIFrame* rowFrame : rgFrame->PrincipalChildList()) {
+ if (aRowIndex == 0) {
+ DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TableRow);
+ if (rowFrame->GetType() != nsGkAtoms::tableRowFrame)
+ return nullptr;
+
+ return rowFrame;
+ }
+ --aRowIndex;
+ }
+ }
+ return nullptr;
+}
+
+void
+nsMathMLmtableWrapperFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ nsAutoString value;
+ // we want to return a table that is anchored according to the align attribute
+
+ nsTableWrapperFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+ NS_ASSERTION(aDesiredSize.Height() >= 0, "illegal height for mtable");
+ NS_ASSERTION(aDesiredSize.Width() >= 0, "illegal width for mtable");
+
+ // see if the user has set the align attribute on the <mtable>
+ int32_t rowIndex = 0;
+ eAlign tableAlign = eAlign_axis;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::align, value);
+ if (!value.IsEmpty()) {
+ ParseAlignAttribute(value, tableAlign, rowIndex);
+ }
+
+ // adjustments if there is a specified row from where to anchor the table
+ // (conceptually: when there is no row of reference, picture the table as if
+ // it is wrapped in a single big fictional row at dy = 0, this way of
+ // doing so allows us to have a single code path for all cases).
+ nscoord dy = 0;
+ WritingMode wm = aDesiredSize.GetWritingMode();
+ nscoord blockSize = aDesiredSize.BSize(wm);
+ nsIFrame* rowFrame = nullptr;
+ if (rowIndex) {
+ rowFrame = GetRowFrameAt(rowIndex);
+ if (rowFrame) {
+ // translate the coordinates to be relative to us and in our writing mode
+ nsIFrame* frame = rowFrame;
+ LogicalRect rect(wm, frame->GetRect(),
+ aReflowInput.ComputedSizeAsContainerIfConstrained());
+ blockSize = rect.BSize(wm);
+ do {
+ dy += rect.BStart(wm);
+ frame = frame->GetParent();
+ } while (frame != this);
+ }
+ }
+ switch (tableAlign) {
+ case eAlign_top:
+ aDesiredSize.SetBlockStartAscent(dy);
+ break;
+ case eAlign_bottom:
+ aDesiredSize.SetBlockStartAscent(dy + blockSize);
+ break;
+ case eAlign_center:
+ aDesiredSize.SetBlockStartAscent(dy + blockSize / 2);
+ break;
+ case eAlign_baseline:
+ if (rowFrame) {
+ // anchor the table on the baseline of the row of reference
+ nscoord rowAscent = ((nsTableRowFrame*)rowFrame)->GetMaxCellAscent();
+ if (rowAscent) { // the row has at least one cell with 'vertical-align: baseline'
+ aDesiredSize.SetBlockStartAscent(dy + rowAscent);
+ break;
+ }
+ }
+ // in other situations, fallback to center
+ aDesiredSize.SetBlockStartAscent(dy + blockSize / 2);
+ break;
+ case eAlign_axis:
+ default: {
+ // XXX should instead use style data from the row of reference here ?
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
+ nscoord axisHeight;
+ GetAxisHeight(aReflowInput.mRenderingContext->GetDrawTarget(), fm, axisHeight);
+ if (rowFrame) {
+ // anchor the table on the axis of the row of reference
+ // XXX fallback to baseline because it is a hard problem
+ // XXX need to fetch the axis of the row; would need rowalign=axis to work better
+ nscoord rowAscent = ((nsTableRowFrame*)rowFrame)->GetMaxCellAscent();
+ if (rowAscent) { // the row has at least one cell with 'vertical-align: baseline'
+ aDesiredSize.SetBlockStartAscent(dy + rowAscent);
+ break;
+ }
+ }
+ // in other situations, fallback to using half of the height
+ aDesiredSize.SetBlockStartAscent(dy + blockSize / 2 + axisHeight);
+ }
+ }
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ // just make-up a bounding metrics
+ mBoundingMetrics = nsBoundingMetrics();
+ mBoundingMetrics.ascent = aDesiredSize.BlockStartAscent();
+ mBoundingMetrics.descent = aDesiredSize.Height() -
+ aDesiredSize.BlockStartAscent();
+ mBoundingMetrics.width = aDesiredSize.Width();
+ mBoundingMetrics.leftBearing = 0;
+ mBoundingMetrics.rightBearing = aDesiredSize.Width();
+
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+nsContainerFrame*
+NS_NewMathMLmtableFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmtableFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableFrame)
+
+nsMathMLmtableFrame::~nsMathMLmtableFrame()
+{
+}
+
+void
+nsMathMLmtableFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ nsTableFrame::SetInitialChildList(aListID, aChildList);
+ MapAllAttributesIntoCSS(this);
+}
+
+void
+nsMathMLmtableFrame::RestyleTable()
+{
+ // re-sync MathML specific style data that may have changed
+ MapAllAttributesIntoCSS(this);
+
+ // Explicitly request a re-resolve and reflow in our subtree to pick up any changes
+ PresContext()->RestyleManager()->
+ PostRestyleEvent(mContent->AsElement(), eRestyle_Subtree,
+ nsChangeHint_AllReflowHints);
+}
+
+nscoord
+nsMathMLmtableFrame::GetColSpacing(int32_t aColIndex)
+{
+ if (mUseCSSSpacing) {
+ return nsTableFrame::GetColSpacing(aColIndex);
+ }
+ if (!mColSpacing.Length()) {
+ NS_ERROR("mColSpacing should not be empty");
+ return 0;
+ }
+ if (aColIndex < 0 || aColIndex >= GetColCount()) {
+ NS_ASSERTION(aColIndex == -1 || aColIndex == GetColCount(),
+ "Desired column beyond bounds of table and border");
+ return mFrameSpacingX;
+ }
+ if ((uint32_t) aColIndex >= mColSpacing.Length()) {
+ return mColSpacing.LastElement();
+ }
+ return mColSpacing.ElementAt(aColIndex);
+}
+
+nscoord
+nsMathMLmtableFrame::GetColSpacing(int32_t aStartColIndex,
+ int32_t aEndColIndex)
+{
+ if (mUseCSSSpacing) {
+ return nsTableFrame::GetColSpacing(aStartColIndex, aEndColIndex);
+ }
+ if (aStartColIndex == aEndColIndex) {
+ return 0;
+ }
+ if (!mColSpacing.Length()) {
+ NS_ERROR("mColSpacing should not be empty");
+ return 0;
+ }
+ nscoord space = 0;
+ if (aStartColIndex < 0) {
+ NS_ASSERTION(aStartColIndex == -1,
+ "Desired column beyond bounds of table and border");
+ space += mFrameSpacingX;
+ aStartColIndex = 0;
+ }
+ if (aEndColIndex >= GetColCount()) {
+ NS_ASSERTION(aEndColIndex == GetColCount(),
+ "Desired column beyond bounds of table and border");
+ space += mFrameSpacingX;
+ aEndColIndex = GetColCount();
+ }
+ // Only iterate over column spacing when there is the potential to vary
+ int32_t min = std::min(aEndColIndex, (int32_t) mColSpacing.Length());
+ for (int32_t i = aStartColIndex; i < min; i++) {
+ space += mColSpacing.ElementAt(i);
+ }
+ // The remaining values are constant. Note that if there are more
+ // column spacings specified than there are columns, LastElement() will be
+ // multiplied by 0, so it is still safe to use.
+ space += (aEndColIndex - min) * mColSpacing.LastElement();
+ return space;
+}
+
+nscoord
+nsMathMLmtableFrame::GetRowSpacing(int32_t aRowIndex)
+{
+ if (mUseCSSSpacing) {
+ return nsTableFrame::GetRowSpacing(aRowIndex);
+ }
+ if (!mRowSpacing.Length()) {
+ NS_ERROR("mRowSpacing should not be empty");
+ return 0;
+ }
+ if (aRowIndex < 0 || aRowIndex >= GetRowCount()) {
+ NS_ASSERTION(aRowIndex == -1 || aRowIndex == GetRowCount(),
+ "Desired row beyond bounds of table and border");
+ return mFrameSpacingY;
+ }
+ if ((uint32_t) aRowIndex >= mRowSpacing.Length()) {
+ return mRowSpacing.LastElement();
+ }
+ return mRowSpacing.ElementAt(aRowIndex);
+}
+
+nscoord
+nsMathMLmtableFrame::GetRowSpacing(int32_t aStartRowIndex,
+ int32_t aEndRowIndex)
+{
+ if (mUseCSSSpacing) {
+ return nsTableFrame::GetRowSpacing(aStartRowIndex, aEndRowIndex);
+ }
+ if (aStartRowIndex == aEndRowIndex) {
+ return 0;
+ }
+ if (!mRowSpacing.Length()) {
+ NS_ERROR("mRowSpacing should not be empty");
+ return 0;
+ }
+ nscoord space = 0;
+ if (aStartRowIndex < 0) {
+ NS_ASSERTION(aStartRowIndex == -1,
+ "Desired row beyond bounds of table and border");
+ space += mFrameSpacingY;
+ aStartRowIndex = 0;
+ }
+ if (aEndRowIndex >= GetRowCount()) {
+ NS_ASSERTION(aEndRowIndex == GetRowCount(),
+ "Desired row beyond bounds of table and border");
+ space += mFrameSpacingY;
+ aEndRowIndex = GetRowCount();
+ }
+ // Only iterate over row spacing when there is the potential to vary
+ int32_t min = std::min(aEndRowIndex, (int32_t) mRowSpacing.Length());
+ for (int32_t i = aStartRowIndex; i < min; i++) {
+ space += mRowSpacing.ElementAt(i);
+ }
+ // The remaining values are constant. Note that if there are more
+ // row spacings specified than there are row, LastElement() will be
+ // multiplied by 0, so it is still safe to use.
+ space += (aEndRowIndex - min) * mRowSpacing.LastElement();
+ return space;
+}
+
+void
+nsMathMLmtableFrame::SetUseCSSSpacing()
+{
+ mUseCSSSpacing =
+ !(mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::rowspacing_) ||
+ mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::columnspacing_) ||
+ mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::framespacing_));
+}
+
+NS_QUERYFRAME_HEAD(nsMathMLmtableFrame)
+ NS_QUERYFRAME_ENTRY(nsMathMLmtableFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsTableFrame)
+
+// --------
+// implementation of nsMathMLmtrFrame
+
+nsContainerFrame*
+NS_NewMathMLmtrFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmtrFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtrFrame)
+
+nsMathMLmtrFrame::~nsMathMLmtrFrame()
+{
+}
+
+nsresult
+nsMathMLmtrFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // Attributes specific to <mtr>:
+ // groupalign : Not yet supported.
+ // rowalign : Here
+ // columnalign : Here
+
+ nsPresContext* presContext = PresContext();
+
+ if (aAttribute != nsGkAtoms::rowalign_ &&
+ aAttribute != nsGkAtoms::columnalign_) {
+ return NS_OK;
+ }
+
+ presContext->PropertyTable()->Delete(this, AttributeToProperty(aAttribute));
+
+ bool allowMultiValues = (aAttribute == nsGkAtoms::columnalign_);
+
+ // Reparse the new attribute.
+ ParseFrameAttribute(this, aAttribute, allowMultiValues);
+
+ // Explicitly request a reflow in our subtree to pick up any changes
+ presContext->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+
+ return NS_OK;
+}
+
+// --------
+// implementation of nsMathMLmtdFrame
+
+nsContainerFrame*
+NS_NewMathMLmtdFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext,
+ nsTableFrame* aTableFrame)
+{
+ return new (aPresShell) nsMathMLmtdFrame(aContext, aTableFrame);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdFrame)
+
+nsMathMLmtdFrame::~nsMathMLmtdFrame()
+{
+}
+
+void
+nsMathMLmtdFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsTableCellFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // We want to use the ancestor <math> element's font inflation to avoid
+ // individual cells having their own varying font inflation.
+ RemoveStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+}
+
+int32_t
+nsMathMLmtdFrame::GetRowSpan()
+{
+ int32_t rowspan = 1;
+
+ // Don't look at the content's rowspan if we're not an mtd or a pseudo cell.
+ if (mContent->IsMathMLElement(nsGkAtoms::mtd_) &&
+ !StyleContext()->GetPseudo()) {
+ nsAutoString value;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rowspan, value);
+ if (!value.IsEmpty()) {
+ nsresult error;
+ rowspan = value.ToInteger(&error);
+ if (NS_FAILED(error) || rowspan < 0)
+ rowspan = 1;
+ rowspan = std::min(rowspan, MAX_ROWSPAN);
+ }
+ }
+ return rowspan;
+}
+
+int32_t
+nsMathMLmtdFrame::GetColSpan()
+{
+ int32_t colspan = 1;
+
+ // Don't look at the content's colspan if we're not an mtd or a pseudo cell.
+ if (mContent->IsMathMLElement(nsGkAtoms::mtd_) &&
+ !StyleContext()->GetPseudo()) {
+ nsAutoString value;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::columnspan_, value);
+ if (!value.IsEmpty()) {
+ nsresult error;
+ colspan = value.ToInteger(&error);
+ if (NS_FAILED(error) || colspan <= 0 || colspan > MAX_COLSPAN)
+ colspan = 1;
+ }
+ }
+ return colspan;
+}
+
+nsresult
+nsMathMLmtdFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // Attributes specific to <mtd>:
+ // groupalign : Not yet supported
+ // rowalign : here
+ // columnalign : here
+ // rowspan : here
+ // columnspan : here
+
+ if (aAttribute == nsGkAtoms::rowalign_ ||
+ aAttribute == nsGkAtoms::columnalign_) {
+
+ nsPresContext* presContext = PresContext();
+ presContext->PropertyTable()->Delete(this, AttributeToProperty(aAttribute));
+
+ // Reparse the attribute.
+ ParseFrameAttribute(this, aAttribute, false);
+ return NS_OK;
+ }
+
+ if (aAttribute == nsGkAtoms::rowspan ||
+ aAttribute == nsGkAtoms::columnspan_) {
+ // use the naming expected by the base class
+ if (aAttribute == nsGkAtoms::columnspan_)
+ aAttribute = nsGkAtoms::colspan;
+ return nsTableCellFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ }
+
+ return NS_OK;
+}
+
+uint8_t
+nsMathMLmtdFrame::GetVerticalAlign() const
+{
+ // Set the default alignment in case no alignment was specified
+ uint8_t alignment = nsTableCellFrame::GetVerticalAlign();
+
+ nsTArray<int8_t>* alignmentList = FindCellProperty(this, RowAlignProperty());
+
+ if (alignmentList) {
+ int32_t rowIndex;
+ GetRowIndex(rowIndex);
+
+ // If the row number is greater than the number of provided rowalign values,
+ // we simply repeat the last value.
+ if (rowIndex < (int32_t)alignmentList->Length())
+ alignment = alignmentList->ElementAt(rowIndex);
+ else
+ alignment = alignmentList->ElementAt(alignmentList->Length() - 1);
+ }
+
+ return alignment;
+}
+
+nsresult
+nsMathMLmtdFrame::ProcessBorders(nsTableFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists)
+{
+ aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
+ nsDisplaymtdBorder(aBuilder, this));
+ return NS_OK;
+}
+
+LogicalMargin
+nsMathMLmtdFrame::GetBorderWidth(WritingMode aWM) const
+{
+ nsStyleBorder styleBorder = *StyleBorder();
+ ApplyBorderToStyle(this, styleBorder);
+ return LogicalMargin(aWM, styleBorder.GetComputedBorder());
+}
+
+nsMargin
+nsMathMLmtdFrame::GetBorderOverflow()
+{
+ nsStyleBorder styleBorder = *StyleBorder();
+ ApplyBorderToStyle(this, styleBorder);
+ nsMargin overflow = ComputeBorderOverflow(this, styleBorder);
+ return overflow;
+}
+
+// --------
+// implementation of nsMathMLmtdInnerFrame
+
+NS_QUERYFRAME_HEAD(nsMathMLmtdInnerFrame)
+ NS_QUERYFRAME_ENTRY(nsIMathMLFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
+
+nsContainerFrame*
+NS_NewMathMLmtdInnerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmtdInnerFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdInnerFrame)
+
+nsMathMLmtdInnerFrame::nsMathMLmtdInnerFrame(nsStyleContext* aContext)
+ : nsBlockFrame(aContext)
+{
+ // Make a copy of the parent nsStyleText for later modification.
+ mUniqueStyleText = new (PresContext()) nsStyleText(*StyleText());
+}
+
+nsMathMLmtdInnerFrame::~nsMathMLmtdInnerFrame()
+{
+ mUniqueStyleText->Destroy(PresContext());
+}
+
+void
+nsMathMLmtdInnerFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ // Let the base class do the reflow
+ nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ // more about <maligngroup/> and <malignmark/> later
+ // ...
+}
+
+const
+nsStyleText* nsMathMLmtdInnerFrame::StyleTextForLineLayout()
+{
+ // Set the default alignment in case nothing was specified
+ uint8_t alignment = StyleText()->mTextAlign;
+
+ nsTArray<int8_t>* alignmentList =
+ FindCellProperty(this, ColumnAlignProperty());
+
+ if (alignmentList) {
+ nsMathMLmtdFrame* cellFrame = (nsMathMLmtdFrame*)GetParent();
+ int32_t columnIndex;
+ cellFrame->GetColIndex(columnIndex);
+
+ // If the column number is greater than the number of provided columalign
+ // values, we simply repeat the last value.
+ if (columnIndex < (int32_t)alignmentList->Length())
+ alignment = alignmentList->ElementAt(columnIndex);
+ else
+ alignment = alignmentList->ElementAt(alignmentList->Length() - 1);
+ }
+
+ mUniqueStyleText->mTextAlign = alignment;
+ return mUniqueStyleText;
+}
+
+/* virtual */ void
+nsMathMLmtdInnerFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsBlockFrame::DidSetStyleContext(aOldStyleContext);
+ mUniqueStyleText->Destroy(PresContext());
+ mUniqueStyleText = new (PresContext()) nsStyleText(*StyleText());
+}
diff --git a/layout/mathml/nsMathMLmtableFrame.h b/layout/mathml/nsMathMLmtableFrame.h
new file mode 100644
index 000000000..5cb4fc4a2
--- /dev/null
+++ b/layout/mathml/nsMathMLmtableFrame.h
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmtableFrame_h___
+#define nsMathMLmtableFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+#include "nsBlockFrame.h"
+#include "nsTableWrapperFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableCellFrame.h"
+
+//
+// <mtable> -- table or matrix
+//
+
+class nsMathMLmtableWrapperFrame : public nsTableWrapperFrame,
+ public nsMathMLFrame
+{
+public:
+ friend nsContainerFrame*
+ NS_NewMathMLmtableOuterFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // overloaded nsTableWrapperFrame methods
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsTableWrapperFrame::IsFrameOfType(aFlags & ~(nsIFrame::eMathML));
+ }
+
+protected:
+ explicit nsMathMLmtableWrapperFrame(nsStyleContext* aContext)
+ : nsTableWrapperFrame(aContext) {}
+ virtual ~nsMathMLmtableWrapperFrame();
+
+ // helper to find the row frame at a given index, positive or negative, e.g.,
+ // 1..n means the first row down to the last row, -1..-n means the last row
+ // up to the first row. Used for alignments that are relative to a given row
+ nsIFrame*
+ GetRowFrameAt(int32_t aRowIndex);
+}; // class nsMathMLmtableWrapperFrame
+
+// --------------
+
+class nsMathMLmtableFrame : public nsTableFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsMathMLmtableFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsContainerFrame*
+ NS_NewMathMLmtableFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ // Overloaded nsTableFrame methods
+
+ virtual void
+ SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual void
+ AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override
+ {
+ nsTableFrame::AppendFrames(aListID, aFrameList);
+ RestyleTable();
+ }
+
+ virtual void
+ InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override
+ {
+ nsTableFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
+ RestyleTable();
+ }
+
+ virtual void
+ RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override
+ {
+ nsTableFrame::RemoveFrame(aListID, aOldFrame);
+ RestyleTable();
+ }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsTableFrame::IsFrameOfType(aFlags & ~(nsIFrame::eMathML));
+ }
+
+ // helper to restyle and reflow the table when a row is changed -- since MathML
+ // attributes are inter-dependent and row/colspan can affect the table, it is
+ // safer (albeit grossly suboptimal) to just relayout the whole thing.
+ void RestyleTable();
+
+ /** helper to get the column spacing style value */
+ nscoord GetColSpacing(int32_t aColIndex) override;
+
+ /** Sums the combined cell spacing between the columns aStartColIndex to
+ * aEndColIndex.
+ */
+ nscoord GetColSpacing(int32_t aStartColIndex,
+ int32_t aEndColIndex) override;
+
+ /** helper to get the row spacing style value */
+ nscoord GetRowSpacing(int32_t aRowIndex) override;
+
+ /** Sums the combined cell spacing between the rows aStartRowIndex to
+ * aEndRowIndex.
+ */
+ nscoord GetRowSpacing(int32_t aStartRowIndex,
+ int32_t aEndRowIndex) override;
+
+ void SetColSpacingArray(const nsTArray<nscoord>& aColSpacing)
+ {
+ mColSpacing = aColSpacing;
+ }
+
+ void SetRowSpacingArray(const nsTArray<nscoord>& aRowSpacing)
+ {
+ mRowSpacing = aRowSpacing;
+ }
+
+ void SetFrameSpacing(nscoord aSpacingX, nscoord aSpacingY)
+ {
+ mFrameSpacingX = aSpacingX;
+ mFrameSpacingY = aSpacingY;
+ }
+
+ /** Determines whether the placement of table cells is determined by CSS
+ * spacing based on padding and border-spacing, or one based upon the
+ * rowspacing, columnspacing and framespacing attributes. The second
+ * approach is used if the user specifies at least one of those attributes.
+ */
+ void SetUseCSSSpacing();
+ bool GetUseCSSSpacing() { return mUseCSSSpacing; }
+
+protected:
+ explicit nsMathMLmtableFrame(nsStyleContext* aContext)
+ : nsTableFrame(aContext) {}
+ virtual ~nsMathMLmtableFrame();
+
+private:
+ nsTArray<nscoord> mColSpacing;
+ nsTArray<nscoord> mRowSpacing;
+ nscoord mFrameSpacingX;
+ nscoord mFrameSpacingY;
+ bool mUseCSSSpacing;
+}; // class nsMathMLmtableFrame
+
+// --------------
+
+class nsMathMLmtrFrame : public nsTableRowFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsContainerFrame*
+ NS_NewMathMLmtrFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ // overloaded nsTableRowFrame methods
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void
+ AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override
+ {
+ nsTableRowFrame::AppendFrames(aListID, aFrameList);
+ RestyleTable();
+ }
+
+ virtual void
+ InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override
+ {
+ nsTableRowFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
+ RestyleTable();
+ }
+
+ virtual void
+ RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override
+ {
+ nsTableRowFrame::RemoveFrame(aListID, aOldFrame);
+ RestyleTable();
+ }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsTableRowFrame::IsFrameOfType(aFlags & ~(nsIFrame::eMathML));
+ }
+
+ // helper to restyle and reflow the table -- @see nsMathMLmtableFrame.
+ void RestyleTable()
+ {
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame && tableFrame->IsFrameOfType(nsIFrame::eMathML)) {
+ // relayout the table
+ ((nsMathMLmtableFrame*)tableFrame)->RestyleTable();
+ }
+ }
+
+protected:
+ explicit nsMathMLmtrFrame(nsStyleContext* aContext)
+ : nsTableRowFrame(aContext) {}
+ virtual ~nsMathMLmtrFrame();
+}; // class nsMathMLmtrFrame
+
+// --------------
+
+class nsMathMLmtdFrame : public nsTableCellFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsContainerFrame*
+ NS_NewMathMLmtdFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext,
+ nsTableFrame* aTableFrame);
+
+ // overloaded nsTableCellFrame methods
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual uint8_t GetVerticalAlign() const override;
+ virtual nsresult ProcessBorders(nsTableFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ virtual int32_t GetRowSpan() override;
+ virtual int32_t GetColSpan() override;
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsTableCellFrame::IsFrameOfType(aFlags & ~(nsIFrame::eMathML));
+ }
+
+ virtual LogicalMargin GetBorderWidth(WritingMode aWM) const override;
+
+ virtual nsMargin GetBorderOverflow() override;
+
+protected:
+ nsMathMLmtdFrame(nsStyleContext* aContext, nsTableFrame* aTableFrame)
+ : nsTableCellFrame(aContext, aTableFrame) {}
+ virtual ~nsMathMLmtdFrame();
+}; // class nsMathMLmtdFrame
+
+// --------------
+
+class nsMathMLmtdInnerFrame : public nsBlockFrame,
+ public nsMathMLFrame
+{
+public:
+ friend nsContainerFrame*
+ NS_NewMathMLmtdInnerFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // Overloaded nsIMathMLFrame methods
+
+ NS_IMETHOD
+ UpdatePresentationDataFromChildAt(int32_t aFirstIndex,
+ int32_t aLastIndex,
+ uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate) override
+ {
+ nsMathMLContainerFrame::PropagatePresentationDataFromChildAt(this,
+ aFirstIndex, aLastIndex, aFlagsValues, aFlagsToUpdate);
+ return NS_OK;
+ }
+
+ virtual void
+ Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsBlockFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eMathML | nsIFrame::eExcludesIgnorableWhitespace));
+ }
+
+ virtual const nsStyleText* StyleTextForLineLayout() override;
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ bool
+ IsMrowLike() override
+ {
+ return mFrames.FirstChild() !=
+ mFrames.LastChild() ||
+ !mFrames.FirstChild();
+ }
+
+protected:
+ explicit nsMathMLmtdInnerFrame(nsStyleContext* aContext);
+ virtual ~nsMathMLmtdInnerFrame();
+
+ nsStyleText* mUniqueStyleText;
+
+}; // class nsMathMLmtdInnerFrame
+
+#endif /* nsMathMLmtableFrame_h___ */
diff --git a/layout/mathml/nsMathMLmunderoverFrame.cpp b/layout/mathml/nsMathMLmunderoverFrame.cpp
new file mode 100644
index 000000000..8fb932719
--- /dev/null
+++ b/layout/mathml/nsMathMLmunderoverFrame.cpp
@@ -0,0 +1,686 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmunderoverFrame.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsMathMLmmultiscriptsFrame.h"
+#include <algorithm>
+#include "gfxMathTable.h"
+
+//
+// <munderover> -- attach an underscript-overscript pair to a base - implementation
+// <mover> -- attach an overscript to a base - implementation
+// <munder> -- attach an underscript to a base - implementation
+//
+
+nsIFrame*
+NS_NewMathMLmunderoverFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLmunderoverFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmunderoverFrame)
+
+nsMathMLmunderoverFrame::~nsMathMLmunderoverFrame()
+{
+}
+
+nsresult
+nsMathMLmunderoverFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (nsGkAtoms::accent_ == aAttribute ||
+ nsGkAtoms::accentunder_ == aAttribute) {
+ // When we have automatic data to update within ourselves, we ask our
+ // parent to re-layout its children
+ return ReLayoutChildren(GetParent());
+ }
+
+ return nsMathMLContainerFrame::
+ AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+NS_IMETHODIMP
+nsMathMLmunderoverFrame::UpdatePresentationData(uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate)
+{
+ nsMathMLContainerFrame::UpdatePresentationData(aFlagsValues, aFlagsToUpdate);
+ // disable the stretch-all flag if we are going to act like a subscript-superscript pair
+ if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) &&
+ StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_INLINE) {
+ mPresentationData.flags &= ~NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+ }
+ else {
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMathMLmunderoverFrame::InheritAutomaticData(nsIFrame* aParent)
+{
+ // let the base class get the default from our parent
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+
+ return NS_OK;
+}
+
+uint8_t
+nsMathMLmunderoverFrame::ScriptIncrement(nsIFrame* aFrame)
+{
+ nsIFrame* child = mFrames.FirstChild();
+ if (!aFrame || aFrame == child) {
+ return 0;
+ }
+ child = child->GetNextSibling();
+ if (aFrame == child) {
+ if (mContent->IsMathMLElement(nsGkAtoms::mover_)) {
+ return mIncrementOver ? 1 : 0;
+ }
+ return mIncrementUnder ? 1 : 0;
+ }
+ if (child && aFrame == child->GetNextSibling()) {
+ // must be a over frame of munderover
+ return mIncrementOver ? 1 : 0;
+ }
+ return 0; // frame not found
+}
+
+NS_IMETHODIMP
+nsMathMLmunderoverFrame::TransmitAutomaticData()
+{
+ // At this stage, all our children are in sync and we can fully
+ // resolve our own mEmbellishData struct
+ //---------------------------------------------------------------------
+
+ /*
+ The REC says:
+
+ As regards munder (respectively mover) :
+ The default value of accentunder is false, unless underscript
+ is an <mo> element or an embellished operator. If underscript is
+ an <mo> element, the value of its accent attribute is used as the
+ default value of accentunder. If underscript is an embellished
+ operator, the accent attribute of the <mo> element at its
+ core is used as the default value. As with all attributes, an
+ explicitly given value overrides the default.
+
+XXX The winner is the outermost setting in conflicting settings like these:
+<munder accentunder='true'>
+ <mi>...</mi>
+ <mo accentunder='false'> ... </mo>
+</munder>
+
+ As regards munderover:
+ The accent and accentunder attributes have the same effect as
+ the attributes with the same names on <mover> and <munder>,
+ respectively. Their default values are also computed in the
+ same manner as described for those elements, with the default
+ value of accent depending on overscript and the default value
+ of accentunder depending on underscript.
+ */
+
+ nsIFrame* overscriptFrame = nullptr;
+ nsIFrame* underscriptFrame = nullptr;
+ nsIFrame* baseFrame = mFrames.FirstChild();
+
+ if (baseFrame) {
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ underscriptFrame = baseFrame->GetNextSibling();
+ } else {
+ NS_ASSERTION(mContent->IsMathMLElement(nsGkAtoms::mover_),
+ "mContent->NodeInfo()->NameAtom() not recognized");
+ overscriptFrame = baseFrame->GetNextSibling();
+ }
+ }
+ if (underscriptFrame &&
+ mContent->IsMathMLElement(nsGkAtoms::munderover_)) {
+ overscriptFrame = underscriptFrame->GetNextSibling();
+
+ }
+
+ // if our base is an embellished operator, let its state bubble to us (in particular,
+ // this is where we get the flag for NS_MATHML_EMBELLISH_MOVABLELIMITS). Our flags
+ // are reset to the default values of false if the base frame isn't embellished.
+ mPresentationData.baseFrame = baseFrame;
+ GetEmbellishDataFrom(baseFrame, mEmbellishData);
+
+ // The default value of accentunder is false, unless the underscript is embellished
+ // and its core <mo> is an accent
+ nsEmbellishData embellishData;
+ nsAutoString value;
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ GetEmbellishDataFrom(underscriptFrame, embellishData);
+ if (NS_MATHML_EMBELLISH_IS_ACCENT(embellishData.flags)) {
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTUNDER;
+ } else {
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTUNDER;
+ }
+
+ // if we have an accentunder attribute, it overrides what the underscript said
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accentunder_, value)) {
+ if (value.EqualsLiteral("true")) {
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTUNDER;
+ } else if (value.EqualsLiteral("false")) {
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTUNDER;
+ }
+ }
+ }
+
+ // The default value of accent is false, unless the overscript is embellished
+ // and its core <mo> is an accent
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover_,
+ nsGkAtoms::munderover_)) {
+ GetEmbellishDataFrom(overscriptFrame, embellishData);
+ if (NS_MATHML_EMBELLISH_IS_ACCENT(embellishData.flags)) {
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTOVER;
+ } else {
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTOVER;
+ }
+
+ // if we have an accent attribute, it overrides what the overscript said
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accent_, value)) {
+ if (value.EqualsLiteral("true")) {
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTOVER;
+ } else if (value.EqualsLiteral("false")) {
+ mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTOVER;
+ }
+ }
+ }
+
+ bool subsupDisplay =
+ NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) &&
+ StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_INLINE;
+
+ // disable the stretch-all flag if we are going to act like a superscript
+ if (subsupDisplay) {
+ mPresentationData.flags &= ~NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+ }
+
+ // Now transmit any change that we want to our children so that they
+ // can update their mPresentationData structs
+ //---------------------------------------------------------------------
+
+ /* The REC says:
+ Within underscript, <munderover> always sets displaystyle to "false",
+ but increments scriptlevel by 1 only when accentunder is "false".
+
+ Within overscript, <munderover> always sets displaystyle to "false",
+ but increments scriptlevel by 1 only when accent is "false".
+
+ Within subscript and superscript it increments scriptlevel by 1, and
+ sets displaystyle to "false", but leaves both attributes unchanged within
+ base.
+
+ The TeXBook treats 'over' like a superscript, so p.141 or Rule 13a
+ say it shouldn't be compressed. However, The TeXBook says
+ that math accents and \overline change uncramped styles to their
+ cramped counterparts.
+ */
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover_,
+ nsGkAtoms::munderover_)) {
+ uint32_t compress = NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)
+ ? NS_MATHML_COMPRESSED : 0;
+ mIncrementOver =
+ !NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) ||
+ subsupDisplay;
+ SetIncrementScriptLevel(mContent->IsMathMLElement(nsGkAtoms::mover_) ? 1 : 2, mIncrementOver);
+ if (mIncrementOver) {
+ PropagateFrameFlagFor(overscriptFrame,
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+ PropagatePresentationDataFor(overscriptFrame, compress, compress);
+ }
+ /*
+ The TeXBook treats 'under' like a subscript, so p.141 or Rule 13a
+ say it should be compressed
+ */
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ mIncrementUnder =
+ !NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags) ||
+ subsupDisplay;
+ SetIncrementScriptLevel(1, mIncrementUnder);
+ if (mIncrementUnder) {
+ PropagateFrameFlagFor(underscriptFrame,
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+ PropagatePresentationDataFor(underscriptFrame,
+ NS_MATHML_COMPRESSED,
+ NS_MATHML_COMPRESSED);
+ }
+
+ /* Set flags for dtls font feature settings.
+
+ dtls
+ Dotless Forms
+ This feature provides dotless forms for Math Alphanumeric
+ characters, such as U+1D422 MATHEMATICAL BOLD SMALL I,
+ U+1D423 MATHEMATICAL BOLD SMALL J, U+1D456
+ U+MATHEMATICAL ITALIC SMALL I, U+1D457 MATHEMATICAL ITALIC
+ SMALL J, and so on.
+ The dotless forms are to be used as base forms for placing
+ mathematical accents over them.
+
+ To opt out of this change, add the following to the stylesheet:
+ "font-feature-settings: 'dtls' 0"
+ */
+ if (overscriptFrame &&
+ NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) &&
+ !NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags)) {
+ PropagatePresentationDataFor(baseFrame, NS_MATHML_DTLS, NS_MATHML_DTLS);
+ }
+
+ return NS_OK;
+}
+
+/*
+The REC says:
+* If the base is an operator with movablelimits="true" (or an embellished
+ operator whose <mo> element core has movablelimits="true"), and
+ displaystyle="false", then underscript and overscript are drawn in
+ a subscript and superscript position, respectively. In this case,
+ the accent and accentunder attributes are ignored. This is often
+ used for limits on symbols such as &sum;.
+
+i.e.,:
+ if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishDataflags) &&
+ StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_INLINE) {
+ // place like subscript-superscript pair
+ }
+ else {
+ // place like underscript-overscript pair
+ }
+*/
+
+/* virtual */ nsresult
+nsMathMLmunderoverFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize)
+{
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) &&
+ StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_INLINE) {
+ //place like sub sup or subsup
+ if (mContent->IsMathMLElement(nsGkAtoms::munderover_)) {
+ return nsMathMLmmultiscriptsFrame::PlaceMultiScript(PresContext(),
+ aDrawTarget,
+ aPlaceOrigin,
+ aDesiredSize,
+ this, 0, 0,
+ fontSizeInflation);
+ } else if (mContent->IsMathMLElement( nsGkAtoms::munder_)) {
+ return nsMathMLmmultiscriptsFrame::PlaceMultiScript(PresContext(),
+ aDrawTarget,
+ aPlaceOrigin,
+ aDesiredSize,
+ this, 0, 0,
+ fontSizeInflation);
+ } else {
+ NS_ASSERTION(mContent->IsMathMLElement(nsGkAtoms::mover_),
+ "mContent->NodeInfo()->NameAtom() not recognized");
+ return nsMathMLmmultiscriptsFrame::PlaceMultiScript(PresContext(),
+ aDrawTarget,
+ aPlaceOrigin,
+ aDesiredSize,
+ this, 0, 0,
+ fontSizeInflation);
+ }
+
+ }
+
+ ////////////////////////////////////
+ // Get the children's desired sizes
+
+ nsBoundingMetrics bmBase, bmUnder, bmOver;
+ ReflowOutput baseSize(aDesiredSize.GetWritingMode());
+ ReflowOutput underSize(aDesiredSize.GetWritingMode());
+ ReflowOutput overSize(aDesiredSize.GetWritingMode());
+ nsIFrame* overFrame = nullptr;
+ nsIFrame* underFrame = nullptr;
+ nsIFrame* baseFrame = mFrames.FirstChild();
+ underSize.SetBlockStartAscent(0);
+ overSize.SetBlockStartAscent(0);
+ bool haveError = false;
+ if (baseFrame) {
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ underFrame = baseFrame->GetNextSibling();
+ } else if (mContent->IsMathMLElement(nsGkAtoms::mover_)) {
+ overFrame = baseFrame->GetNextSibling();
+ }
+ }
+ if (underFrame && mContent->IsMathMLElement(nsGkAtoms::munderover_)) {
+ overFrame = underFrame->GetNextSibling();
+ }
+
+ if (mContent->IsMathMLElement(nsGkAtoms::munder_)) {
+ if (!baseFrame || !underFrame || underFrame->GetNextSibling()) {
+ // report an error, encourage people to get their markups in order
+ haveError = true;
+ }
+ }
+ if (mContent->IsMathMLElement(nsGkAtoms::mover_)) {
+ if (!baseFrame || !overFrame || overFrame->GetNextSibling()) {
+ // report an error, encourage people to get their markups in order
+ haveError = true;
+ }
+ }
+ if (mContent->IsMathMLElement(nsGkAtoms::munderover_)) {
+ if (!baseFrame || !underFrame || !overFrame || overFrame->GetNextSibling()) {
+ // report an error, encourage people to get their markups in order
+ haveError = true;
+ }
+ }
+ if (haveError) {
+ if (aPlaceOrigin) {
+ ReportChildCountError();
+ }
+ return ReflowError(aDrawTarget, aDesiredSize);
+ }
+ GetReflowAndBoundingMetricsFor(baseFrame, baseSize, bmBase);
+ if (underFrame) {
+ GetReflowAndBoundingMetricsFor(underFrame, underSize, bmUnder);
+ }
+ if (overFrame) {
+ GetReflowAndBoundingMetricsFor(overFrame, overSize, bmOver);
+ }
+
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+
+ ////////////////////
+ // Place Children
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+
+ nscoord xHeight = fm->XHeight();
+ nscoord oneDevPixel = fm->AppUnitsPerDevPixel();
+ gfxFont* mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
+
+ nscoord ruleThickness;
+ GetRuleThickness (aDrawTarget, fm, ruleThickness);
+
+ nscoord correction = 0;
+ GetItalicCorrection (bmBase, correction);
+
+ // there are 2 different types of placement depending on
+ // whether we want an accented under or not
+
+ nscoord underDelta1 = 0; // gap between base and underscript
+ nscoord underDelta2 = 0; // extra space beneath underscript
+
+ if (!NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags)) {
+ // Rule 13a, App. G, TeXbook
+ nscoord bigOpSpacing2, bigOpSpacing4, bigOpSpacing5, dummy;
+ GetBigOpSpacings (fm,
+ dummy, bigOpSpacing2,
+ dummy, bigOpSpacing4,
+ bigOpSpacing5);
+ if (mathFont) {
+ // XXXfredw The Open Type MATH table has some StretchStack* parameters
+ // that we may use when the base is a stretchy horizontal operator. See
+ // bug 963131.
+ bigOpSpacing2 =
+ mathFont->MathTable()->Constant(gfxMathTable::LowerLimitGapMin,
+ oneDevPixel);
+ bigOpSpacing4 =
+ mathFont->MathTable()->Constant(gfxMathTable::LowerLimitBaselineDropMin,
+ oneDevPixel);
+ bigOpSpacing5 = 0;
+ }
+ underDelta1 = std::max(bigOpSpacing2, (bigOpSpacing4 - bmUnder.ascent));
+ underDelta2 = bigOpSpacing5;
+ }
+ else {
+ // No corresponding rule in TeXbook - we are on our own here
+ // XXX tune the gap delta between base and underscript
+ // XXX Should we use Rule 10 like \underline does?
+ // XXXfredw Perhaps use the Underbar* parameters of the MATH table. See
+ // bug 963125.
+ underDelta1 = ruleThickness + onePixel/2;
+ underDelta2 = ruleThickness;
+ }
+ // empty under?
+ if (!(bmUnder.ascent + bmUnder.descent)) {
+ underDelta1 = 0;
+ underDelta2 = 0;
+ }
+
+ nscoord overDelta1 = 0; // gap between base and overscript
+ nscoord overDelta2 = 0; // extra space above overscript
+
+ if (!NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)) {
+ // Rule 13a, App. G, TeXbook
+ // XXXfredw The Open Type MATH table has some StretchStack* parameters
+ // that we may use when the base is a stretchy horizontal operator. See
+ // bug 963131.
+ nscoord bigOpSpacing1, bigOpSpacing3, bigOpSpacing5, dummy;
+ GetBigOpSpacings (fm,
+ bigOpSpacing1, dummy,
+ bigOpSpacing3, dummy,
+ bigOpSpacing5);
+ if (mathFont) {
+ // XXXfredw The Open Type MATH table has some StretchStack* parameters
+ // that we may use when the base is a stretchy horizontal operator. See
+ // bug 963131.
+ bigOpSpacing1 =
+ mathFont->MathTable()->Constant(gfxMathTable::UpperLimitGapMin,
+ oneDevPixel);
+ bigOpSpacing3 =
+ mathFont->MathTable()->Constant(gfxMathTable::UpperLimitBaselineRiseMin,
+ oneDevPixel);
+ bigOpSpacing5 = 0;
+ }
+ overDelta1 = std::max(bigOpSpacing1, (bigOpSpacing3 - bmOver.descent));
+ overDelta2 = bigOpSpacing5;
+
+ // XXX This is not a TeX rule...
+ // delta1 (as computed abvove) can become really big when bmOver.descent is
+ // negative, e.g., if the content is &OverBar. In such case, we use the height
+ if (bmOver.descent < 0)
+ overDelta1 = std::max(bigOpSpacing1, (bigOpSpacing3 - (bmOver.ascent + bmOver.descent)));
+ }
+ else {
+ // Rule 12, App. G, TeXbook
+ // We are going to modify this rule to make it more general.
+ // The idea behind Rule 12 in the TeXBook is to keep the accent
+ // as close to the base as possible, while ensuring that the
+ // distance between the *baseline* of the accent char and
+ // the *baseline* of the base is atleast x-height.
+ // The idea is that for normal use, we would like all the accents
+ // on a line to line up atleast x-height above the baseline
+ // if possible.
+ // When the ascent of the base is >= x-height,
+ // the baseline of the accent char is placed just above the base
+ // (specifically, the baseline of the accent char is placed
+ // above the baseline of the base by the ascent of the base).
+ // For ease of implementation,
+ // this assumes that the font-designer designs accents
+ // in such a way that the bottom of the accent is atleast x-height
+ // above its baseline, otherwise there will be collisions
+ // with the base. Also there should be proper padding between
+ // the bottom of the accent char and its baseline.
+ // The above rule may not be obvious from a first
+ // reading of rule 12 in the TeXBook !!!
+ // The mathml <mover> tag can use accent chars that
+ // do not follow this convention. So we modify TeX's rule
+ // so that TeX's rule gets subsumed for accents that follow
+ // TeX's convention,
+ // while also allowing accents that do not follow the convention :
+ // we try to keep the *bottom* of the accent char atleast x-height
+ // from the baseline of the base char. we also slap on an extra
+ // padding between the accent and base chars.
+ overDelta1 = ruleThickness + onePixel/2;
+ nscoord accentBaseHeight = xHeight;
+ if (mathFont) {
+ accentBaseHeight =
+ mathFont->MathTable()->Constant(gfxMathTable::AccentBaseHeight,
+ oneDevPixel);
+ }
+ if (bmBase.ascent < accentBaseHeight) {
+ // also ensure at least accentBaseHeight above the baseline of the base
+ overDelta1 += accentBaseHeight - bmBase.ascent;
+ }
+ overDelta2 = ruleThickness;
+ }
+ // empty over?
+ if (!(bmOver.ascent + bmOver.descent)) {
+ overDelta1 = 0;
+ overDelta2 = 0;
+ }
+
+ nscoord dxBase = 0, dxOver = 0, dxUnder = 0;
+ nsAutoString valueAlign;
+ enum {
+ center,
+ left,
+ right
+ } alignPosition = center;
+
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::align, valueAlign)) {
+ if (valueAlign.EqualsLiteral("left")) {
+ alignPosition = left;
+ } else if (valueAlign.EqualsLiteral("right")) {
+ alignPosition = right;
+ }
+ }
+
+ //////////
+ // pass 1, do what <mover> does: attach the overscript on the base
+
+ // Ad-hoc - This is to override fonts which have ready-made _accent_
+ // glyphs with negative lbearing and rbearing. We want to position
+ // the overscript ourselves
+ nscoord overWidth = bmOver.width;
+ if (!overWidth && (bmOver.rightBearing - bmOver.leftBearing > 0)) {
+ overWidth = bmOver.rightBearing - bmOver.leftBearing;
+ dxOver = -bmOver.leftBearing;
+ }
+
+ if (NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)) {
+ mBoundingMetrics.width = bmBase.width;
+ if (alignPosition == center) {
+ dxOver += correction;
+ }
+ }
+ else {
+ mBoundingMetrics.width = std::max(bmBase.width, overWidth);
+ if (alignPosition == center) {
+ dxOver += correction/2;
+ }
+ }
+
+ if (alignPosition == center) {
+ dxOver += (mBoundingMetrics.width - overWidth)/2;
+ dxBase = (mBoundingMetrics.width - bmBase.width)/2;
+ } else if (alignPosition == right) {
+ dxOver += mBoundingMetrics.width - overWidth;
+ dxBase = mBoundingMetrics.width - bmBase.width;
+ }
+
+ mBoundingMetrics.ascent =
+ bmBase.ascent + overDelta1 + bmOver.ascent + bmOver.descent;
+ mBoundingMetrics.descent = bmBase.descent;
+ mBoundingMetrics.leftBearing =
+ std::min(dxBase + bmBase.leftBearing, dxOver + bmOver.leftBearing);
+ mBoundingMetrics.rightBearing =
+ std::max(dxBase + bmBase.rightBearing, dxOver + bmOver.rightBearing);
+
+ //////////
+ // pass 2, do what <munder> does: attach the underscript on the previous
+ // result. We conceptually view the previous result as an "anynomous base"
+ // from where to attach the underscript. Hence if the underscript is empty,
+ // we should end up like <mover>. If the overscript is empty, we should
+ // end up like <munder>.
+
+ nsBoundingMetrics bmAnonymousBase = mBoundingMetrics;
+ nscoord ascentAnonymousBase =
+ std::max(mBoundingMetrics.ascent + overDelta2,
+ overSize.BlockStartAscent() + bmOver.descent +
+ overDelta1 + bmBase.ascent);
+ ascentAnonymousBase = std::max(ascentAnonymousBase,
+ baseSize.BlockStartAscent());
+
+ // Width of non-spacing marks is zero so use left and right bearing.
+ nscoord underWidth = bmUnder.width;
+ if (!underWidth) {
+ underWidth = bmUnder.rightBearing - bmUnder.leftBearing;
+ dxUnder = -bmUnder.leftBearing;
+ }
+
+ nscoord maxWidth = std::max(bmAnonymousBase.width, underWidth);
+ if (alignPosition == center &&
+ !NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags)) {
+ GetItalicCorrection(bmAnonymousBase, correction);
+ dxUnder += -correction/2;
+ }
+ nscoord dxAnonymousBase = 0;
+ if (alignPosition == center) {
+ dxUnder += (maxWidth - underWidth)/2;
+ dxAnonymousBase = (maxWidth - bmAnonymousBase.width)/2;
+ } else if (alignPosition == right) {
+ dxUnder += maxWidth - underWidth;
+ dxAnonymousBase = maxWidth - bmAnonymousBase.width;
+ }
+
+ // adjust the offsets of the real base and overscript since their
+ // final offsets should be relative to us...
+ dxOver += dxAnonymousBase;
+ dxBase += dxAnonymousBase;
+
+ mBoundingMetrics.width =
+ std::max(dxAnonymousBase + bmAnonymousBase.width, dxUnder + bmUnder.width);
+ // At this point, mBoundingMetrics.ascent = bmAnonymousBase.ascent
+ mBoundingMetrics.descent =
+ bmAnonymousBase.descent + underDelta1 + bmUnder.ascent + bmUnder.descent;
+ mBoundingMetrics.leftBearing =
+ std::min(dxAnonymousBase + bmAnonymousBase.leftBearing, dxUnder + bmUnder.leftBearing);
+ mBoundingMetrics.rightBearing =
+ std::max(dxAnonymousBase + bmAnonymousBase.rightBearing, dxUnder + bmUnder.rightBearing);
+
+ aDesiredSize.SetBlockStartAscent(ascentAnonymousBase);
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() +
+ std::max(mBoundingMetrics.descent + underDelta2,
+ bmAnonymousBase.descent + underDelta1 + bmUnder.ascent +
+ underSize.Height() - underSize.BlockStartAscent());
+ aDesiredSize.Height() = std::max(aDesiredSize.Height(),
+ aDesiredSize.BlockStartAscent() +
+ baseSize.Height() - baseSize.BlockStartAscent());
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ if (aPlaceOrigin) {
+ nscoord dy;
+ // place overscript
+ if (overFrame) {
+ dy = aDesiredSize.BlockStartAscent() -
+ mBoundingMetrics.ascent + bmOver.ascent -
+ overSize.BlockStartAscent();
+ FinishReflowChild (overFrame, PresContext(), overSize, nullptr, dxOver, dy, 0);
+ }
+ // place base
+ dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent();
+ FinishReflowChild (baseFrame, PresContext(), baseSize, nullptr, dxBase, dy, 0);
+ // place underscript
+ if (underFrame) {
+ dy = aDesiredSize.BlockStartAscent() +
+ mBoundingMetrics.descent - bmUnder.descent -
+ underSize.BlockStartAscent();
+ FinishReflowChild (underFrame, PresContext(), underSize, nullptr,
+ dxUnder, dy, 0);
+ }
+ }
+ return NS_OK;
+}
diff --git a/layout/mathml/nsMathMLmunderoverFrame.h b/layout/mathml/nsMathMLmunderoverFrame.h
new file mode 100644
index 000000000..bbb0ea8f3
--- /dev/null
+++ b/layout/mathml/nsMathMLmunderoverFrame.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLmunderoverFrame_h___
+#define nsMathMLmunderoverFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLContainerFrame.h"
+
+//
+// <munderover> -- attach an underscript-overscript pair to a base
+//
+
+class nsMathMLmunderoverFrame : public nsMathMLContainerFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLmunderoverFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ virtual nsresult
+ Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ NS_IMETHOD
+ UpdatePresentationData(uint32_t aFlagsValues,
+ uint32_t aFlagsToUpdate) override;
+
+ virtual nsresult
+ AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ uint8_t
+ ScriptIncrement(nsIFrame* aFrame) override;
+
+protected:
+ explicit nsMathMLmunderoverFrame(nsStyleContext* aContext) : nsMathMLContainerFrame(aContext),
+ mIncrementUnder(false),
+ mIncrementOver(false) {}
+ virtual ~nsMathMLmunderoverFrame();
+
+private:
+ bool mIncrementUnder;
+ bool mIncrementOver;
+};
+
+
+#endif /* nsMathMLmunderoverFrame_h___ */
diff --git a/layout/mathml/nsMathMLsemanticsFrame.cpp b/layout/mathml/nsMathMLsemanticsFrame.cpp
new file mode 100644
index 000000000..59029e209
--- /dev/null
+++ b/layout/mathml/nsMathMLsemanticsFrame.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsMathMLsemanticsFrame.h"
+#include "nsMimeTypes.h"
+#include "mozilla/gfx/2D.h"
+
+//
+// <semantics> -- associate annotations with a MathML expression
+//
+
+nsIFrame*
+NS_NewMathMLsemanticsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMathMLsemanticsFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLsemanticsFrame)
+
+nsMathMLsemanticsFrame::~nsMathMLsemanticsFrame()
+{
+}
+
+nsIFrame*
+nsMathMLsemanticsFrame::GetSelectedFrame()
+{
+ // By default, we will display the first child of the <semantics> element.
+ nsIFrame* childFrame = mFrames.FirstChild();
+ mSelectedFrame = childFrame;
+
+ // An empty <semantics> is invalid
+ if (!childFrame) {
+ mInvalidMarkup = true;
+ return mSelectedFrame;
+ }
+ mInvalidMarkup = false;
+
+ // Using <annotation> or <annotation-xml> as a first child is invalid.
+ // However some people use this syntax so we take care of this case too.
+ bool firstChildIsAnnotation = false;
+ nsIContent* childContent = childFrame->GetContent();
+ if (childContent->IsAnyOfMathMLElements(nsGkAtoms::annotation_,
+ nsGkAtoms::annotation_xml_)) {
+ firstChildIsAnnotation = true;
+ }
+
+ // If the first child is a presentation MathML element other than
+ // <annotation> or <annotation-xml>, we are done.
+ if (!firstChildIsAnnotation &&
+ childFrame->IsFrameOfType(nsIFrame::eMathML)) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame);
+ if (mathMLFrame) {
+ TransmitAutomaticData();
+ return mSelectedFrame;
+ }
+ // The first child is not an annotation, so skip it.
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ // Otherwise, we read the list of annotations and select the first one that
+ // could be displayed in place of the first child of <semantics>. If none is
+ // found, we fallback to this first child.
+ for ( ; childFrame; childFrame = childFrame->GetNextSibling()) {
+ nsIContent* childContent = childFrame->GetContent();
+
+ if (childContent->IsMathMLElement(nsGkAtoms::annotation_)) {
+
+ // If the <annotation> element has an src attribute we ignore it.
+ // XXXfredw Should annotation images be supported? See the related
+ // bug 297465 for mglyph.
+ if (childContent->HasAttr(kNameSpaceID_None, nsGkAtoms::src)) continue;
+
+ // Otherwise, we assume it is a text annotation that can always be
+ // displayed and stop here.
+ mSelectedFrame = childFrame;
+ break;
+ }
+
+ if (childContent->IsMathMLElement(nsGkAtoms::annotation_xml_)) {
+
+ // If the <annotation-xml> element has an src attribute we ignore it.
+ if (childContent->HasAttr(kNameSpaceID_None, nsGkAtoms::src)) continue;
+
+ // If the <annotation-xml> element has an encoding attribute
+ // describing presentation MathML, SVG or HTML we assume the content
+ // can be displayed and stop here.
+ //
+ // We recognize the following encoding values:
+ //
+ // - "MathML-Presentation", which is mentioned in the MathML3 REC
+ // - "SVG1.1" which is mentioned in the W3C note
+ // http://www.w3.org/Math/Documents/Notes/graphics.xml
+ // - Other mime Content-Types for SVG and HTML
+ //
+ // We exclude APPLICATION_MATHML_XML = "application/mathml+xml" which
+ // is ambiguous about whether it is Presentation or Content MathML.
+ // Authors must use a more explicit encoding value.
+ nsAutoString value;
+ childContent->GetAttr(kNameSpaceID_None, nsGkAtoms::encoding, value);
+ if (value.EqualsLiteral("application/mathml-presentation+xml") ||
+ value.EqualsLiteral("MathML-Presentation") ||
+ value.EqualsLiteral(IMAGE_SVG_XML) ||
+ value.EqualsLiteral("SVG1.1") ||
+ value.EqualsLiteral(APPLICATION_XHTML_XML) ||
+ value.EqualsLiteral(TEXT_HTML)) {
+ mSelectedFrame = childFrame;
+ break;
+ }
+ }
+ }
+
+ TransmitAutomaticData();
+ return mSelectedFrame;
+}
diff --git a/layout/mathml/nsMathMLsemanticsFrame.h b/layout/mathml/nsMathMLsemanticsFrame.h
new file mode 100644
index 000000000..aedb8af8b
--- /dev/null
+++ b/layout/mathml/nsMathMLsemanticsFrame.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMathMLsemanticsFrame_h___
+#define nsMathMLsemanticsFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsMathMLSelectedFrame.h"
+
+//
+// <semantics> -- associate annotations with a MathML expression
+//
+
+class nsMathMLsemanticsFrame : public nsMathMLSelectedFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewMathMLsemanticsFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+protected:
+ explicit nsMathMLsemanticsFrame(nsStyleContext* aContext) :
+ nsMathMLSelectedFrame(aContext) {}
+ virtual ~nsMathMLsemanticsFrame();
+
+ nsIFrame* GetSelectedFrame() override;
+};
+
+#endif /* nsMathMLsemanticsFrame_h___ */
diff --git a/layout/mathml/operatorDictionary.xsl b/layout/mathml/operatorDictionary.xsl
new file mode 100644
index 000000000..702b230ee
--- /dev/null
+++ b/layout/mathml/operatorDictionary.xsl
@@ -0,0 +1,76 @@
+<!-- -*- Mode: nXML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:strip-space elements="*"/>
+
+ <xsl:template match="charlist">
+ <root><xsl:apply-templates select="character"/></root>
+ </xsl:template>
+
+ <xsl:template match="character">
+ <xsl:if test="operator-dictionary">
+ <xsl:for-each select="operator-dictionary">
+ <entry>
+
+ <xsl:attribute name="unicode">
+ <xsl:value-of select="../@id"/>
+ </xsl:attribute>
+
+ <xsl:attribute name="form">
+ <xsl:value-of select="@form"/>
+ </xsl:attribute>
+
+ <!-- begin operator-dictionary -->
+ <xsl:if test="@lspace">
+ <xsl:attribute name="lspace">
+ <xsl:value-of select="@lspace"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@rspace">
+ <xsl:attribute name="rspace">
+ <xsl:value-of select="@rspace"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@minsize">
+ <xsl:attribute name="minsize">
+ <xsl:value-of select="@minsize"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@*[.='true']">
+ <xsl:attribute name="properties">
+ <!-- largeop, movablelimits, stretchy, separator, accent, fence,
+ symmetric -->
+ <xsl:for-each select="@*[.='true']">
+ <xsl:value-of select="name()"/>
+ <xsl:text> </xsl:text>
+ </xsl:for-each>
+ <xsl:if test="../unicodedata/@mirror = 'Y'">
+ <xsl:text>mirrorable </xsl:text>
+ </xsl:if>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@priority">
+ <xsl:attribute name="priority">
+ <xsl:value-of select="@priority"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@linebreakstyle">
+ <xsl:attribute name="linebreakstyle">
+ <xsl:value-of select="@linebreakstyle"/>
+ </xsl:attribute>
+ </xsl:if>
+ <!-- end operator-dictionary -->
+
+ <xsl:attribute name="description">
+ <xsl:value-of select="../description"/>
+ </xsl:attribute>
+
+ </entry>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/layout/mathml/tests/chrome.ini b/layout/mathml/tests/chrome.ini
new file mode 100644
index 000000000..0885f4c6b
--- /dev/null
+++ b/layout/mathml/tests/chrome.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+
+support-files =
+ mathml_example_test.html
+
+[test_disabled_chrome.html]
diff --git a/layout/mathml/tests/mathml_example_test.html b/layout/mathml/tests/mathml_example_test.html
new file mode 100644
index 000000000..6eee75d01
--- /dev/null
+++ b/layout/mathml/tests/mathml_example_test.html
@@ -0,0 +1,28 @@
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mstyle scriptsizemultiplier="2">
+ <msub>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msub>
+ <msup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msup>
+ <msubsup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msubsup>
+ <mmultiscripts>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mprescripts/>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mmultiscripts>
+ </mstyle>
+</math>
+
+<svg id="svgel">
+</svg>
diff --git a/layout/mathml/tests/mochitest.ini b/layout/mathml/tests/mochitest.ini
new file mode 100644
index 000000000..6a6651509
--- /dev/null
+++ b/layout/mathml/tests/mochitest.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+
+[test_bug330964.html]
+[test_bug553917.html]
+[test_bug706406.html]
+[test_bug827713-2.html]
+[test_bug827713.html]
+[test_bug975681.html]
+[test_disabled.html]
+[test_opentype-axis-height.html]
+[test_opentype-fraction.html]
+[test_opentype-limits.html]
+skip-if = os == "win" # Fails on WinXP
+[test_opentype-radical.html]
+skip-if = os == "win" # Fails on WinXP
+[test_opentype-scripts.html]
+[test_opentype-stack.html]
diff --git a/layout/mathml/tests/stretchy-and-large-operators.html b/layout/mathml/tests/stretchy-and-large-operators.html
new file mode 100644
index 000000000..13fe14aa7
--- /dev/null
+++ b/layout/mathml/tests/stretchy-and-large-operators.html
@@ -0,0 +1,88 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test Stretchy and Large Operators</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+ <script type="text/javascript"
+ src="stretchy-and-large-operators.js"></script>
+ <script type="text/javascript">
+
+ var mathml = "http://www.w3.org/1998/Math/MathML";
+
+ function createMo(aOperator, aForm)
+ {
+ var mo = document.createElementNS(mathml, "mo");
+ mo.appendChild(document.createTextNode(aOperator));
+ mo.setAttribute("form", aForm);
+ return mo;
+ }
+
+ function createTest(aEntry)
+ {
+ var opname = aEntry[0];
+ var operator = aEntry[1];
+ var type = aEntry[2];
+ var form = aEntry[3];
+
+ var div = document.createElement("div");
+ div.appendChild(document.createTextNode(opname));
+
+ var math = document.createElementNS(mathml, "math");
+
+ switch (type)
+ {
+ case "l": // largeop
+ math.appendChild(createMo(operator, form));
+ var mstyle = document.createElementNS(mathml, "mstyle");
+ mstyle.setAttribute("displaystyle", "true");
+ mstyle.appendChild(createMo(operator, form));
+ math.appendChild(mstyle);
+ break;
+
+ case "v": // vertical
+ for (var i = 1; i < 10; i+=2) {
+ var mo = createMo(operator, form);
+ mo.setAttribute("minsize", (.5 * i) + "em");
+ math.appendChild(mo);
+ }
+ break;
+
+ case "h": // horizontal
+ for (var i = 1; i < 10; i+=2) {
+ var mo = createMo(operator, form);
+ var mspace = document.createElementNS(mathml, "mspace");
+ mspace.setAttribute("width", (.5 * i) + "em");
+ var mover = document.createElementNS(mathml, "mover");
+ mover.setAttribute("accent", "false");
+ mover.appendChild(mspace);
+ mover.appendChild(mo);
+ math.appendChild(mover);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ div.appendChild(math);
+ document.body.appendChild(div);
+ }
+
+ function init()
+ {
+ for (var i in stretchy_and_large_operators) {
+ createTest(stretchy_and_large_operators[i]);
+ }
+ }
+ </script>
+ </head>
+
+ <body onload="init()">
+
+ </body>
+</html>
diff --git a/layout/mathml/tests/stretchy-and-large-operators.js b/layout/mathml/tests/stretchy-and-large-operators.js
new file mode 100644
index 000000000..775e02840
--- /dev/null
+++ b/layout/mathml/tests/stretchy-and-large-operators.js
@@ -0,0 +1,2 @@
+// This file is automatically generated. Do not edit.
+var stretchy_and_large_operators = [['\\u2194.infix: ', '\u2194','h','infix'],['\\u222E.prefix: ', '\u222E','l','prefix'],['\\u2A11.prefix: ', '\u2A11','l','prefix'],['\\u007E.infix: ', '\u007E','h','infix'],['\\u27F8.infix: ', '\u27F8','h','infix'],['\\u02CD.postfix: ', '\u02CD','h','postfix'],['\\u0028.prefix: ', '\u0028','v','prefix'],['\\u220F.prefix: ', '\u220F','l','prefix'],['\\u2225.postfix: ', '\u2225','v','postfix'],['\\u2A13.prefix: ', '\u2A13','l','prefix'],['\\u007C\u007C.infix: ', '\u007C\u007C','v','infix'],['\\u203E.postfix: ', '\u203E','h','postfix'],['\\u27EE.prefix: ', '\u27EE','v','prefix'],['\\u007C.prefix: ', '\u007C','v','prefix'],['\\u2A00.prefix: ', '\u2A00','l','prefix'],['\\u295A.infix: ', '\u295A','h','infix'],['\\u02C6.postfix: ', '\u02C6','h','postfix'],['\\u21E2.infix: ', '\u21E2','h','infix'],['\\u2961.infix: ', '\u2961','v','infix'],['\\u2998.postfix: ', '\u2998','v','postfix'],['\\u2197.infix: ', '\u2197','v','infix'],['\\u21BE.infix: ', '\u21BE','v','infix'],['\\u21A7.infix: ', '\u21A7','v','infix'],['\\u21C7.infix: ', '\u21C7','h','infix'],['\\u2A0D.prefix: ', '\u2A0D','l','prefix'],['\\u23E1.postfix: ', '\u23E1','h','postfix'],['\\u2A18.prefix: ', '\u2A18','l','prefix'],['\\u21A9.infix: ', '\u21A9','h','infix'],['\\u21F0.infix: ', '\u21F0','h','infix'],['\\u2016.prefix: ', '\u2016','v','prefix'],['\\u007D.postfix: ', '\u007D','v','postfix'],['\\u294E.infix: ', '\u294E','h','infix'],['\\u2A02.prefix: ', '\u2A02','l','prefix'],['\\u21D5.infix: ', '\u21D5','v','infix'],['\\u219E.infix: ', '\u219E','h','infix'],['\\u21EE.infix: ', '\u21EE','v','infix'],['\\u22C3.prefix: ', '\u22C3','l','prefix'],['\\u21C9.infix: ', '\u21C9','h','infix'],['\\u21A6.infix: ', '\u21A6','h','infix'],['\\u2044.infix: ', '\u2044','v','infix'],['\\u27F9.infix: ', '\u27F9','h','infix'],['\\u21A1.infix: ', '\u21A1','v','infix'],['\\u2953.infix: ', '\u2953','h','infix'],['\\u2195.infix: ', '\u2195','v','infix'],['\\u21E0.infix: ', '\u21E0','h','infix'],['\\u21E4.infix: ', '\u21E4','h','infix'],['\\u2987.prefix: ', '\u2987','v','prefix'],['\\u2A15.prefix: ', '\u2A15','l','prefix'],['\\u21FE.infix: ', '\u21FE','h','infix'],['\\u2225.prefix: ', '\u2225','v','prefix'],['\\u2233.prefix: ', '\u2233','l','prefix'],['\\u2772.prefix: ', '\u2772','v','prefix'],['\\u27FA.infix: ', '\u27FA','h','infix'],['\\u2230.prefix: ', '\u2230','l','prefix'],['\\u2960.infix: ', '\u2960','v','infix'],['\\u21AF.infix: ', '\u21AF','v','infix'],['\\u20E1.postfix: ', '\u20E1','h','postfix'],['\\u007C\u007C\u007C.postfix: ', '\u007C\u007C\u007C','v','postfix'],['\\u27FB.infix: ', '\u27FB','h','infix'],['\\u295C.infix: ', '\u295C','v','infix'],['\\u007C.postfix: ', '\u007C','v','postfix'],['\\u21D1.infix: ', '\u21D1','v','infix'],['\\u27F7.infix: ', '\u27F7','h','infix'],['\\u21B1.infix: ', '\u21B1','v','infix'],['\\u21EC.infix: ', '\u21EC','v','infix'],['\\u21CC.infix: ', '\u21CC','h','infix'],['\\u27E8.prefix: ', '\u27E8','v','prefix'],['\\u2990.postfix: ', '\u2990','v','postfix'],['\\u21B4.infix: ', '\u21B4','h','infix'],['\\u22C0.prefix: ', '\u22C0','l','prefix'],['\\u2951.infix: ', '\u2951','v','infix'],['\\u2AFF.prefix: ', '\u2AFF','l','prefix'],['\\u21B5.infix: ', '\u21B5','v','infix'],['\\u2215.infix: ', '\u2215','v','infix'],['\\u2988.postfix: ', '\u2988','v','postfix'],['\\u290D.infix: ', '\u290D','h','infix'],['\\u21C1.infix: ', '\u21C1','h','infix'],['\\u290B.infix: ', '\u290B','v','infix'],['\\u2A16.prefix: ', '\u2A16','l','prefix'],['\\u295E.infix: ', '\u295E','h','infix'],['\\u2950.infix: ', '\u2950','h','infix'],['\\u27EB.postfix: ', '\u27EB','v','postfix'],['\\u29FC.prefix: ', '\u29FC','v','prefix'],['\\u21A4.infix: ', '\u21A4','h','infix'],['\\u295D.infix: ', '\u295D','v','infix'],['\\u2A10.prefix: ', '\u2A10','l','prefix'],['\\u2199.infix: ', '\u2199','h','infix'],['\\u222C.prefix: ', '\u222C','l','prefix'],['\\u2959.infix: ', '\u2959','v','infix'],['\\u21EB.infix: ', '\u21EB','v','infix'],['\\u290C.infix: ', '\u290C','h','infix'],['\\u21E9.infix: ', '\u21E9','v','infix'],['\\u2A1A.prefix: ', '\u2A1A','l','prefix'],['\\u2913.infix: ', '\u2913','v','infix'],['\\u2910.infix: ', '\u2910','h','infix'],['\\uFE36.postfix: ', '\uFE36','h','postfix'],['\\u21B0.infix: ', '\u21B0','v','infix'],['\\u296E.infix: ', '\u296E','v','infix'],['\\u2500.infix: ', '\u2500','h','infix'],['\\u2308.prefix: ', '\u2308','v','prefix'],['\\u21A8.infix: ', '\u21A8','v','infix'],['\\u21C6.infix: ', '\u21C6','h','infix'],['\\u21DD.infix: ', '\u21DD','h','infix'],['\\u221A.prefix: ', '\u221A','v','prefix'],['\\u2994.postfix: ', '\u2994','v','postfix'],['\\u2296.prefix: ', '\u2296','l','prefix'],['\\u2985.prefix: ', '\u2985','v','prefix'],['\\u21F6.infix: ', '\u21F6','h','infix'],['\\u298B.prefix: ', '\u298B','v','prefix'],['\\u02C7.postfix: ', '\u02C7','h','postfix'],['\\u2223.prefix: ', '\u2223','v','prefix'],['\\u007C.infix: ', '\u007C','v','infix'],['\\u2A07.prefix: ', '\u2A07','l','prefix'],['\\u2A06.prefix: ', '\u2A06','l','prefix'],['\\u20D6.postfix: ', '\u20D6','h','postfix'],['\\u2A12.prefix: ', '\u2A12','l','prefix'],['\\u2223.postfix: ', '\u2223','v','postfix'],['\\u2958.infix: ', '\u2958','v','infix'],['\\u23DC.postfix: ', '\u23DC','h','postfix'],['\\u2986.postfix: ', '\u2986','v','postfix'],['\\u2B46.infix: ', '\u2B46','h','infix'],['\\u21FD.infix: ', '\u21FD','h','infix'],['\\u007C\u007C\u007C.prefix: ', '\u007C\u007C\u007C','v','prefix'],['\\u21D2.infix: ', '\u21D2','h','infix'],['\\u298C.postfix: ', '\u298C','v','postfix'],['\\u2912.infix: ', '\u2912','v','infix'],['\\u21EA.infix: ', '\u21EA','v','infix'],['\\u21BC.infix: ', '\u21BC','h','infix'],['\\u005B.prefix: ', '\u005B','v','prefix'],['\\u22C2.prefix: ', '\u22C2','l','prefix'],['\\u296F.infix: ', '\u296F','v','infix'],['\\u2B45.infix: ', '\u2B45','h','infix'],['\\u02F7.postfix: ', '\u02F7','h','postfix'],['\\u0332.postfix: ', '\u0332','h','postfix'],['\\u27FE.infix: ', '\u27FE','h','infix'],['\\u0029.postfix: ', '\u0029','v','postfix'],['\\u21C0.infix: ', '\u21C0','h','infix'],['\\u2A0F.prefix: ', '\u2A0F','l','prefix'],['\\u27E7.postfix: ', '\u27E7','v','postfix'],['\\u27ED.postfix: ', '\u27ED','v','postfix'],['\\u27E6.prefix: ', '\u27E6','v','prefix'],['\\u2995.prefix: ', '\u2995','v','prefix'],['\\u219F.infix: ', '\u219F','v','infix'],['\\u21AC.infix: ', '\u21AC','h','infix'],['\\u21A2.infix: ', '\u21A2','h','infix'],['\\u007B.prefix: ', '\u007B','v','prefix'],['\\u222D.prefix: ', '\u222D','l','prefix'],['\\u2A04.prefix: ', '\u2A04','l','prefix'],['\\u2A0C.prefix: ', '\u2A0C','l','prefix'],['\\u27FF.infix: ', '\u27FF','h','infix'],['\\uFE37.postfix: ', '\uFE37','h','postfix'],['\\u007C\u007C.prefix: ', '\u007C\u007C','v','prefix'],['\\u2A14.prefix: ', '\u2A14','l','prefix'],['\\u23DD.postfix: ', '\u23DD','h','postfix'],['\\u29FD.postfix: ', '\u29FD','v','postfix'],['\\u20D7.postfix: ', '\u20D7','h','postfix'],['\\u222F.prefix: ', '\u222F','l','prefix'],['\\u230A.prefix: ', '\u230A','v','prefix'],['\\u27FD.infix: ', '\u27FD','h','infix'],['\\u2AFC.prefix: ', '\u2AFC','l','prefix'],['\\u21E6.infix: ', '\u21E6','h','infix'],['\\u2210.prefix: ', '\u2210','l','prefix'],['\\u298E.postfix: ', '\u298E','v','postfix'],['\\u21E8.infix: ', '\u21E8','h','infix'],['\\u2A0B.prefix: ', '\u2A0B','l','prefix'],['\\u21D0.infix: ', '\u21D0','h','infix'],['\\u27EC.prefix: ', '\u27EC','v','prefix'],['\\u2996.postfix: ', '\u2996','v','postfix'],['\\u2A1C.prefix: ', '\u2A1C','l','prefix'],['\\uFE38.postfix: ', '\uFE38','h','postfix'],['\\u2A0E.prefix: ', '\u2A0E','l','prefix'],['\\u23DF.postfix: ', '\u23DF','h','postfix'],['\\u290F.infix: ', '\u290F','h','infix'],['\\u2954.infix: ', '\u2954','v','infix'],['\\u21DA.infix: ', '\u21DA','h','infix'],['\\u298A.postfix: ', '\u298A','v','postfix'],['\\u2232.prefix: ', '\u2232','l','prefix'],['\\u298D.prefix: ', '\u298D','v','prefix'],['\\u21F3.infix: ', '\u21F3','v','infix'],['\\u2980.postfix: ', '\u2980','v','postfix'],['\\u21B3.infix: ', '\u21B3','v','infix'],['\\u02DC.postfix: ', '\u02DC','h','postfix'],['\\u230B.postfix: ', '\u230B','v','postfix'],['\\u2A19.prefix: ', '\u2A19','l','prefix'],['\\u007C\u007C.postfix: ', '\u007C\u007C','v','postfix'],['\\u21CA.infix: ', '\u21CA','v','infix'],['\\u2231.prefix: ', '\u2231','l','prefix'],['\\u2A01.prefix: ', '\u2A01','l','prefix'],['\\u21E3.infix: ', '\u21E3','v','infix'],['\\u2192.infix: ', '\u2192','h','infix'],['\\u23E0.postfix: ', '\u23E0','h','postfix'],['\\u21A3.infix: ', '\u21A3','h','infix'],['\\u21D4.infix: ', '\u21D4','h','infix'],['\\u2957.infix: ', '\u2957','h','infix'],['\\u298F.prefix: ', '\u298F','v','prefix'],['\\u290A.infix: ', '\u290A','v','infix'],['\\u2989.prefix: ', '\u2989','v','prefix'],['\\u2297.prefix: ', '\u2297','l','prefix'],['\\u27EF.postfix: ', '\u27EF','v','postfix'],['\\u2991.prefix: ', '\u2991','v','prefix'],['\\u21B9.infix: ', '\u21B9','h','infix'],['\\u2295.prefix: ', '\u2295','l','prefix'],['\\u007E.postfix: ', '\u007E','h','postfix'],['\\u02C9.postfix: ', '\u02C9','h','postfix'],['\\u21C4.infix: ', '\u21C4','h','infix'],['\\u21BD.infix: ', '\u21BD','h','infix'],['\\u21D3.infix: ', '\u21D3','v','infix'],['\\u005F.infix: ', '\u005F','h','infix'],['\\u2A08.prefix: ', '\u2A08','l','prefix'],['\\u20D1.postfix: ', '\u20D1','h','postfix'],['\\u2196.infix: ', '\u2196','v','infix'],['\\u27E9.postfix: ', '\u27E9','v','postfix'],['\\u27F5.infix: ', '\u27F5','h','infix'],['\\u2980.prefix: ', '\u2980','v','prefix'],['\\u21B2.infix: ', '\u21B2','v','infix'],['\\u294F.infix: ', '\u294F','v','infix'],['\\u21C5.infix: ', '\u21C5','v','infix'],['\\u21DC.infix: ', '\u21DC','h','infix'],['\\u21E7.infix: ', '\u21E7','v','infix'],['\\u21ED.infix: ', '\u21ED','v','infix'],['\\u21C2.infix: ', '\u21C2','v','infix'],['\\u21E5.infix: ', '\u21E5','h','infix'],['\\u23B1.postfix: ', '\u23B1','v','postfix'],['\\u21C8.infix: ', '\u21C8','v','infix'],['\\u27FC.infix: ', '\u27FC','h','infix'],['\\u290E.infix: ', '\u290E','h','infix'],['\\u2955.infix: ', '\u2955','v','infix'],['\\u295B.infix: ', '\u295B','h','infix'],['\\u23DE.postfix: ', '\u23DE','h','postfix'],['\\u2997.prefix: ', '\u2997','v','prefix'],['\\u2983.prefix: ', '\u2983','v','prefix'],['\\u2309.postfix: ', '\u2309','v','postfix'],['\\u2A17.prefix: ', '\u2A17','l','prefix'],['\\u23B4.postfix: ', '\u23B4','h','postfix'],['\\u22C1.prefix: ', '\u22C1','l','prefix'],['\\u2190.infix: ', '\u2190','h','infix'],['\\u21F5.infix: ', '\u21F5','v','infix'],['\\u21BF.infix: ', '\u21BF','v','infix'],['\\u27F0.infix: ', '\u27F0','v','infix'],['\\uFE35.postfix: ', '\uFE35','h','postfix'],['\\u21A0.infix: ', '\u21A0','h','infix'],['\\u2211.prefix: ', '\u2211','l','prefix'],['\\u219D.infix: ', '\u219D','h','infix'],['\\u23B5.postfix: ', '\u23B5','h','postfix'],['\\u27F6.infix: ', '\u27F6','h','infix'],['\\u2299.prefix: ', '\u2299','l','prefix'],['\\u219C.infix: ', '\u219C','h','infix'],['\\u00AF.postfix: ', '\u00AF','h','postfix'],['\\u21EF.infix: ', '\u21EF','v','infix'],['\\u2191.infix: ', '\u2191','v','infix'],['\\u2A09.prefix: ', '\u2A09','l','prefix'],['\\u2A0A.prefix: ', '\u2A0A','l','prefix'],['\\u005F.postfix: ', '\u005F','h','postfix'],['\\u23B0.prefix: ', '\u23B0','v','prefix'],['\\u21C3.infix: ', '\u21C3','v','infix'],['\\u21A5.infix: ', '\u21A5','v','infix'],['\\u27EA.prefix: ', '\u27EA','v','prefix'],['\\u2773.postfix: ', '\u2773','v','postfix'],['\\u2016.infix: ', '\u2016','v','infix'],['\\u21CB.infix: ', '\u21CB','h','infix'],['\\u228E.prefix: ', '\u228E','l','prefix'],['\\u27F1.infix: ', '\u27F1','v','infix'],['\\u005E.postfix: ', '\u005E','h','postfix'],['\\u0302.postfix: ', '\u0302','h','postfix'],['\\u005D.postfix: ', '\u005D','v','postfix'],['\\u2952.infix: ', '\u2952','h','infix'],['\\u295F.infix: ', '\u295F','h','infix'],['\\u21AB.infix: ', '\u21AB','h','infix'],['\\u2992.postfix: ', '\u2992','v','postfix'],['\\u2016.postfix: ', '\u2016','v','postfix'],['\\u007C\u007C\u007C.infix: ', '\u007C\u007C\u007C','v','infix'],['\\u2A03.prefix: ', '\u2A03','l','prefix'],['\\u2193.infix: ', '\u2193','v','infix'],['\\u2956.infix: ', '\u2956','h','infix'],['\\u2984.postfix: ', '\u2984','v','postfix'],['\\u21AA.infix: ', '\u21AA','h','infix'],['\\u2A1B.prefix: ', '\u2A1B','l','prefix'],['\\u21E1.infix: ', '\u21E1','v','infix'],['\\u2198.infix: ', '\u2198','h','infix'],['\\u21AD.infix: ', '\u21AD','h','infix'],['\\u2A05.prefix: ', '\u2A05','l','prefix'],['\\u21DB.infix: ', '\u21DB','h','infix'],['\\u2993.prefix: ', '\u2993','v','prefix'],['\\u222B.prefix: ', '\u222B','l','prefix'],['\\u20D0.postfix: ', '\u20D0','h','postfix'],['\\u21FF.infix: ', '\u21FF','h','infix'],];
diff --git a/layout/mathml/tests/test_bug330964.html b/layout/mathml/tests/test_bug330964.html
new file mode 100644
index 000000000..d0599a97b
--- /dev/null
+++ b/layout/mathml/tests/test_bug330964.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=330964
+-->
+<head>
+ <title>Test for Bug 706406</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=330964">Mozilla Bug 330964</a>
+<p id="display"></p>
+
+<math>
+ <mtable framespacing="7px 20px" frame="solid" rowspacing="11px 27px" columnspacing="5px 16px"
+ style="border-width: 2px;" id="mtable0">
+ <mtr>
+ <mtd id="mtd0">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd1">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd2">
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd id="mtd3">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd4">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd5">
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd id="mtd6">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd7">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd8">
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+</math>
+
+<pre id="test">
+<script type="application/javascript">
+
+ var epsilon = 2;
+ function almostEqual(x, y) { return Math.abs(x - y) < epsilon; }
+
+ rectTable = $("mtable0").getBoundingClientRect();
+ rect0 = $("mtd0").getBoundingClientRect();
+ rect1 = $("mtd1").getBoundingClientRect();
+ rect2 = $("mtd2").getBoundingClientRect();
+ rect3 = $("mtd3").getBoundingClientRect();
+ rect4 = $("mtd4").getBoundingClientRect();
+ rect5 = $("mtd5").getBoundingClientRect();
+ rect6 = $("mtd6").getBoundingClientRect();
+ rect7 = $("mtd7").getBoundingClientRect();
+ rect8 = $("mtd8").getBoundingClientRect();
+ ok(almostEqual(rect1.left - rect0.right, 5), "columnspacing wonky");
+ ok(almostEqual(rect2.left - rect1.right, 16), "columnspacing wonky");
+ ok(almostEqual(rect4.left - rect3.right, 5), "columnspacing wonky");
+ ok(almostEqual(rect5.left - rect4.right, 16), "columnspacing wonky");
+ ok(almostEqual(rect7.left - rect6.right, 5), "columnspacing wonky");
+ ok(almostEqual(rect8.left - rect7.right, 16), "columnspacing wonky");
+ ok(almostEqual(rect3.top - rect0.bottom, 11), "rowspacing wonky");
+ ok(almostEqual(rect4.top - rect1.bottom, 11), "rowspacing wonky");
+ ok(almostEqual(rect5.top - rect2.bottom, 11), "rowspacing wonky");
+ ok(almostEqual(rect6.top - rect3.bottom, 27), "rowspacing wonky");
+ ok(almostEqual(rect7.top - rect4.bottom, 27), "rowspacing wonky");
+ ok(almostEqual(rect8.top - rect5.bottom, 27), "rowspacing wonky");
+ // Remember to subtract border
+ ok(almostEqual(rect0.left - rectTable.left - 2, 7), "framespacing left wonky");
+ ok(almostEqual(rect3.left - rectTable.left - 2, 7), "framespacing left wonky");
+ ok(almostEqual(rect6.left - rectTable.left - 2, 7), "framespacing left wonky");
+ ok(almostEqual(rect0.top - rectTable.top - 2, 20), "framespacing top wonky");
+ ok(almostEqual(rect1.top - rectTable.top - 2, 20), "framespacing top wonky");
+ ok(almostEqual(rect2.top - rectTable.top - 2, 20), "framespacing top wonky");
+ ok(almostEqual(rectTable.bottom - rect6.bottom - 2, 20), "framespacing bottom wonky");
+ ok(almostEqual(rectTable.bottom - rect7.bottom - 2, 20), "framespacing bottom wonky");
+ ok(almostEqual(rectTable.bottom - rect8.bottom - 2, 20), "framespacing bottom wonky");
+ ok(almostEqual(rectTable.right - rect2.right - 2, 7), "framespacing right wonky");
+ ok(almostEqual(rectTable.right - rect5.right - 2, 7), "framespacing right wonky");
+ ok(almostEqual(rectTable.right - rect8.right - 2, 7), "framespacing right wonky");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/mathml/tests/test_bug553917.html b/layout/mathml/tests/test_bug553917.html
new file mode 100644
index 000000000..439ddbdd6
--- /dev/null
+++ b/layout/mathml/tests/test_bug553917.html
@@ -0,0 +1,171 @@
+<!DOCTYPE HTML>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=553917
+-->
+<html>
+ <head>
+ <title>Test for Bug 553917</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript">
+ var stringBundleService =
+ SpecialPowers.Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(SpecialPowers.Ci.nsIStringBundleService);
+ var g_bundl =
+ stringBundleService.createBundle("chrome://global/locale/mathml/mathml.properties");
+
+ var g_errorInfo = {
+ /*<math><mroot></mroot></math>
+ <math><msub></msub></math>
+ <math><msup></msup></math>
+ <math><mfrac></mfrac></math>
+ <math><msubsup></msubsup></math>
+ <math><munderover></munderover></math>*/
+ ChildCountIncorrect: {
+ status : [false, false, false, false, false, false],
+ args : [["mroot"], ["msub"], ["msup"], ["mfrac"], ["msubsup"], ["munderover"]] },
+ /*<math fontfamily="serif"></math>
+ <math color="#112233"></math>
+ <math background="#FFFFFF"></math>
+ <math fontsize="10"></math>
+ <math xlink:href="http://www.mozilla.org"></math>*/
+ DeprecatedSupersededBy: {
+ status: [false, false, false, false, false],
+ args: [["fontfamily","mathvariant"],["color","mathcolor"], ["background","mathbackground"],
+ ["fontsize","mathsize"], ["xlink:href","href"]] },
+ /*<math><mpadded width="BAD!"></mpadded></math>
+ <math><mpadded height="BAD!"></mpadded></math>
+ <math><mpadded voffset="BAD!"></mpadded></math>*/
+ AttributeParsingError: {
+ status: [false, false, false],
+ args: [["BAD!","width","mpadded"], ["BAD!","height","mpadded"], ["BAD!","voffset","mpadded"]]
+ },
+ /*<math scriptlevel="BAD!"></math>
+ <math scriptsizemultiplier="BAD!"></math>*/
+ AttributeParsingErrorNoTag: {
+ status: [false, false],
+ args: [["BAD!","scriptlevel"], ["BAD!","scriptsizemultiplier"]]
+ },
+ /* <math><mo rspace="2..0">+</mo></math>
+ <math><mo minsize="1.5notaunit">+</mo></math>
+ <math><mspace width="2"/></math>
+ <math><mo lspace="BADlspace">+</mo></math>
+ <math><mspace height="BADheight"/></math>
+ <math><mspace depth="BADdepth"/></math>*/
+ LengthParsingError : {
+ status: [false, false, false, false, false, false],
+ args: [["2..0"], ["1.5notaunit"], ["2"],["BADlspace"],["BADheight"],["BADdepth"]]
+ },
+ /*<math><mmultiscripts></mmultiscripts></math>
+ <math><mmultiscripts><mprescripts/><mprescripts/></mmultiscripts></math>
+ <math><mmultiscripts><mi>x</mi><mi>y</mi></mmultiscripts></math>*/
+ MMultiscriptsErrors: {
+ status: [false, false, false],
+ args: ["NoBase","DuplicateMprescripts", "SubSupMismatch"]
+ },
+ /*<math><mo minsize="2">+</mo></math>*/
+ UnitlessValuesAreDeprecated : {
+ status : [false],
+ args : [[]] }};
+
+ var g_errorTypes = ["ChildCountIncorrect","DeprecatedSupersededBy","AttributeParsingError",
+ "AttributeParsingErrorNoTag","LengthParsingError", "MMultiscriptsErrors",
+ "UnitlessValuesAreDeprecated"];
+
+ function getErrorMessage(name,idx)
+ {
+ if (name != "MMultiscriptsErrors") {
+ var formatParams = g_errorInfo[name].args[idx];
+ if (formatParams.length > 0) {
+ return g_bundl.formatStringFromName(name,formatParams,formatParams.length);
+ } else {
+ return g_bundl.GetStringFromName(name);
+ }
+ } else {
+ return g_bundl.GetStringFromName(g_errorInfo[name].args[idx]);
+ }
+ }
+
+ /** Checks the roll call to see if all expected error messages were present. */
+ function processRollCall()
+ {
+ for (var i=0; i<g_errorTypes.length;i++) {
+ for (var j = 0; j < g_errorInfo[g_errorTypes[i]].status.length; j++) {
+ ok(g_errorInfo[g_errorTypes[i]].status[j],
+ "\"" + getErrorMessage(g_errorTypes[i], j)
+ + "\" was expected to be in the error console.");
+ }
+ }
+ }
+
+ /** Tests a candidate to see if it is one of the expected messages and updates the
+ g_errorInfo structure if it is. */
+ function doRollCall(msg)
+ {
+ for (var i = 0; i < g_errorTypes.length; i++) {
+ for (var j = 0; j < g_errorInfo[g_errorTypes[i]].status.length; j++) {
+ if (msg == getErrorMessage(g_errorTypes[i], j))
+ {
+ g_errorInfo[g_errorTypes[i]].status[j] = true;
+ }
+ }
+ }
+ }
+
+ SpecialPowers.registerConsoleListener(
+ function (msg) {
+ if (msg.message == "SENTINEL") {
+ processRollCall();
+ SimpleTest.finish();
+ } else if (msg.isScriptError) {
+ doRollCall(msg.errorMessage);
+ }
+ });
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </head>
+ <body onload="SpecialPowers.postConsoleSentinel();">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=553917">Mozilla Bug 553917</a>
+ <!-- ChildCountIncorrect -->
+ <math><mroot></mroot></math>
+ <math><msub></msub></math>
+ <math><msup></msup></math>
+ <math><mfrac></mfrac></math>
+ <math><msubsup></msubsup></math>
+ <math><munderover></munderover></math>
+
+ <!-- DeprecatedSupersededBy -->
+ <math fontfamily="serif"></math>
+ <math color="#112233"></math>
+ <math background="#FFFFFF"></math>
+ <math fontsize="10"></math>
+ <math xlink:href="http://www.mozilla.org"></math>
+
+ <!-- AttributeParsingError -->
+ <math><mpadded width="BAD!"></mpadded></math>
+ <math><mpadded height="BAD!"></mpadded></math>
+ <math><mpadded voffset="BAD!"></mpadded></math>
+
+ <!-- AttributeParsingErrorNoTag -->
+ <math scriptlevel="BAD!"></math>
+ <math scriptsizemultiplier="BAD!"></math>
+
+ <!-- LengthParsingError -->
+ <math><mo rspace="2..0">+</mo></math>
+ <math><mo minsize="1.5notaunit">+</mo></math>
+ <math><mspace width="2"/></math>
+ <math><mo lspace="BADlspace">+</mo></math>
+ <math><mspace height="BADheight"/></math>
+ <math><mspace depth="BADdepth"/></math>
+
+ <!-- MMultiscriptsErrors -->
+ <math><mmultiscripts></mmultiscripts></math>
+ <math><mmultiscripts><mprescripts/><mprescripts/></mmultiscripts></math>
+ <math><mmultiscripts><mi>x</mi><mi>y</mi></mmultiscripts></math>
+
+ <!-- UnitlessValuesAreDeprecated -->
+ <math><mo minsize="2">+</mo></math>
+ </body>
+</html>
diff --git a/layout/mathml/tests/test_bug706406.html b/layout/mathml/tests/test_bug706406.html
new file mode 100644
index 000000000..76cfc6352
--- /dev/null
+++ b/layout/mathml/tests/test_bug706406.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=706406
+-->
+<head>
+ <title>Test for Bug 706406</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=706406">Mozilla Bug 706406</a>
+<p id="display"></p>
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <maction actiontype="toggle" id="maction">
+ <mn>1</mn>
+ <mn>2</mn>
+ </maction>
+</math>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 706406 **/
+
+function doTest()
+{
+ function doStopPropagation(aEvent) {
+ aEvent.stopPropagation();
+ }
+
+ var maction = document.getElementById("maction");
+
+ synthesizeMouseAtCenter(maction, {});
+
+ is(maction.getAttribute("selection"), "2",
+ "maction's selection attribute isn't 2 by first click");
+
+ synthesizeMouseAtCenter(maction, {});
+
+ is(maction.getAttribute("selection"), "1",
+ "maction's selection attribute isn't 1 by second click");
+
+ window.addEventListener("mousedown", doStopPropagation, true);
+ window.addEventListener("mouseup", doStopPropagation, true);
+ window.addEventListener("click", doStopPropagation, true);
+
+ synthesizeMouseAtCenter(maction, {});
+
+ is(maction.getAttribute("selection"), "2",
+ "maction's selection attribute isn't 2 by first click called stopPropagation()");
+
+ synthesizeMouseAtCenter(maction, {});
+
+ is(maction.getAttribute("selection"), "1",
+ "maction's selection attribute isn't 1 by second click called stopPropagation()");
+
+ window.removeEventListener("mousedown", doStopPropagation, true);
+ window.removeEventListener("mouseup", doStopPropagation, true);
+ window.removeEventListener("click", doStopPropagation, true);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(doTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/mathml/tests/test_bug827713-2.html b/layout/mathml/tests/test_bug827713-2.html
new file mode 100644
index 000000000..b7d27504d
--- /dev/null
+++ b/layout/mathml/tests/test_bug827713-2.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=553917
+-->
+<html>
+ <head>
+ <title>Test for error handling aspect of Bug 827713</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript">
+
+ var stringBundleService =
+ SpecialPowers.Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(SpecialPowers.Ci.nsIStringBundleService);
+ var g_bundl =
+ stringBundleService.createBundle("chrome://global/locale/mathml/mathml.properties");
+
+ var g_errorInfo = {
+ InvalidChild: {
+ status : [false, false, false, false, false, false],
+ args : [["mprescripts", "msubsup"], ["mprescripts", "msubsup"],
+ ["mprescripts", "msub"], ["none", "msub"], ["none","msup"],
+ ["none","msubsup"]]
+ },
+
+ MMultiscriptsErrors: {
+ status: [false, false],
+ args: ["NoBase", "SubSupMismatch"]
+ }
+ };
+
+ var g_errorTypes = ["InvalidChild", "MMultiscriptsErrors"];
+
+ function getErrorMessage(name,idx)
+ {
+ if (name != "MMultiscriptsErrors") {
+ return g_bundl.formatStringFromName(name,g_errorInfo[name].args[idx],
+ g_errorInfo[name].args[idx].length);
+ }
+ else {
+ return g_bundl.GetStringFromName(g_errorInfo[name].args[idx]);
+ }
+ }
+
+ /** Checks the roll call to see if all expected error messages were present. */
+ function processRollCall()
+ {
+ for (var i=0; i<g_errorTypes.length;i++) {
+ for (var j = 0; j < g_errorInfo[g_errorTypes[i]].status.length; j++) {
+ ok(g_errorInfo[g_errorTypes[i]].status[j],
+ "\"" + getErrorMessage(g_errorTypes[i], j)
+ + "\" was expected to be in the error console.");
+ }
+ }
+ }
+
+ /** Tests a candidate to see if it is one of the expected messages and updates the
+ g_errorInfo structure if it is. */
+ function doRollCall(msg)
+ {
+ for (var i = 0; i < g_errorTypes.length; i++) {
+ for (var j = 0; j < g_errorInfo[g_errorTypes[i]].status.length; j++) {
+ if (msg == getErrorMessage(g_errorTypes[i], j))
+ {
+ g_errorInfo[g_errorTypes[i]].status[j] = true;
+ }
+ }
+ }
+ }
+
+ SpecialPowers.registerConsoleListener(
+ function (msg) {
+ if (msg.message == "SENTINEL") {
+ processRollCall();
+ SimpleTest.finish();
+ } else if (msg.isScriptError) {
+ doRollCall(msg.errorMessage);
+ }
+ });
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </head>
+ <body onload="SpecialPowers.postConsoleSentinel();">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=827713">Mozilla Bug 827713</a>
+
+ <!-- InvalidChild -->
+ <math>
+ <msubsup>
+ <mprescripts/>
+ </msubsup>
+ </math>
+
+ <math>
+ <msubsup>
+ <mprescripts/>
+ <mprescripts/>
+ </msubsup>
+ </math>
+
+ <math>
+ <msub>
+ <mtext>a</mtext>
+ <mprescripts/>
+ <mtext>a</mtext>
+ <mprescripts/>
+ </msub>
+ </math>
+
+ <math>
+ <msub>
+ <mtext>a</mtext>
+ <none/>
+ </msub>
+ </math>
+
+ <math>
+ <msup>
+ <mtext>a</mtext>
+ <none/>
+ </msup>
+ </math>
+
+ <math>
+ <msubsup>
+ <mtext>a</mtext>
+ <mtext>b</mtext>
+ <none/>
+ </msubsup>
+ </math>
+
+ <!-- NoBase -->
+ <math>
+ <mmultiscripts>
+ <none/>
+ <mtext>b</mtext>
+ <mtext>c</mtext>
+ </mmultiscripts>
+ </math>
+
+ <!-- SubSupMismatch -->
+ <math>
+ <mmultiscripts>
+ <mtext>b</mtext>
+ <mtext>c</mtext>
+ <mprescripts/>
+ <mtext>a</mtext>
+ </mmultiscripts>
+ </math>
+ </body>
+</html>
diff --git a/layout/mathml/tests/test_bug827713.html b/layout/mathml/tests/test_bug827713.html
new file mode 100644
index 000000000..ebc46d333
--- /dev/null
+++ b/layout/mathml/tests/test_bug827713.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=706406
+-->
+<head>
+ <title>Test for Bug 706406</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=827713">Mozilla Bug 827713</a>
+<p id="display"></p>
+ <p><math>
+ <msub subscriptshift="50px">
+ <mspace width="50px" height="25px" depth="25px" mathbackground="blue" id ="subbase"></mspace>
+ <mspace width="50px" height="25px" depth="25px" mathbackground="red" id="subsub"></mspace>
+ </msub>
+ </math></p>
+
+ <p><math>
+ <msup superscriptshift="50px">
+ <mspace width="50px" height="25px" depth="25px" mathbackground="blue" id="supbase"></mspace>
+ <mspace width="50px" height="25px" depth="25px" mathbackground="green" id="supsup"></mspace>
+ </msup>
+ </math></p>
+
+ <p><math>
+ <msubsup subscriptshift="50px" superscriptshift="50px">
+ <mspace width="50px" height="25px" depth="25px" mathbackground="blue" id="ssbase"></mspace>
+ <mspace width="50px" height="25px" depth="25px" mathbackground="red" id="sssub"></mspace>
+ <mspace width="50px" height="25px" depth="25px" mathbackground="green" id="sssup"></mspace>
+ </msubsup>
+ </math></p>
+<pre id="test">
+<script type="application/javascript">
+
+ /** Test for the scriptshift aspect of bug 827713 **/
+ SimpleTest.waitForExplicitFinish();
+
+ subBaseRect = $("subbase").getBoundingClientRect();
+ subSubRect = $("subsub").getBoundingClientRect();
+ is(subBaseRect.bottom, subSubRect.top, "Bad subscript shift for msub");
+
+ supBaseRect = $("supbase").getBoundingClientRect();
+ supSupRect = $("supsup").getBoundingClientRect();
+ is(supBaseRect.top, supSupRect.bottom, "Bad superscript shift for msup");
+
+ ssBaseRect = $("ssbase").getBoundingClientRect();
+ ssSubRect = $("sssub").getBoundingClientRect();
+ ssSupRect = $("sssup").getBoundingClientRect();
+ is(ssBaseRect.bottom, ssSubRect.top, "Bad subscript shift for msubusp");
+ is(ssBaseRect.top, ssSupRect.bottom, "Bad superscript shift for msubusp");
+
+
+ SimpleTest.finish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/mathml/tests/test_bug975681.html b/layout/mathml/tests/test_bug975681.html
new file mode 100644
index 000000000..854693ead
--- /dev/null
+++ b/layout/mathml/tests/test_bug975681.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=975681
+-->
+ <head>
+ <meta charset="utf-8">
+ <title> Test for Bug 975681 </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"> </script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"> </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=975681"> Mozilla Bug 975681 </a>
+ <p id="display"></p>
+
+ <p>
+ <math>
+ <mfrac id="test_mfrac">
+ <mspace width="100px" height="20px" mathbackground="red"></mspace>
+ <mspace width="100px" height="20px" mathbackground="red"></mspace>
+ </mfrac>
+ </math>
+ </p>
+
+ <p>
+ <math>
+ <mfrac linethickness="30px" id="mfrac_linethickness">
+ <mspace width="100px" height="20px" mathbackground="red"></mspace>
+ <mspace width="100px" height="20px" mathbackground="red"></mspace>
+ </mfrac>
+ </math>
+ </p>
+
+ <p>
+ <math>
+ <mfrac numalign="left" id="mfrac_numalign">
+ <mspace width="50px" height="20px" mathbackground="red"></mspace>
+ <mspace width="100px" height="20px" mathbackground="red"></mspace>
+ </mfrac>
+ </math>
+ </p>
+
+ <p>
+ <math>
+ <mfrac denomalign="right" id="mfrac_denomalign">
+ <mspace width="100px" height="20px" mathbackground="red"></mspace>
+ <mspace width="50px" height="20px" mathbackground="red"></mspace>
+ </mfrac>
+ </math>
+ </p>
+
+ <p>
+ <math>
+ <mfrac bevelled="true" id="mfrac_bevelled">
+ <mspace width="100px" height="20px" mathbackground="red"></mspace>
+ <mspace width="100px" height="20px" mathbackground="red"></mspace>
+ </mfrac>
+ </math>
+ </p>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 975681 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var epsilon = 1; // allow a small relative error
+ var delta = .25; // used to indicate a small shift
+
+ function almostEqualAbs(x, y) {
+ var e = Math.abs(x - y);
+ return (e <= epsilon);
+ }
+
+ function almostLessThanAbs(x, y) {
+ var e = x - y;
+ return (e <= epsilon);
+ }
+
+ // test: mfrac
+ var mfracNum = document.getElementById("test_mfrac").firstElementChild.getBoundingClientRect();
+ var mfracDenom = document.getElementById("test_mfrac").lastElementChild.getBoundingClientRect();
+
+ ok(almostEqualAbs(mfracNum.left, mfracDenom.left) &&
+ almostEqualAbs(mfracNum.right, mfracDenom.right), "Numerator and denominator should be vertical aligned");
+
+ ok(almostLessThanAbs(mfracNum.bottom, mfracDenom.top), "Numerator should be above denominator");
+
+ // test: mfrac attributes
+ var mfrac = document.getElementById("mfrac_linethickness").getBoundingClientRect();
+ var num = document.getElementById("mfrac_linethickness").firstElementChild.getBoundingClientRect();
+ var denom = document.getElementById("mfrac_linethickness").lastElementChild.getBoundingClientRect();
+
+ ok(almostLessThanAbs(num.height + 30 + denom.height, mfrac.height) &&
+ almostLessThanAbs(num.bottom + 30, denom.top), "numerator and denominator should be separated by linethickness");
+
+ num = document.getElementById("mfrac_numalign").firstElementChild.getBoundingClientRect();
+ mfrac = document.getElementById("mfrac_numalign").getBoundingClientRect();
+
+ ok(almostEqualAbs(num.left, mfrac.left), "numerator should be aligned left");
+
+ mfrac = document.getElementById("mfrac_denomalign").getBoundingClientRect();
+ denom = document.getElementById("mfrac_denomalign").lastElementChild.getBoundingClientRect();
+
+ ok(almostEqualAbs(mfrac.right, denom.right), "denominator should be aligned right");
+
+ num = document.getElementById("mfrac_bevelled").firstElementChild.getBoundingClientRect();
+ denom = document.getElementById("mfrac_bevelled").lastElementChild.getBoundingClientRect();
+
+ ok(almostLessThanAbs(num.right, denom.left) &&
+ almostLessThanAbs(num.top*(1-delta)+num.bottom*delta, denom.top), "incorrect position of mfrac children");
+
+ SimpleTest.finish();
+
+ </script>
+ </body>
+</html>
diff --git a/layout/mathml/tests/test_disabled.html b/layout/mathml/tests/test_disabled.html
new file mode 100644
index 000000000..9b649d0f9
--- /dev/null
+++ b/layout/mathml/tests/test_disabled.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Copied from
+https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<pre id="test">
+<script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["mathml.disabled", true]]}, doTest);
+ function doTest() {
+ let t = document.getElementById('testnodes');
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math:math"));
+ t.firstChild.textContent = "<foo>";
+ is(t.innerHTML, "<math:math>&lt;foo&gt;</math:math>");
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "script"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<math><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></math>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "style"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<math><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></math>');
+
+ SimpleTest.finish();
+ }
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/mathml/tests/test_disabled_chrome.html b/layout/mathml/tests/test_disabled_chrome.html
new file mode 100644
index 000000000..dff4011bb
--- /dev/null
+++ b/layout/mathml/tests/test_disabled_chrome.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<!--
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+-->
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<pre id="test">
+<script type="application/javascript">
+ add_task(function* () {
+ const initialPrefValue = SpecialPowers.getBoolPref("mathml.disabled");
+ SpecialPowers.setBoolPref("mathml.disabled", true);
+ const Cu = SpecialPowers.Components.utils;
+ const { ContentTaskUtils } = Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
+ let t = document.getElementById('testnodes');
+
+ let url = 'chrome://mochitests/content/chrome/layout/mathml/tests/mathml_example_test.html'
+ const chromeIframeEl = document.createElement('iframe');
+ let chromeLoadPromise = ContentTaskUtils.waitForEvent(chromeIframeEl, 'load', false);
+ chromeIframeEl.src = url;
+ t.appendChild(chromeIframeEl);
+
+ yield chromeLoadPromise;
+ const chromeBR = chromeIframeEl.contentDocument.body.getBoundingClientRect();
+
+ url = "http://mochi.test:8888/chrome/layout/mathml/tests/mathml_example_test.html";
+ const iframeEl = document.createElement('iframe');
+ iframeEl.src = url;
+ let loadPromise = ContentTaskUtils.waitForEvent(iframeEl, 'load', false);
+ t.appendChild(iframeEl);
+ yield loadPromise;
+
+ const contentBR = iframeEl.contentDocument.body.getBoundingClientRect();
+
+ ok(chromeBR.height > contentBR.height, "Chrome content height should be bigger than content due to layout");
+
+ ok(!iframeEl.contentDocument.getElementById('svgel').hasExtension("http://www.w3.org/1998/Math/MathML"), 'SVG namespace support is disabled in content iframe');
+ ok(chromeIframeEl.contentDocument.getElementById('svgel').hasExtension("http://www.w3.org/1998/Math/MathML"), 'SVG namespace support is enabled in chrome iframe');
+
+ SpecialPowers.setBoolPref("mathml.disabled", initialPrefValue);
+ });
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/mathml/tests/test_opentype-axis-height.html b/layout/mathml/tests/test_opentype-axis-height.html
new file mode 100644
index 000000000..cfab51d40
--- /dev/null
+++ b/layout/mathml/tests/test_opentype-axis-height.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - axis-height</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ math {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: axis-height-1;
+ src: url(/tests/fonts/math/axis-height-1.otf);
+ }
+ @font-face {
+ font-family: axis-height-2;
+ src: url(/tests/fonts/math/axis-height-2.otf);
+ }
+ </style>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var epsilon = 5;
+ function almostEqual(x, y) { return Math.abs(x - y) < epsilon; }
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ function doTest() {
+ ok(almostEqual(getBox("plus1").top - getBox("plus2").top, 10 * 20),
+ "Bad AxisHeight");
+
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="doTest()">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=961365">
+ Mozilla Bug 961365
+ </a>
+
+ <p id="display"></p>
+
+ <p>
+ <math style="font-family: axis-height-1;">
+ <mo id="plus1">+</mo>
+ </math>
+ <math style="font-family: axis-height-2;">
+ <mo id="plus2">+</mo>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/layout/mathml/tests/test_opentype-fraction.html b/layout/mathml/tests/test_opentype-fraction.html
new file mode 100644
index 000000000..ad25b2e4d
--- /dev/null
+++ b/layout/mathml/tests/test_opentype-fraction.html
@@ -0,0 +1,187 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - fraction</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ math {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: fraction-1;
+ src: url(/tests/fonts/math/fraction-1.otf);
+ }
+ @font-face {
+ font-family: fraction-2;
+ src: url(/tests/fonts/math/fraction-2.otf);
+ }
+ @font-face {
+ font-family: fraction-3;
+ src: url(/tests/fonts/math/fraction-3.otf);
+ }
+ @font-face {
+ font-family: fraction-4;
+ src: url(/tests/fonts/math/fraction-4.otf);
+ }
+ @font-face {
+ font-family: fraction-5;
+ src: url(/tests/fonts/math/fraction-5.otf);
+ }
+ @font-face {
+ font-family: fraction-6;
+ src: url(/tests/fonts/math/fraction-6.otf);
+ }
+ @font-face {
+ font-family: fraction-7;
+ src: url(/tests/fonts/math/fraction-7.otf);
+ }
+ @font-face {
+ font-family: fraction-8;
+ src: url(/tests/fonts/math/fraction-8.otf);
+ }
+ @font-face {
+ font-family: fraction-9;
+ src: url(/tests/fonts/math/fraction-9.otf);
+ }
+ </style>
+ <script type="text/javascript">
+ var epsilon = 5;
+ function almostEqual(x, y) { return Math.abs(x - y) < epsilon; }
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ function doTest() {
+ /* inline style */
+ var ref = getBox("ref").height;
+
+ ok(almostEqual(getBox("d1").top - getBox("n1").bottom, ref * 20),
+ "Bad FractionRuleThickness");
+
+ ok(almostEqual(getBox("n1").top, getBox("n2").top) &&
+ almostEqual(getBox("d2").top - getBox("n2").bottom, ref * 10.5),
+ "Bad FractionNumeratorGapMin");
+
+ ok(almostEqual(getBox("d1").bottom, getBox("d3").bottom) &&
+ almostEqual(getBox("d3").top - getBox("n3").bottom, ref * 10.5),
+ "Bad FractionDenominatorGapMin");
+
+ ok(almostEqual(getBox("ref").top - getBox("n4").top, ref*3),
+ "Bad FractionNumeratorShiftUp");
+
+ ok(almostEqual(getBox("d5").bottom - getBox("ref").bottom, ref*3),
+ "Bad FractionDenominatorShiftDown");
+
+ /* display style */
+ ref = getBox("dref").height;
+
+ ok(almostEqual(getBox("dd1").top - getBox("dn1").bottom, ref * 20),
+ "Bad FractionRuleThickness");
+
+ ok(almostEqual(getBox("dn1").top, getBox("n6").top) &&
+ almostEqual(getBox("d6").top - getBox("n6").bottom, ref * 10.5),
+ "Bad FractionNumeratorDisplayStyleGapMin");
+
+ ok(almostEqual(getBox("dd1").bottom, getBox("d7").bottom) &&
+ almostEqual(getBox("d7").top - getBox("n7").bottom, ref * 10.5),
+ "Bad FractionDenominatorDisplayStyleGapMin");
+
+ ok(almostEqual(getBox("dref").top - getBox("n8").top, ref*3),
+ "Bad FractionNumeratorDisplayStyleShiftUp");
+
+ ok(almostEqual(getBox("d9").bottom - getBox("dref").bottom, ref*3),
+ "Bad FractionDenominatorDisplayStyleShiftDown");
+
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="doTest()">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=961365">
+ Mozilla Bug 961365
+ </a>
+
+ <p id="display"></p>
+
+ <p>
+ <math>
+ <mspace id="ref" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math style="font-family: fraction-1;">
+ <mfrac>
+ <mspace id="n1" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d1" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math style="font-family: fraction-2;">
+ <mfrac>
+ <mspace id="n2" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d2" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math style="font-family: fraction-3;">
+ <mfrac>
+ <mspace id="n3" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d3" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math style="font-family: fraction-4;">
+ <mfrac>
+ <mspace id="n4" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d4" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math style="font-family: fraction-5;">
+ <mfrac>
+ <mspace id="n5" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d5" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ </p>
+
+ <p>
+ <math displaystyle="true">
+ <mspace id="dref" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math displaystyle="true" style="font-family: fraction-1;">
+ <mfrac>
+ <mspace id="dn1" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="dd1" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math displaystyle="true" style="font-family: fraction-6;">
+ <mfrac>
+ <mspace id="n6" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d6" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math displaystyle="true" style="font-family: fraction-7;">
+ <mfrac>
+ <mspace id="n7" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d7" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math displaystyle="true" style="font-family: fraction-8;">
+ <mfrac>
+ <mspace id="n8" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d8" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math displaystyle="true" style="font-family: fraction-9;">
+ <mfrac>
+ <mspace id="n9" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d9" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/layout/mathml/tests/test_opentype-limits.html b/layout/mathml/tests/test_opentype-limits.html
new file mode 100644
index 000000000..596838789
--- /dev/null
+++ b/layout/mathml/tests/test_opentype-limits.html
@@ -0,0 +1,172 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - limits</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ math {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: limits-1;
+ src: url(/tests/fonts/math/limits-1.otf);
+ }
+ @font-face {
+ font-family: limits-2;
+ src: url(/tests/fonts/math/limits-2.otf);
+ }
+ @font-face {
+ font-family: limits-3;
+ src: url(/tests/fonts/math/limits-3.otf);
+ }
+ @font-face {
+ font-family: limits-4;
+ src: url(/tests/fonts/math/limits-4.otf);
+ }
+ @font-face {
+ font-family: limits-5;
+ src: url(/tests/fonts/math/limits-5.otf);
+ }
+ </style>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var epsilon = 5;
+ function almostEqual(x, y) { return Math.abs(x - y) < epsilon; }
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ function doTest() {
+ ok(almostEqual(getBox("base1").top - getBox("over1").bottom, 7 * 10) &&
+ almostEqual(getBox("base2").top - getBox("over2").bottom, 7 * 10),
+ "Bad UpperLimitGapMin");
+
+ ok(almostEqual(getBox("under3").top - getBox("base3").bottom, 5 * 10) &&
+ almostEqual(getBox("under4").top - getBox("base4").bottom, 5 * 10),
+ "Bad LowerLimitGapMin");
+
+ ok(almostEqual(getBox("ref3").top - getBox("over5").bottom, 9 * 10) &&
+ almostEqual(getBox("ref3").top - getBox("over6").bottom, 9 * 10),
+ "UpperLimitBaselineRiseMin");
+
+ ok(almostEqual(getBox("under7").top - getBox("ref4").bottom, 2 * 10) &&
+ almostEqual(getBox("under8").top - getBox("ref4").bottom, 2 * 10),
+ "LowerLimitBaselineDropMin");
+
+ ok(almostEqual(getBox("base9").top - getBox("over9").bottom,
+ (6 - 2) * 10) &&
+ almostEqual(getBox("base10").top - getBox("over10").bottom,
+ (6 - 2) * 10),
+ "Bad AccentBaseHeight");
+
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="doTest()">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=961365">
+ Mozilla Bug 961365
+ </a>
+
+ <p id="display"></p>
+
+ <p>
+ <math style="font-family: limits-1;" displaystyle="true">
+ <mover>
+ <mo id="base1">∑</mo>
+ <mspace id="over1" height="1em" width="1em" mathbackground="red"/>
+ </mover>
+ </math>
+ <math style="font-family: limits-1;" displaystyle="true">
+ <munderover>
+ <mo id="base2">∑</mo>
+ <mspace id="under2" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="over2" height="1em" width="1em" mathbackground="red"/>
+ </munderover>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: limits-2;" displaystyle="true">
+ <munder>
+ <mo id="base3">∑</mo>
+ <mspace id="under3" height="1em" width="1em" mathbackground="red"/>
+ </munder>
+ </math>
+ <math style="font-family: limits-2;" displaystyle="true">
+ <munderover>
+ <mo id="base4">∑</mo>
+ <mspace id="under4" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="over4" height="1em" width="1em" mathbackground="red"/>
+ </munderover>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: limits-3;" displaystyle="true">
+ <mspace id="ref3" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math style="font-family: limits-3;" displaystyle="true">
+ <mover>
+ <mo id="base5">∑</mo>
+ <mspace id="over5" height="1em" width="1em" mathbackground="red"/>
+ </mover>
+ </math>
+ <math style="font-family: limits-3;" displaystyle="true">
+ <munderover>
+ <mo id="base6">∑</mo>
+ <mspace id="under6" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="over6" height="1em" width="1em" mathbackground="red"/>
+ </munderover>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: limits-4;" displaystyle="true">
+ <mspace id="ref4" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math style="font-family: limits-4;" displaystyle="true">
+ <munder>
+ <mo id="base7">∑</mo>
+ <mspace id="under7" height="1em" width="1em" mathbackground="red"/>
+ </munder>
+ </math>
+ <math style="font-family: limits-4;" displaystyle="true">
+ <munderover>
+ <mo id="base8">∑</mo>
+ <mspace id="under8" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="over8" height="1em" width="1em" mathbackground="red"/>
+ </munderover>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: limits-5;" displaystyle="true">
+ <mspace id="ref5" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math style="font-family: limits-5;" displaystyle="true">
+ <mover>
+ <mspace id="base9" height="2em" width="2em" mathbackground="blue"/>
+ <mo id="over9" stretchy="false">~</mo>
+ </mover>
+ </math>
+ <math style="font-family: limits-5;" displaystyle="true">
+ <munderover>
+ <mspace id="base10" height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="under10" height="1em" width="1em" mathbackground="red"/>
+ <mo id="over10" stretchy="false">~</mo>
+ </munderover>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/layout/mathml/tests/test_opentype-radical.html b/layout/mathml/tests/test_opentype-radical.html
new file mode 100644
index 000000000..b073eb358
--- /dev/null
+++ b/layout/mathml/tests/test_opentype-radical.html
@@ -0,0 +1,196 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - radical</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ math {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: radical-1;
+ src: url(/tests/fonts/math/radical-1.otf);
+ }
+ @font-face {
+ font-family: radical-2;
+ src: url(/tests/fonts/math/radical-2.otf);
+ }
+ @font-face {
+ font-family: radical-3;
+ src: url(/tests/fonts/math/radical-3.otf);
+ }
+ @font-face {
+ font-family: radical-4;
+ src: url(/tests/fonts/math/radical-4.otf);
+ }
+ @font-face {
+ font-family: radical-5;
+ src: url(/tests/fonts/math/radical-5.otf);
+ }
+ @font-face {
+ font-family: radical-6;
+ src: url(/tests/fonts/math/radical-6.otf);
+ }
+ @font-face {
+ font-family: radical-7;
+ src: url(/tests/fonts/math/radical-7.otf);
+ }
+ </style>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var epsilon = 5;
+ function almostEqual(x, y) { return Math.abs(x - y) < epsilon; }
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ function doTest() {
+ ok(almostEqual(getBox("inner1").top -
+ getBox("outer1").top, 5*10) &&
+ almostEqual(getBox("inner2").top -
+ getBox("outer2").top, 5*10) &&
+ almostEqual(getBox("inner3").top -
+ getBox("outer3").top, 5*10),
+ "Bad RadicalRuleThickness");
+
+ ok(almostEqual(getBox("inner4").top -
+ getBox("outer4").top, (7+1)*10) &&
+ almostEqual(getBox("inner5").top -
+ getBox("outer5").top, (7+1)*10) &&
+ almostEqual(getBox("inner6").top -
+ getBox("outer6").top, (7+1)*10),
+ "Bad RadicalExtraAscender");
+
+ ok(almostEqual(getBox("inner7").top -
+ getBox("outer7").top, (3+1)*10) &&
+ almostEqual(getBox("inner7").top -
+ getBox("outer8").top, (3+1)*10) &&
+ almostEqual(getBox("inner8").top -
+ getBox("outer9").top, (3+1)*10),
+ "Bad RadicalVerticalGap");
+
+ ok(almostEqual(getBox("inner10").top -
+ getBox("outer10").top, (9+1)*10) &&
+ almostEqual(getBox("inner11").top -
+ getBox("outer11").top, (9+1)*10) &&
+ almostEqual(getBox("inner12").top -
+ getBox("outer12").top, (9+1)*10),
+ "Bad RadicalDisplayStyleVerticalGap");
+
+ ok(almostEqual(getBox("base5").bottom - getBox("index5").bottom,
+ (getBox("base5").bottom - getBox("root5").top) * .25),
+ "Bad RadicalDegreeBottomRaisePercent")
+
+ ok(almostEqual(getBox("index6").left - getBox("root6").left, 10 * 5),
+ "Bad RadicalKernBeforeDegree")
+
+ ok(almostEqual(getBox("base7").left -
+ getBox("index7").right, 10 * (7+1)),
+ "Bad RadicalKernAfterDegree")
+
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="doTest()">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=961365">
+ Mozilla Bug 961365
+ </a>
+
+ <p id="display"></p>
+
+ <p>
+ <math style="font-family: radical-1;">
+ <msqrt id="outer1" mathbackground="green">
+ <mspace id="inner1" width="1em" height="1em" mathbackground="red"/>
+ </msqrt>
+ <menclose id="outer2" notation="radical" mathbackground="green">
+ <mspace id="inner2" width="1em" height="1em" mathbackground="red"/>
+ </menclose>
+ <mroot id="outer3" mathbackground="green">
+ <mspace id="inner3" width="1em" height="1em" mathbackground="red"/>
+ <mspace/>
+ </mroot>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: radical-2;">
+ <msqrt id="outer4" mathbackground="green">
+ <mspace id="inner4" width="1em" height="1em" mathbackground="red"/>
+ </msqrt>
+ <menclose id="outer5" notation="radical" mathbackground="green">
+ <mspace id="inner5" width="1em" height="1em" mathbackground="red"/>
+ </menclose>
+ <mroot id="outer6" mathbackground="green">
+ <mspace id="inner6" width="1em" height="1em" mathbackground="red"/>
+ <mspace/>
+ </mroot>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: radical-3;">
+ <msqrt id="outer7" mathbackground="green">
+ <mspace id="inner7" width="1em" height="1em" mathbackground="red"/>
+ </msqrt>
+ <menclose id="outer8" notation="radical" mathbackground="green">
+ <mspace id="inner8" width="1em" height="1em" mathbackground="red"/>
+ </menclose>
+ <mroot id="outer9" mathbackground="green">
+ <mspace id="inner9" width="1em" height="1em" mathbackground="red"/>
+ <mspace/>
+ </mroot>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: radical-4;" displaystyle="true">
+ <msqrt id="outer10" mathbackground="green">
+ <mspace id="inner10" width="1em" height="1em" mathbackground="red"/>
+ </msqrt>
+ <menclose id="outer11" notation="radical" mathbackground="green">
+ <mspace id="inner11" width="1em" height="1em" mathbackground="red"/>
+ </menclose>
+ <mroot id="outer12" mathbackground="green">
+ <mspace id="inner12" width="1em" height="1em" mathbackground="red"/>
+ <mspace/>
+ </mroot>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: radical-5;">
+ <mroot id="root5" mathbackground="green">
+ <mspace id="base5" width="1em" height="10em" mathbackground="red"/>
+ <mspace id="index5" width="1em" height="1em" mathbackground="blue"/>
+ </mroot>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: radical-6;">
+ <mroot id="root6" mathbackground="green">
+ <mspace id="base6" width="1em" height="10em" mathbackground="red"/>
+ <mspace id="index6" width="1em" height="1em" mathbackground="blue"/>
+ </mroot>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: radical-7;">
+ <mroot id="root7" mathbackground="green">
+ <mspace id="base7" width="1em" height="10em" mathbackground="red"/>
+ <mspace id="index7" width="1em" height="1em" mathbackground="blue"/>
+ </mroot>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/layout/mathml/tests/test_opentype-scripts.html b/layout/mathml/tests/test_opentype-scripts.html
new file mode 100644
index 000000000..7ad0e90ab
--- /dev/null
+++ b/layout/mathml/tests/test_opentype-scripts.html
@@ -0,0 +1,292 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - scripts</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ math, mspace {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: scripts-1;
+ src: url(/tests/fonts/math/scripts-1.otf);
+ }
+ @font-face {
+ font-family: scripts-2;
+ src: url(/tests/fonts/math/scripts-2.otf);
+ }
+ @font-face {
+ font-family: scripts-3;
+ src: url(/tests/fonts/math/scripts-3.otf);
+ }
+ @font-face {
+ font-family: scripts-4;
+ src: url(/tests/fonts/math/scripts-4.otf);
+ }
+ @font-face {
+ font-family: scripts-5;
+ src: url(/tests/fonts/math/scripts-5.otf);
+ }
+ @font-face {
+ font-family: scripts-6;
+ src: url(/tests/fonts/math/scripts-6.otf);
+ }
+ @font-face {
+ font-family: scripts-7;
+ src: url(/tests/fonts/math/scripts-7.otf);
+ }
+ @font-face {
+ font-family: scripts-8;
+ src: url(/tests/fonts/math/scripts-8.otf);
+ }
+ @font-face {
+ font-family: scripts-9;
+ src: url(/tests/fonts/math/scripts-9.otf);
+ }
+ </style>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ var epsilon = 5;
+ function almostEqual(x, y) { return Math.abs(x - y) < epsilon; }
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ ok(almostEqual(getBox("ref1").left - getBox("sub1").right, 3*10) &&
+ almostEqual(getBox("ref2").left - getBox("sup2").right, 3*10) &&
+ almostEqual(getBox("sup32").left - getBox("sup31").right, 3*10) &&
+ almostEqual(getBox("ref3").left - getBox("sup32").right, 3*10),
+ "SpaceAfterScript");
+
+ ok(almostEqual(getBox("ref4").bottom -
+ getBox("sup41").bottom, 7 * 10) &&
+ almostEqual(getBox("ref4").bottom -
+ getBox("sup42").bottom, 7 * 10) &&
+ almostEqual(getBox("ref4").bottom -
+ getBox("sup43").bottom, 7 * 10) &&
+ almostEqual(getBox("ref4").bottom -
+ getBox("sup44").bottom, 7 * 10),
+ "Bad SuperscriptShiftUp")
+ ok(almostEqual(getBox("ref5").bottom -
+ getBox("sup51").bottom, 5 * 10) &&
+ almostEqual(getBox("ref5").bottom -
+ getBox("sup52").bottom, 5 * 10) &&
+ almostEqual(getBox("ref5").bottom -
+ getBox("sup53").bottom, 5 * 10) &&
+ almostEqual(getBox("ref5").bottom -
+ getBox("sup54").bottom, 5 * 10),
+ "Bad SuperscriptShiftUpCramped")
+
+ ok(almostEqual(getBox("ref6").bottom -
+ getBox("sub61").bottom, -6 * 10) &&
+ almostEqual(getBox("ref6").bottom -
+ getBox("sub62").bottom, -6 * 10) &&
+ almostEqual(getBox("ref6").bottom -
+ getBox("sub63").bottom, -6 * 10) &&
+ almostEqual(getBox("ref6").bottom -
+ getBox("sub64").bottom, -6 * 10),
+ "Bad SubscriptShiftDown")
+
+ ok(almostEqual(getBox("sub7").top -
+ getBox("sup7").bottom, 11 * 10),
+ "Bad SubSuperscriptGapMin");
+
+ ok(almostEqual(getBox("sub8").top -
+ getBox("sup8").bottom, 11 * 10) &&
+ almostEqual(getBox("ref8").bottom -
+ getBox("sup8").bottom, 3 * 10),
+ "Bad SuperscriptBottomMaxWithSubscript");
+
+ ok(almostEqual(getBox("ref9").top, getBox("sub9").top),
+ "Bad SubscriptTopMax");
+
+ ok(almostEqual(getBox("ref10").bottom - getBox("sup10").bottom, 9 * 10),
+ "Bad SuperscriptBottomMin");
+
+ ok(almostEqual(getBox("base11").bottom + 3 * 10,
+ getBox("sub11").top),
+ "Bad SubscriptBaselineDrop");
+
+ ok(almostEqual(getBox("base11").top + 5 * 10,
+ getBox("sup11").bottom),
+ "Bad SuperscriptBaselineDrop");
+
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="doTest()">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=961365">
+ Mozilla Bug 961365
+ </a>
+
+ <p id="display"></p>
+
+ <p>
+ <math style="font-family: scripts-1;">
+ <msub>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sub1" height="1em" width="1em" mathbackground="red"/>
+ </msub>
+ <mspace id="ref1" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math style="font-family: scripts-1;">
+ <msup>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sup2" height="1em" width="1em" mathbackground="red"/>
+ </msup>
+ <mspace id="ref2" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math style="font-family: scripts-1;">
+ <mmultiscripts>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <none/>
+ <mspace id="sup31" height="1em" width="1em" mathbackground="red"/>
+ <none/>
+ <mspace id="sup32" height="1em" width="1em" mathbackground="red"/>
+ </mmultiscripts>
+ <mspace id="ref3" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: scripts-2;">
+ <mspace id="ref4" height="1em" width="1em" mathbackground="green"/>
+ <msup>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sup41" height="1em" width="1em" mathbackground="red"/>
+ </msup>
+ <msubsup>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace height="1em" width="1em" mathbackground="red"/>
+ <mspace id="sup42" height="1em" width="1em" mathbackground="red"/>
+ </msubsup>
+ <mmultiscripts>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <none/>
+ <mspace id="sup43" height="1em" width="1em" mathbackground="red"/>
+ <none/>
+ <mspace id="sup44" height="1em" width="1em" mathbackground="red"/>
+ </mmultiscripts>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: scripts-3;">
+ <msqrt>
+ <mspace id="ref5" height="1em" width="1em" mathbackground="green"/>
+ <msup>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sup51" height="1em" width="1em" mathbackground="red"/>
+ </msup>
+ <msubsup>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace height="1em" width="1em" mathbackground="red"/>
+ <mspace id="sup52" height="1em" width="1em" mathbackground="red"/>
+ </msubsup>
+ <mmultiscripts>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace height="1em" width="1em" mathbackground="red"/>
+ <mspace id="sup53" height="1em" width="1em" mathbackground="red"/>
+ <mspace height="1em" width="1em" mathbackground="red"/>
+ <mspace id="sup54" height="1em" width="1em" mathbackground="red"/>
+ </mmultiscripts>
+ </msqrt>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: scripts-4;">
+ <mspace id="ref6" height="1em" width="1em" mathbackground="green"/>
+ <msub>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sub61" height="1em" width="1em" mathbackground="red"/>
+ </msub>
+ <msubsup>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sub62" height="1em" width="1em" mathbackground="red"/>
+ <mspace height="1em" width="1em" mathbackground="red"/>
+ </msubsup>
+ <mmultiscripts>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sub63" height="1em" width="1em" mathbackground="red"/>
+ <mspace height="1em" width="1em" mathbackground="red"/>
+ <mspace id="sub64" height="1em" width="1em" mathbackground="red"/>
+ <mspace height="1em" width="1em" mathbackground="red"/>
+ </mmultiscripts>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: scripts-5;">
+ <msubsup>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sub7" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="sup7" height="1em" width="1em" mathbackground="red"/>
+ </msubsup>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: scripts-6;">
+ <mspace id="ref8" height="1em" width="1em" mathbackground="green"/>
+ <msubsup>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sub8" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="sup8" height="1em" width="1em" mathbackground="red"/>
+ </msubsup>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: scripts-7;">
+ <msub>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="ref9" height="5em"
+ width="1em" mathbackground="green"/>
+ </msub>
+ <msub>
+ <mspace height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="sub9" height="9em"
+ width="1em" mathbackground="red"/>
+ </msub>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: scripts-8;">
+ <mspace id="ref10" height="1em"
+ width="1em" mathbackground="green"/>
+ <msup>
+ <mspace id="base10" height="2em"
+ width="2em" mathbackground="blue"/>
+ <mspace id="sup10" height="1em"
+ width="1em" mathbackground="red"/>
+ </msup>
+ </math>
+ </p>
+
+ <p>
+ <math style="font-family: scripts-9;">
+ <msubsup>
+ <mspace id="base11" height="10em" depth="10em"
+ width="1em" mathbackground="red"/>
+ <mspace id="sub11"
+ height="0em" depth="1em" width="1em" mathbackground="green"/>
+ <mspace id="sup11"
+ height="1em" depth="0em" width="1em" mathbackground="blue"/>
+ </msubsup>
+ </math>
+ </p>
+ </body>
+</html>
diff --git a/layout/mathml/tests/test_opentype-stack.html b/layout/mathml/tests/test_opentype-stack.html
new file mode 100644
index 000000000..0cbaabb97
--- /dev/null
+++ b/layout/mathml/tests/test_opentype-stack.html
@@ -0,0 +1,137 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - stack</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ math {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: stack-1;
+ src: url(/tests/fonts/math/stack-1.otf);
+ }
+ @font-face {
+ font-family: stack-2;
+ src: url(/tests/fonts/math/stack-2.otf);
+ }
+ @font-face {
+ font-family: stack-3;
+ src: url(/tests/fonts/math/stack-3.otf);
+ }
+ @font-face {
+ font-family: stack-4;
+ src: url(/tests/fonts/math/stack-4.otf);
+ }
+ @font-face {
+ font-family: stack-5;
+ src: url(/tests/fonts/math/stack-5.otf);
+ }
+ @font-face {
+ font-family: stack-6;
+ src: url(/tests/fonts/math/stack-6.otf);
+ }
+ </style>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ var epsilon = 5;
+ function almostEqual(x, y) { return Math.abs(x - y) < epsilon; }
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ /* inline style */
+ var ref = getBox("ref").height;
+
+ ok(almostEqual(getBox("d1").top - getBox("n1").bottom, ref * 20),
+ "Bad StackGapMin");
+
+ ok(almostEqual(getBox("ref").top - getBox("n2").top, ref*3),
+ "Bad StackTopShiftMin");
+
+ ok(almostEqual(getBox("d3").bottom - getBox("ref").bottom, ref*3),
+ "Bad StackBottomShiftDown");
+
+ /* display style */
+ ref = getBox("ref").height;
+
+ ok(almostEqual(getBox("d4").top - getBox("n4").bottom, ref * 20),
+ "Bad StackGapDisplayStyleMin");
+
+ ok(almostEqual(getBox("dref").top - getBox("n5").top, ref*3),
+ "Bad StackTopDisplayStyleShiftMin");
+
+ ok(almostEqual(getBox("d6").bottom - getBox("dref").bottom, ref*3),
+ "Bad StackDisplayStyleBottomShiftDown");
+
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="doTest()">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=961365">
+ Mozilla Bug 961365
+ </a>
+
+ <p id="display"></p>
+
+ <p>
+ <math>
+ <mspace id="ref" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math style="font-family: stack-1;">
+ <mfrac linethickness="0">
+ <mspace id="n1" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d1" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math style="font-family: stack-2;">
+ <mfrac linethickness="0">
+ <mspace id="n2" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d2" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math style="font-family: stack-3;">
+ <mfrac linethickness="0">
+ <mspace id="n3" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d3" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ </p>
+
+ <p>
+ <math displaystyle="true">
+ <mspace id="dref" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math displaystyle="true" style="font-family: stack-4;">
+ <mfrac linethickness="0">
+ <mspace id="n4" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d4" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math displaystyle="true" style="font-family: stack-5;">
+ <mfrac linethickness="0">
+ <mspace id="n5" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d5" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ <math displaystyle="true" style="font-family: stack-6;">
+ <mfrac linethickness="0">
+ <mspace id="n6" height="1em" width="1em" mathbackground="red"/>
+ <mspace id="d6" height="1em" width="1em" mathbackground="red"/>
+ </mfrac>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/layout/mathml/updateOperatorDictionary.pl b/layout/mathml/updateOperatorDictionary.pl
new file mode 100755
index 000000000..ff9344cc8
--- /dev/null
+++ b/layout/mathml/updateOperatorDictionary.pl
@@ -0,0 +1,499 @@
+#!/usr/bin/perl
+# -*- Mode: Perl; tab-width: 2; indent-tabs-mode: nil; -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use XML::LibXSLT;
+use XML::LibXML;
+use LWP::Simple;
+
+# output files
+$FILE_UNICODE = "unicode.xml";
+$FILE_DICTIONARY = "dictionary.xml";
+$FILE_DIFFERENCES = "differences.txt";
+$FILE_NEW_DICTIONARY = "new_dictionary.txt";
+$FILE_SYNTAX_ERRORS = "syntax_errors.txt";
+$FILE_JS = "tests/stretchy-and-large-operators.js";
+
+# our dictionary (property file)
+$MOZ_DICTIONARY = "mathfont.properties";
+
+# dictionary provided by the W3C in "XML Entity Definitions for Characters"
+$WG_DICTIONARY_URL = "http://www.w3.org/2003/entities/2007xml/unicode.xml";
+
+# XSL stylesheet to extract relevant data from the dictionary
+$DICTIONARY_XSL = "operatorDictionary.xsl";
+
+# dictionary provided by the W3C transformed with operatorDictionary.xsl
+$WG_DICTIONARY = $FILE_DICTIONARY;
+
+if (!($#ARGV >= 0 &&
+ ((($ARGV[0] eq "download") && $#ARGV <= 1) ||
+ (($ARGV[0] eq "compare") && $#ARGV <= 1) ||
+ (($ARGV[0] eq "check") && $#ARGV <= 0) ||
+ (($ARGV[0] eq "make-js") && $#ARGV <= 0) ||
+ (($ARGV[0] eq "clean") && $#ARGV <= 0)))) {
+ &usage;
+}
+
+if ($ARGV[0] eq "download") {
+ if ($#ARGV == 1) {
+ $WG_DICTIONARY_URL = $ARGV[1];
+ }
+ print "Downloading $WG_DICTIONARY_URL...\n";
+ getstore($WG_DICTIONARY_URL, $FILE_UNICODE);
+
+ print "Converting $FILE_UNICODE into $FILE_DICTIONARY...\n";
+ my $xslt = XML::LibXSLT->new();
+ my $source = XML::LibXML->load_xml(location => $FILE_UNICODE);
+ my $style_doc = XML::LibXML->load_xml(location => $DICTIONARY_XSL,
+ no_cdata=>1);
+ my $stylesheet = $xslt->parse_stylesheet($style_doc);
+ my $results = $stylesheet->transform($source);
+ open($file, ">$FILE_DICTIONARY") || die ("Couldn't open $FILE_DICTIONARY!");
+ print $file $stylesheet->output_as_bytes($results);
+ close($file);
+ exit 0;
+}
+
+if ($ARGV[0] eq "clean") {
+ unlink($FILE_UNICODE,
+ $FILE_DICTIONARY,
+ $FILE_DIFFERENCES,
+ $FILE_NEW_DICTIONARY,
+ $FILE_SYNTAX_ERRORS);
+ exit 0;
+}
+
+if ($ARGV[0] eq "compare" && $#ARGV == 1) {
+ $WG_DICTIONARY = $ARGV[1];
+}
+
+################################################################################
+# structure of the dictionary used by this script:
+# - key: same as in mathfont.properties
+# - table:
+# index | value
+# 0 | description
+# 1 | lspace
+# 2 | rspace
+# 3 | minsize
+# 4 | largeop
+# 5 | movablelimits
+# 6 | stretchy
+# 7 | separator
+# 8 | accent
+# 9 | fence
+# 10 | symmetric
+# 11 | priority
+# 12 | linebreakstyle
+# 13 | direction
+# 14 | integral
+# 15 | mirrorable
+
+# 1) build %moz_hash from $MOZ_DICTIONARY
+
+print "loading $MOZ_DICTIONARY...\n";
+open($file, $MOZ_DICTIONARY) || die ("Couldn't open $MOZ_DICTIONARY!");
+
+print "building dictionary...\n";
+while (<$file>) {
+ next unless (m/^operator\.(.*)$/);
+ (m/^([\w|\.|\\]*)\s=\s(.*)\s#\s(.*)$/);
+
+ # 1.1) build the key
+ $key = $1;
+
+ # 1.2) build the array
+ $_ = $2;
+ @value = ();
+ $value[0] = $3;
+ if (m/^(.*)lspace:(\d)(.*)$/) { $value[1] = $2; } else { $value[1] = "5"; }
+ if (m/^(.*)rspace:(\d)(.*)$/) { $value[2] = $2; } else { $value[2] = "5"; }
+ if (m/^(.*)minsize:(\d)(.*)$/) { $value[3] = $2; } else { $value[3] = "1"; }
+ $value[4] = (m/^(.*)largeop(.*)$/);
+ $value[5] = (m/^(.*)movablelimits(.*)$/);
+ $value[6] = (m/^(.*)stretchy(.*)$/);
+ $value[7] = (m/^(.*)separator(.*)$/);
+ $value[8] = (m/^(.*)accent(.*)$/);
+ $value[9] = (m/^(.*)fence(.*)$/);
+ $value[10] = (m/^(.*)symmetric(.*)$/);
+ $value[11] = ""; # we don't store "priority" in our dictionary
+ $value[12] = ""; # we don't store "linebreakstyle" in our dictionary
+ if (m/^(.*)direction:([a-z]*)(.*)$/) { $value[13] = $2; }
+ else { $value[13] = ""; }
+ $value[14] = (m/^(.*)integral(.*)$/);
+ $value[15] = (m/^(.*)mirrorable(.*)$/);
+
+ # 1.3) save the key and value
+ $moz_hash{$key} = [ @value ];
+}
+
+close($file);
+
+################################################################################
+# 2) If mode "make-js", generate tests/stretchy-and-large-operators.js and quit.
+# If mode "check", verify validity of our operator dictionary and quit.
+# If mode "compare", go to step 3)
+
+if ($ARGV[0] eq "make-js") {
+ print "generating file $FILE_JS...\n";
+ open($file_js, ">$FILE_JS") ||
+ die ("Couldn't open $FILE_JS!");
+ print $file_js "// This file is automatically generated. Do not edit.\n";
+ print $file_js "var stretchy_and_large_operators = [";
+ @moz_keys = (keys %moz_hash);
+ while ($key = pop(@moz_keys)) {
+ @moz = @{ $moz_hash{$key} };
+
+ $_ = $key;
+ (m/^operator\.([\w|\.|\\]*)\.(prefix|infix|postfix)$/);
+ $opname = "\\$1.$2: ";
+
+ if (@moz[4]) {
+ print $file_js "['$opname', '$1','l','$2'],";
+ }
+
+ if (@moz[6]) {
+ $_ = substr(@moz[13], 0, 1);
+ print $file_js "['$opname', '$1','$_','$2'],";
+ }
+ }
+ print $file_js "];\n";
+ close($file_js);
+ exit 0;
+}
+
+if ($ARGV[0] eq "check") {
+ print "checking operator dictionary...\n";
+ open($file_syntax_errors, ">$FILE_SYNTAX_ERRORS") ||
+ die ("Couldn't open $FILE_SYNTAX_ERRORS!");
+
+ $nb_errors = 0;
+ $nb_warnings = 0;
+ @moz_keys = (keys %moz_hash);
+ # check the validity of our private data
+ while ($key = pop(@moz_keys)) {
+ @moz = @{ $moz_hash{$key} };
+ $entry = &generateEntry($key, @moz);
+ $valid = 1;
+
+ if (!(@moz[13] eq "" ||
+ @moz[13] eq "horizontal" ||
+ @moz[13] eq "vertical")) {
+ $valid = 0;
+ $nb_errors++;
+ print $file_syntax_errors "error: invalid direction \"$moz[13]\"\n";
+ }
+
+ if (!@moz[4] && @moz[14]) {
+ $valid = 0;
+ $nb_warnings++;
+ print $file_syntax_errors "warning: operator is integral but not largeop\n";
+ }
+
+ $_ = @moz[0];
+ if ((m/^(.*)[iI]ntegral(.*)$/) && !@moz[14]) {
+ $valid = 0;
+ $nb_warnings++;
+ print $file_syntax_errors "warning: operator contains the term \"integral\" in its comment, but is not integral\n";
+ }
+
+ if (!$valid) {
+ print $file_syntax_errors $entry;
+ print $file_syntax_errors "\n";
+ }
+ }
+
+ # check that all forms have the same direction.
+ @moz_keys = (keys %moz_hash);
+ while ($key = pop(@moz_keys)) {
+
+ if (@{ $moz_hash{$key} }) {
+ # the operator has not been removed from the hash table yet.
+
+ $_ = $key;
+ (m/^([\w|\.|\\]*)\.(prefix|infix|postfix)$/);
+ $key_prefix = "$1.prefix";
+ $key_infix = "$1.infix";
+ $key_postfix = "$1.postfix";
+ @moz_prefix = @{ $moz_hash{$key_prefix} };
+ @moz_infix = @{ $moz_hash{$key_infix} };
+ @moz_postfix = @{ $moz_hash{$key_postfix} };
+
+ $same_direction = 1;
+
+ if (@moz_prefix) {
+ if (@moz_infix &&
+ !($moz_infix[13] eq $moz_prefix[13])) {
+ $same_direction = 0;
+ }
+ if (@moz_postfix &&
+ !($moz_postfix[13] eq $moz_prefix[13])) {
+ $same_direction = 0;
+ }
+ }
+ if (@moz_infix) {
+ if (@moz_postfix &&
+ !($moz_postfix[13] eq $moz_infix[13])) {
+ $same_direction = 0;
+ }
+ }
+
+ if (!$same_direction) {
+ $nb_errors++;
+ print $file_syntax_errors
+ "error: operator has a stretchy form, but all forms";
+ print $file_syntax_errors
+ " have not the same direction\n";
+ if (@moz_prefix) {
+ $_ = &generateEntry($key_prefix, @moz_prefix);
+ print $file_syntax_errors $_;
+ }
+ if (@moz_infix) {
+ $_ = &generateEntry($key_infix, @moz_infix);
+ print $file_syntax_errors $_;
+ }
+ if (@moz_postfix) {
+ $_ = &generateEntry($key_postfix, @moz_postfix);
+ print $file_syntax_errors $_;
+ }
+ print $file_syntax_errors "\n";
+ }
+
+ if (@moz_prefix) {
+ delete $moz_hash{$key.prefix};
+ }
+ if (@moz_infix) {
+ delete $moz_hash{$key_infix};
+ }
+ if (@moz_postfix) {
+ delete $moz_hash{$key_postfix};
+ }
+ }
+ }
+
+ close($file_syntax_errors);
+ print "\n";
+ if ($nb_errors > 0 || $nb_warnings > 0) {
+ print "$nb_errors error(s) found\n";
+ print "$nb_warnings warning(s) found\n";
+ print "See output file $FILE_SYNTAX_ERRORS.\n\n";
+ } else {
+ print "No error found.\n\n";
+ }
+
+ exit 0;
+}
+
+################################################################################
+# 3) build %wg_hash and @wg_keys from the page $WG_DICTIONARY
+
+print "loading $WG_DICTIONARY...\n";
+my $parser = XML::LibXML->new();
+my $doc = $parser->parse_file($WG_DICTIONARY);
+
+print "building dictionary...\n";
+@wg_keys = ();
+
+foreach my $entry ($doc->findnodes('/root/entry')) {
+ # 3.1) build the key
+ $key = "operator.";
+
+ $_ = $entry->getAttribute("unicode");
+ $_ = "$_-";
+ while (m/^U?0(\w*)-(.*)$/) {
+ # Concatenate .\uNNNN
+ $key = "$key\\u$1";
+ $_ = $2;
+ }
+
+ $_ = $entry->getAttribute("form"); # "Form"
+ $key = "$key.$_";
+
+ # 3.2) build the array
+ @value = ();
+ $value[0] = lc($entry->getAttribute("description"));
+ $value[1] = $entry->getAttribute("lspace");
+ if ($value[1] eq "") { $value[1] = "5"; }
+ $value[2] = $entry->getAttribute("rspace");
+ if ($value[2] eq "") { $value[2] = "5"; }
+ $value[3] = $entry->getAttribute("minsize");
+ if ($value[3] eq "") { $value[3] = "1"; }
+
+ $_ = $entry->getAttribute("properties");
+ $value[4] = (m/^(.*)largeop(.*)$/);
+ $value[5] = (m/^(.*)movablelimits(.*)$/);
+ $value[6] = (m/^(.*)stretchy(.*)$/);
+ $value[7] = (m/^(.*)separator(.*)$/);
+ $value[8] = (m/^(.*)accent(.*)$/);
+ $value[9] = (m/^(.*)fence(.*)$/);
+ $value[10] = (m/^(.*)symmetric(.*)$/);
+ $value[15] = (m/^(.*)mirrorable(.*)$/);
+ $value[11] = $entry->getAttribute("priority");
+ $value[12] = $entry->getAttribute("linebreakstyle");
+
+ # not stored in the WG dictionary
+ $value[13] = ""; # direction
+ $value[14] = ""; # integral
+
+ # 3.3) save the key and value
+ push(@wg_keys, $key);
+ $wg_hash{$key} = [ @value ];
+}
+@wg_keys = reverse(@wg_keys);
+
+################################################################################
+# 4) Compare the two dictionaries and output the result
+
+print "comparing dictionaries...\n";
+open($file_differences, ">$FILE_DIFFERENCES") ||
+ die ("Couldn't open $FILE_DIFFERENCES!");
+open($file_new_dictionary, ">$FILE_NEW_DICTIONARY") ||
+ die ("Couldn't open $FILE_NEW_DICTIONARY!");
+
+$conflicting = 0; $conflicting_stretching = 0;
+$new = 0; $new_stretching = 0;
+$obsolete = 0; $obsolete_stretching = 0;
+$unchanged = 0;
+
+# 4.1) look to the entries of the WG dictionary
+while ($key = pop(@wg_keys)) {
+
+ @wg = @{ $wg_hash{$key} };
+ delete $wg_hash{$key};
+ $wg_value = &generateCommon(@wg);
+
+ if (exists($moz_hash{$key})) {
+ # entry is in both dictionary
+ @moz = @{ $moz_hash{$key} };
+ delete $moz_hash{$key};
+ $moz_value = &generateCommon(@moz);
+ if ($moz_value ne $wg_value) {
+ # conflicting entry
+ print $file_differences "[conflict]";
+ $conflicting++;
+ if ($moz[6] != $wg[6]) {
+ print $file_differences "[stretching]";
+ $conflicting_stretching++;
+ }
+ print $file_differences " - $key ($wg[0])\n";
+ print $file_differences "-$moz_value\n+$wg_value\n\n";
+ $_ = &completeCommon($wg_value, $key, @moz, @wg);
+ print $file_new_dictionary $_;
+ } else {
+ # unchanged entry
+ $unchanged++;
+ $_ = &completeCommon($wg_value, $key, @moz, @wg);
+ print $file_new_dictionary $_;
+ }
+ } else {
+ # we don't have this entry in our dictionary yet
+ print $file_differences "[new entry]";
+ $new++;
+ if ($wg[6]) {
+ print $file_differences "[stretching]";
+ $new_stretching++;
+ }
+ print $file_differences " - $key ($wg[0])\n";
+ print $file_differences "-\n+$wg_value\n\n";
+ $_ = &completeCommon($wg_value, $key, (), @wg);
+ print $file_new_dictionary $_;
+ }
+}
+
+print $file_new_dictionary
+ "\n# Entries below are not part of the official MathML dictionary\n\n";
+# 4.2) look in our dictionary the remaining entries
+@moz_keys = (keys %moz_hash);
+@moz_keys = reverse(sort(@moz_keys));
+
+while ($key = pop(@moz_keys)) {
+ @moz = @{ $moz_hash{$key} };
+ $moz_value = &generateCommon(@moz);
+ print $file_differences "[obsolete entry]";
+ $obsolete++;
+ if ($moz[6]) {
+ print $file_differences "[stretching]";
+ $obsolete_stretching++;
+ }
+ print $file_differences " - $key ($moz[0])\n";
+ print $file_differences "-$moz_value\n+\n\n";
+ $_ = &completeCommon($moz_value, $key, (), @moz);
+ print $file_new_dictionary $_;
+}
+
+close($file_differences);
+close($file_new_dictionary);
+
+print "\n";
+print "- $obsolete obsolete entries ";
+print "($obsolete_stretching of them are related to stretching)\n";
+print "- $unchanged unchanged entries\n";
+print "- $conflicting conflicting entries ";
+print "($conflicting_stretching of them are related to stretching)\n";
+print "- $new new entries ";
+print "($new_stretching of them are related to stretching)\n";
+print "\nSee output files $FILE_DIFFERENCES and $FILE_NEW_DICTIONARY.\n\n";
+print "After having modified the dictionary, please run";
+print "./updateOperatorDictionary check\n\n";
+exit 0;
+
+################################################################################
+sub usage {
+ # display the accepted command syntax and quit
+ print "usage:\n";
+ print " ./updateOperatorDictionary.pl download [unicode.xml]\n";
+ print " ./updateOperatorDictionary.pl compare [dictionary.xml]\n";
+ print " ./updateOperatorDictionary.pl check\n";
+ print " ./updateOperatorDictionary.pl make-js\n";
+ print " ./updateOperatorDictionary.pl clean\n";
+ exit 0;
+}
+
+sub generateCommon {
+ # helper function to generate the string of data shared by both dictionaries
+ my(@v) = @_;
+ $entry = "lspace:$v[1] rspace:$v[2]";
+ if ($v[3] ne "1") { $entry = "$entry minsize:$v[3]"; }
+ if ($v[4]) { $entry = "$entry largeop"; }
+ if ($v[5]) { $entry = "$entry movablelimits"; }
+ if ($v[6]) { $entry = "$entry stretchy"; }
+ if ($v[7]) { $entry = "$entry separator"; }
+ if ($v[8]) { $entry = "$entry accent"; }
+ if ($v[9]) { $entry = "$entry fence"; }
+ if ($v[10]) { $entry = "$entry symmetric"; }
+ if ($v[15]) { $entry = "$entry mirrorable"; }
+ return $entry;
+}
+
+sub completeCommon {
+ # helper to add key and private data to generateCommon
+ my($entry, $key, @v_moz, @v_wg) = @_;
+
+ $entry = "$key = $entry";
+
+ if ($v_moz[13]) { $entry = "$entry direction:$v_moz[13]"; }
+ if ($v_moz[14]) { $entry = "$entry integral"; }
+ if ($v_moz[15]) { $entry = "$entry mirrorable"; }
+
+ if ($v_moz[0]) {
+ # keep our previous comment
+ $entry = "$entry # $v_moz[0]";
+ } else {
+ # otherwise use the description given by the WG
+ $entry = "$entry # $v_wg[0]";
+ }
+
+ $entry = "$entry\n";
+ return $entry;
+}
+
+sub generateEntry {
+ # helper function to generate an entry of our operator dictionary
+ my($key, @moz) = @_;
+ $entry = &generateCommon(@moz);
+ $entry = &completeCommon($entry, $key, @moz, @moz);
+ return $entry;
+}