diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /layout/mathml | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/mathml')
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 0000000000..b8d0947741 --- /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 0000000000..ee69c8ed07 --- /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 0000000000..619345c9bc --- /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 0000000000..7ad9f54ffd --- /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 0000000000..879b426e17 --- /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 0000000000..7275f08596 --- /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 0000000000..77c13fbd6b --- /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 0000000000..5551d8588c --- /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 0000000000..ad673f9460 --- /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 0000000000..318aa93b4e --- /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 0000000000..8527c1a783 --- /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 0000000000..2cd225990d --- /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 0000000000..b8d3d9225a --- /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 0000000000..9c0b46ff54 --- /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 Binary files differnew file mode 100644 index 0000000000..475ea8c164 --- /dev/null +++ b/layout/mathml/crashtests/347355-1.gif diff --git a/layout/mathml/crashtests/347355-1.html b/layout/mathml/crashtests/347355-1.html new file mode 100644 index 0000000000..7e3c6c7c58 --- /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 0000000000..9cff7bdcfe --- /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>∏</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 0000000000..6231950c76 --- /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 0000000000..18edae35d2 --- /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 0000000000..907ede6abb --- /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 0000000000..367cc32bec --- /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 0000000000..30f7943a98 --- /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 0000000000..386e66f933 --- /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 0000000000..d2736cf731 --- /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 0000000000..25a2499f55 --- /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 0000000000..306a847382 --- /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 0000000000..a83e5a8fe0 --- /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> ∫ </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 0000000000..775ace87e0 --- /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 0000000000..79e10c46ac --- /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 0000000000..13e2a0b544 --- /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 0000000000..42511880f8 --- /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 0000000000..41edd7d9b7 --- /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 0000000000..6c6a304a73 --- /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 0000000000..1c7e1c7ade --- /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 0000000000..491b313597 --- /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 0000000000..ddbe7da664 --- /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 0000000000..dc6e9e5711 --- /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 0000000000..b1d261eb38 --- /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 0000000000..cb5c2d7a12 --- /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 0000000000..bc460ead11 --- /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 0000000000..b383141c45 --- /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 0000000000..78754875da --- /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 0000000000..13ff2fcb63 --- /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 0000000000..8a2a927533 --- /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 0000000000..b49bff7d04 --- /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>䦚<span>c<div>t䦚</div></span>䦚x䦚䦚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 0000000000..fea18cbd59 --- /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 0000000000..537512e629 --- /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 0000000000..b7b9e7a60f --- /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 0000000000..2e8f13b73e --- /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 0000000000..425db9a818 --- /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 0000000000..94a9abde21 --- /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 0000000000..db17b277a7 --- /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>⁡</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 0000000000..f25ce28f14 --- /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 0000000000..2630cea131 --- /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 0000000000..61df8ff0c3 --- /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 0000000000..8d41abade0 --- /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 0000000000..0cece35eaa --- /dev/null +++ b/layout/mathml/crashtests/476547-1.xhtml @@ -0,0 +1,5 @@ +<html xmlns="http://www.w3.org/1999/xhtml" style="quotes: '<1>' '';"> +<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 0000000000..b3e9c133cb --- /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 0000000000..ccef762e48 --- /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 0000000000..1bf8d534ca --- /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 0000000000..da52f34b45 --- /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 0000000000..619a1c2719 --- /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 0000000000..a0d4939a83 --- /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 0000000000..9b5895ad64 --- /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 0000000000..7ce9c434cc --- /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>↠</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 0000000000..b6362f9bf2 --- /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>↡</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 0000000000..3ddfb7b984 --- /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 0000000000..dfac127fb4 --- /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 0000000000..6322f9eaa2 --- /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 0000000000..4047b54398 --- /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 Binary files differnew file mode 100644 index 0000000000..0f2ae3bc12 --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-axisheight7000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff Binary files differnew file mode 100644 index 0000000000..03e9b36885 --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woff Binary files differnew file mode 100644 index 0000000000..df6aab016a --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-denominatordisplaystyleshiftdown6000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-denominatorgapmin4000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-denominatorgapmin4000-rulethickness1000.woff Binary files differnew file mode 100644 index 0000000000..c5ba7a6a78 --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-denominatorgapmin4000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-denominatorshiftdown3000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-denominatorshiftdown3000-rulethickness1000.woff Binary files differnew file mode 100644 index 0000000000..2c2b3d81b1 --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-denominatorshiftdown3000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff Binary files differnew file mode 100644 index 0000000000..a899cc9d2c --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woff Binary files differnew file mode 100644 index 0000000000..d3fa259f9a --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-numeratordisplaystyleshiftup2000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-numeratorgapmin9000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-numeratorgapmin9000-rulethickness1000.woff Binary files differnew file mode 100644 index 0000000000..cceffbb5fa --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-numeratorgapmin9000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-numeratorshiftup11000-rulethickness1000.woff b/layout/mathml/imptests/fonts/fraction-numeratorshiftup11000-rulethickness1000.woff Binary files differnew file mode 100644 index 0000000000..20bd5f7d5e --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-numeratorshiftup11000-rulethickness1000.woff diff --git a/layout/mathml/imptests/fonts/fraction-rulethickness10000.woff b/layout/mathml/imptests/fonts/fraction-rulethickness10000.woff Binary files differnew file mode 100644 index 0000000000..8ac9828348 --- /dev/null +++ b/layout/mathml/imptests/fonts/fraction-rulethickness10000.woff diff --git a/layout/mathml/imptests/fonts/stack-axisheight7000.woff b/layout/mathml/imptests/fonts/stack-axisheight7000.woff Binary files differnew file mode 100644 index 0000000000..fa2e6f3aa6 --- /dev/null +++ b/layout/mathml/imptests/fonts/stack-axisheight7000.woff diff --git a/layout/mathml/imptests/fonts/stack-bottomdisplaystyleshiftdown5000.woff b/layout/mathml/imptests/fonts/stack-bottomdisplaystyleshiftdown5000.woff Binary files differnew file mode 100644 index 0000000000..2addfc4b29 --- /dev/null +++ b/layout/mathml/imptests/fonts/stack-bottomdisplaystyleshiftdown5000.woff diff --git a/layout/mathml/imptests/fonts/stack-bottomshiftdown6000.woff b/layout/mathml/imptests/fonts/stack-bottomshiftdown6000.woff Binary files differnew file mode 100644 index 0000000000..246154e2ea --- /dev/null +++ b/layout/mathml/imptests/fonts/stack-bottomshiftdown6000.woff diff --git a/layout/mathml/imptests/fonts/stack-displaystylegapmin4000.woff b/layout/mathml/imptests/fonts/stack-displaystylegapmin4000.woff Binary files differnew file mode 100644 index 0000000000..02dd515311 --- /dev/null +++ b/layout/mathml/imptests/fonts/stack-displaystylegapmin4000.woff diff --git a/layout/mathml/imptests/fonts/stack-gapmin8000.woff b/layout/mathml/imptests/fonts/stack-gapmin8000.woff Binary files differnew file mode 100644 index 0000000000..173907405b --- /dev/null +++ b/layout/mathml/imptests/fonts/stack-gapmin8000.woff diff --git a/layout/mathml/imptests/fonts/stack-topdisplaystyleshiftup3000.woff b/layout/mathml/imptests/fonts/stack-topdisplaystyleshiftup3000.woff Binary files differnew file mode 100644 index 0000000000..c8db7da1f7 --- /dev/null +++ b/layout/mathml/imptests/fonts/stack-topdisplaystyleshiftup3000.woff diff --git a/layout/mathml/imptests/fonts/stack-topshiftup9000.woff b/layout/mathml/imptests/fonts/stack-topshiftup9000.woff Binary files differnew file mode 100644 index 0000000000..8587906595 --- /dev/null +++ b/layout/mathml/imptests/fonts/stack-topshiftup9000.woff diff --git a/layout/mathml/imptests/fonts/xheight500.woff b/layout/mathml/imptests/fonts/xheight500.woff Binary files differnew file mode 100644 index 0000000000..fe9f88893a --- /dev/null +++ b/layout/mathml/imptests/fonts/xheight500.woff diff --git a/layout/mathml/imptests/mochitest.ini b/layout/mathml/imptests/mochitest.ini new file mode 100644 index 0000000000..ad92b49939 --- /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 0000000000..d067d01848 --- /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 0000000000..03a249a92b --- /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=" 	

 	

2.54cm 	

 	

"/> + <mspace id="spaceEm" width=" 	

 	

12em 	

 	

"/> + <mspace id="spaceEx" width=" 	

 	

100ex 	

 	

"/> + <mspace id="spaceIn" width=" 	

 	

3in 	

 	

"/> + <mspace style="font-size: 1000px" id="spaceNamed" width=" 	

 	

veryverythickmathspace 	

 	

"/> + <mspace id="spaceMm" width=" 	

 	

152.4mm 	

 	

"/> + <mspace id="spacePc" width=" 	

 	

6pc 	

 	

"/> + <mstyle mathsize="200%"><mspace id="spacePercentage" width=" 	

 	

3em 	

 	

"/></mstyle> + <mspace id="spacePt" width=" 	

 	

72pt 	

 	

"/> + <mspace id="spacePx" width=" 	

 	

123px 	

 	

"/> + <mstyle mathsize="5"><mspace id="spaceNone" width=" 	

 	

3em 	

 	

"/></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 0000000000..784e7dfe8d --- /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 0000000000..d3aeb44e90 --- /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 0000000000..eb958a6cc3 --- /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 # & +operator.\u0026\u0026.infix = lspace:4 rspace:4 # && +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 # < +operator.\u003C\u003D.infix = lspace:5 rspace:5 # <= +operator.\u003C\u003E.infix = lspace:1 rspace:1 # <> +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 # ^ 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 # ` +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 # | | +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 # ¨ +operator.\u00AC.prefix = lspace:2 rspace:1 # not sign +operator.\u00AF.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ‾ +operator.\u00B0.postfix = lspace:0 rspace:0 # degree sign +operator.\u00B1.infix = lspace:4 rspace:4 # ± +operator.\u00B1.prefix = lspace:0 rspace:1 # ± +operator.\u00B4.postfix = lspace:0 rspace:0 accent # ´ +operator.\u00B7.infix = lspace:4 rspace:4 # · +operator.\u00B8.postfix = lspace:0 rspace:0 accent # ¸ +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 # ˇ 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 # ˘ +operator.\u02D9.postfix = lspace:0 rspace:0 accent # ˙ +operator.\u02DA.postfix = lspace:0 rspace:0 accent # ring above +operator.\u02DC.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ˜ small tilde +operator.\u02DD.postfix = lspace:0 rspace:0 accent # ˝ +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 # ̑ +operator.\u03F6.infix = lspace:5 rspace:5 # greek reversed lunate epsilon symbol +operator.\u2016.prefix = lspace:0 rspace:0 stretchy fence direction:vertical # ‖ ‖ +operator.\u2016.postfix = lspace:0 rspace:0 stretchy fence direction:vertical # ‖ ‖ +operator.\u2018.prefix = lspace:0 rspace:0 fence mirrorable # ‘ +operator.\u2019.postfix = lspace:0 rspace:0 fence mirrorable # ’ +operator.\u201C.prefix = lspace:0 rspace:0 fence mirrorable # “ +operator.\u201D.postfix = lspace:0 rspace:0 fence mirrorable # ” +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 # ⁡ +operator.\u2062.infix = lspace:0 rspace:0 # ⁢ +operator.\u2063.infix = lspace:0 rspace:0 separator # ⁣ +operator.\u2064.infix = lspace:0 rspace:0 # invisible plus +operator.\u20DB.postfix = lspace:0 rspace:0 accent # ⃛ +operator.\u20DC.postfix = lspace:0 rspace:0 accent # combining four dots above +operator.\u2145.prefix = lspace:2 rspace:1 # ⅅ +operator.\u2146.prefix = lspace:2 rspace:0 # ⅆ +operator.\u2190.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ← +operator.\u2191.infix = lspace:5 rspace:5 stretchy direction:vertical # ↑ +operator.\u2192.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # → +operator.\u2193.infix = lspace:5 rspace:5 stretchy direction:vertical # ↓ +operator.\u2194.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ↔ +operator.\u2195.infix = lspace:5 rspace:5 stretchy direction:vertical # ↕ +operator.\u2196.infix = lspace:5 rspace:5 stretchy direction:vertical # ↖ +operator.\u2197.infix = lspace:5 rspace:5 stretchy direction:vertical # ↗ +operator.\u2198.infix = lspace:5 rspace:5 stretchy direction:horizontal # ↘ +operator.\u2199.infix = lspace:5 rspace:5 stretchy direction:horizontal # ↙ +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 # ↤ +operator.\u21A5.infix = lspace:5 rspace:5 stretchy direction:vertical # ↥ +operator.\u21A6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ↦ +operator.\u21A7.infix = lspace:5 rspace:5 stretchy direction:vertical # ↧ +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 # ↩ ↩ +operator.\u21AA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ↪ ↪ +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 # ↼ +operator.\u21BD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ↽ +operator.\u21BE.infix = lspace:5 rspace:5 stretchy direction:vertical # ↾ +operator.\u21BF.infix = lspace:5 rspace:5 stretchy direction:vertical # ↿ +operator.\u21C0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⇀ +operator.\u21C1.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⇁ +operator.\u21C2.infix = lspace:5 rspace:5 stretchy direction:vertical # ⇂ +operator.\u21C3.infix = lspace:5 rspace:5 stretchy direction:vertical # ⇃ +operator.\u21C4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⇄ +operator.\u21C5.infix = lspace:5 rspace:5 stretchy direction:vertical # ⇅ +operator.\u21C6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⇆ +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 # ⇋ +operator.\u21CC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⇌ +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 # ⇐ +operator.\u21D1.infix = lspace:5 rspace:5 stretchy direction:vertical # ⇑ +operator.\u21D2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⇒ ⇒ +operator.\u21D3.infix = lspace:5 rspace:5 stretchy direction:vertical # ⇓ +operator.\u21D4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⇔ +operator.\u21D5.infix = lspace:5 rspace:5 stretchy direction:vertical # ⇕ +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 # ⇤ +operator.\u21E5.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⇥ +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 # ⇵ +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 # ∀ +operator.\u2201.infix = lspace:1 rspace:2 # complement +operator.\u2202.prefix = lspace:2 rspace:1 # ∂ +operator.\u2203.prefix = lspace:2 rspace:1 # ∃ +operator.\u2204.prefix = lspace:2 rspace:1 # ∄ +operator.\u2206.infix = lspace:3 rspace:3 # increment +operator.\u2207.prefix = lspace:2 rspace:1 # ∇ +operator.\u2208.infix = lspace:5 rspace:5 # ∈ +operator.\u2209.infix = lspace:5 rspace:5 # ∉ +operator.\u220A.infix = lspace:5 rspace:5 # small element of +operator.\u220B.infix = lspace:5 rspace:5 # ∋ ∋ +operator.\u220C.infix = lspace:5 rspace:5 # ∌ +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 # ∏ +operator.\u2210.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # ∐ +operator.\u2211.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical mirrorable # ∑ +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 # ∓ +operator.\u2213.prefix = lspace:0 rspace:1 # ∓ +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 # ∘ +operator.\u2219.infix = lspace:4 rspace:4 # bullet operator +operator.\u221A.prefix = lspace:1 rspace:1 stretchy direction:vertical mirrorable # √ +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 # ∝ +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 # ∤ +operator.\u2225.infix = lspace:5 rspace:5 direction:vertical # parallel to +operator.\u2226.infix = lspace:5 rspace:5 # ∦ +operator.\u2227.infix = lspace:4 rspace:4 # ∧ +operator.\u2228.infix = lspace:4 rspace:4 # ∨ +operator.\u2229.infix = lspace:4 rspace:4 # ∩ +operator.\u222A.infix = lspace:4 rspace:4 # ∪ +operator.\u222B.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # ∫ +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 # ∮ +operator.\u222F.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # ∯ +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 # ∲ +operator.\u2233.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral # ∳ +operator.\u2234.infix = lspace:5 rspace:5 # ∴ +operator.\u2235.infix = lspace:5 rspace:5 # ∵ +operator.\u2236.infix = lspace:5 rspace:5 # ratio +operator.\u2237.infix = lspace:5 rspace:5 # ∷ ∷ +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 # ∼ +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 # ≀ +operator.\u2241.infix = lspace:5 rspace:5 # ≁ +operator.\u2242.infix = lspace:5 rspace:5 # ≂ +operator.\u2242\u0338.infix = lspace:5 rspace:5 # ≂̸ +operator.\u2243.infix = lspace:5 rspace:5 # ≃ +operator.\u2244.infix = lspace:5 rspace:5 # ≄ +operator.\u2245.infix = lspace:5 rspace:5 # ≅ +operator.\u2246.infix = lspace:5 rspace:5 # approximately but not actually equal to +operator.\u2247.infix = lspace:5 rspace:5 # ≇ +operator.\u2248.infix = lspace:5 rspace:5 # ≈ +operator.\u2249.infix = lspace:5 rspace:5 # ≉ +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 # ≍ +operator.\u224E.infix = lspace:5 rspace:5 # ≎ +operator.\u224E\u0338.infix = lspace:5 rspace:5 # ≎̸ +operator.\u224F.infix = lspace:5 rspace:5 # ≏ +operator.\u224F\u0338.infix = lspace:5 rspace:5 # ≏̸ +operator.\u2250.infix = lspace:5 rspace:5 # ≐ +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 # ≔ +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 # ≠ +operator.\u2261.infix = lspace:5 rspace:5 # ≡ +operator.\u2262.infix = lspace:5 rspace:5 # ≢ +operator.\u2263.infix = lspace:5 rspace:5 # strictly equivalent to +operator.\u2264.infix = lspace:5 rspace:5 # ≤ +operator.\u2265.infix = lspace:5 rspace:5 # ≥ +operator.\u2266.infix = lspace:5 rspace:5 # ≦ +operator.\u2266\u0338.infix = lspace:5 rspace:5 # ≧̸ +operator.\u2267.infix = lspace:5 rspace:5 # ≧ +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 # ≪ +operator.\u226A\u0338.infix = lspace:5 rspace:5 # ≪̸ +operator.\u226B.infix = lspace:5 rspace:5 # ≫ +operator.\u226B\u0338.infix = lspace:5 rspace:5 # ≫̸ +operator.\u226C.infix = lspace:5 rspace:5 # between +operator.\u226D.infix = lspace:5 rspace:5 # ≭ +operator.\u226E.infix = lspace:5 rspace:5 # ≮ +operator.\u226F.infix = lspace:5 rspace:5 # ≯ +operator.\u2270.infix = lspace:5 rspace:5 # ≰ +operator.\u2271.infix = lspace:5 rspace:5 # ≱ +operator.\u2272.infix = lspace:5 rspace:5 # ≲ +operator.\u2273.infix = lspace:5 rspace:5 # ≳ +operator.\u2274.infix = lspace:5 rspace:5 # ≴ +operator.\u2275.infix = lspace:5 rspace:5 # ≵ +operator.\u2276.infix = lspace:5 rspace:5 # ≶ +operator.\u2277.infix = lspace:5 rspace:5 # ≷ +operator.\u2278.infix = lspace:5 rspace:5 # ≸ +operator.\u2279.infix = lspace:5 rspace:5 # ≹ +operator.\u227A.infix = lspace:5 rspace:5 # ≺ +operator.\u227B.infix = lspace:5 rspace:5 # ≻ +operator.\u227C.infix = lspace:5 rspace:5 # ≼ +operator.\u227D.infix = lspace:5 rspace:5 # ≽ +operator.\u227E.infix = lspace:5 rspace:5 # ≾ +operator.\u227F.infix = lspace:5 rspace:5 # ≿ +operator.\u227F\u0338.infix = lspace:5 rspace:5 # ≿̸ +operator.\u2280.infix = lspace:5 rspace:5 # ⊀ +operator.\u2281.infix = lspace:5 rspace:5 # ⊁ +operator.\u2282.infix = lspace:5 rspace:5 # ⊂ +operator.\u2282\u20D2.infix = lspace:5 rspace:5 # subset of with vertical line +operator.\u2283.infix = lspace:5 rspace:5 # ⊃ +operator.\u2283\u20D2.infix = lspace:5 rspace:5 # superset of with vertical line +operator.\u2284.infix = lspace:5 rspace:5 # ⊄ +operator.\u2285.infix = lspace:5 rspace:5 # ⊅ +operator.\u2286.infix = lspace:5 rspace:5 # ⊆ +operator.\u2287.infix = lspace:5 rspace:5 # ⊇ +operator.\u2288.infix = lspace:5 rspace:5 # ⊈ +operator.\u2289.infix = lspace:5 rspace:5 # ⊉ +operator.\u228A.infix = lspace:5 rspace:5 # ⊊ ⊊ +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 # ⊎ +operator.\u228F.infix = lspace:5 rspace:5 # ⊏ +operator.\u228F\u0338.infix = lspace:5 rspace:5 # ⊏̸ +operator.\u2290.infix = lspace:5 rspace:5 # ⊐ +operator.\u2290\u0338.infix = lspace:5 rspace:5 # ⊐̸ +operator.\u2291.infix = lspace:5 rspace:5 # ⊑ +operator.\u2292.infix = lspace:5 rspace:5 # ⊒ +operator.\u2293.infix = lspace:4 rspace:4 direction:vertical # ⊓ +operator.\u2294.infix = lspace:4 rspace:4 direction:vertical # ⊔ +operator.\u2295.infix = lspace:4 rspace:4 # ⊕ +operator.\u2296.infix = lspace:4 rspace:4 # ⊖ +operator.\u2297.infix = lspace:4 rspace:4 # ⊗ +operator.\u2298.infix = lspace:4 rspace:4 # circled division slash +operator.\u2299.infix = lspace:4 rspace:4 # ⊙ +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 # ⊢ +operator.\u22A3.infix = lspace:5 rspace:5 # ⊣ +operator.\u22A4.infix = lspace:5 rspace:5 # ⊤ +operator.\u22A5.infix = lspace:5 rspace:5 # ⊥ +operator.\u22A6.infix = lspace:5 rspace:5 # assertion +operator.\u22A7.infix = lspace:5 rspace:5 # models +operator.\u22A8.infix = lspace:5 rspace:5 # ⊨ +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 # ⊲ +operator.\u22B3.infix = lspace:5 rspace:5 # ⊳ +operator.\u22B4.infix = lspace:5 rspace:5 # ⊴ +operator.\u22B5.infix = lspace:5 rspace:5 # ⊵ +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 # ⋀ +operator.\u22C1.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # ⋁ +operator.\u22C2.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # ⋂ +operator.\u22C3.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # ⋃ +operator.\u22C4.infix = lspace:4 rspace:4 # ⋄ +operator.\u22C5.infix = lspace:4 rspace:4 # ċ +operator.\u22C6.infix = lspace:4 rspace:4 # ⋆ +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 # ⋐ +operator.\u22D1.infix = lspace:5 rspace:5 # double superset +operator.\u22D2.infix = lspace:4 rspace:4 # ⋒ +operator.\u22D3.infix = lspace:4 rspace:4 # ⋓ +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 # ⋚ +operator.\u22DB.infix = lspace:5 rspace:5 # ⋛ +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 # ⋠ +operator.\u22E1.infix = lspace:5 rspace:5 # ⋡ +operator.\u22E2.infix = lspace:5 rspace:5 # ⋢ +operator.\u22E3.infix = lspace:5 rspace:5 # ⋣ +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 # ⋪ +operator.\u22EB.infix = lspace:5 rspace:5 # ⋫ +operator.\u22EC.infix = lspace:5 rspace:5 # ⋬ +operator.\u22ED.infix = lspace:5 rspace:5 # ⋭ +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 # ⌈ +operator.\u2309.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # ⌉ +operator.\u230A.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # ⌊ +operator.\u230B.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # ⌋ +operator.\u23B4.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⎴ +operator.\u23B5.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⎵ +operator.\u23DC.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⏜ (Unicode) +operator.\u23DD.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⏝ (Unicode) +operator.\u23DE.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⏞ (Unicode) +operator.\u23DF.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⏟ (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 # ⟦ +operator.\u27E7.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # ⟧ +operator.\u27E8.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # ⟨ +operator.\u27E9.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # ⟩ +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 # ⟵ +operator.\u27F6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⟶ +operator.\u27F7.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⟷ +operator.\u27F8.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⟸ +operator.\u27F9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⟹ +operator.\u27FA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⟺ +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 # ⤒ +operator.\u2913.infix = lspace:5 rspace:5 stretchy direction:vertical # ⤓ +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 # ⥎ +operator.\u294F.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥏ +operator.\u2950.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⥐ +operator.\u2951.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥑ +operator.\u2952.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⥒ +operator.\u2953.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⥓ +operator.\u2954.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥔ +operator.\u2955.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥕ +operator.\u2956.infix = lspace:5 rspace:5 stretchy direction:horizontal # ⥖ +operator.\u2957.infix = lspace:5 rspace:5 stretchy direction:horizontal # ⥗ +operator.\u2958.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥘ +operator.\u2959.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥙ +operator.\u295A.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⥚ +operator.\u295B.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⥛ +operator.\u295C.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥜ +operator.\u295D.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥝ +operator.\u295E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⥞ +operator.\u295F.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # ⥟ +operator.\u2960.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥠ +operator.\u2961.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥡ +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 # ⥮ +operator.\u296F.infix = lspace:5 rspace:5 stretchy direction:vertical # ⥯ +operator.\u2970.infix = lspace:5 rspace:5 accent # ⥰ +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 # ⧏ +operator.\u29CF\u0338.infix = lspace:5 rspace:5 # ⧏̸ +operator.\u29D0.infix = lspace:5 rspace:5 # ⧐ +operator.\u29D0\u0338.infix = lspace:5 rspace:5 # ⧐̸ +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 # ⨀ +operator.\u2A01.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # ⨁ +operator.\u2A02.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # ⨂ +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 # ⨄ +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 # ⨆ +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 # ⨯ +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 # ⩓ +operator.\u2A54.infix = lspace:4 rspace:4 direction:vertical # ⩔ +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 # ⩵ +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 # ⩽ +operator.\u2A7D\u0338.infix = lspace:5 rspace:5 # ⩽̸ +operator.\u2A7E.infix = lspace:5 rspace:5 # ⩾ +operator.\u2A7E\u0338.infix = lspace:5 rspace:5 # ⩾̸ +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 # ⪅ +operator.\u2A86.infix = lspace:5 rspace:5 # ⪆ +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 # ⪋ +operator.\u2A8C.infix = lspace:5 rspace:5 # ⪌ +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 # ⪡ +operator.\u2AA1\u0338.infix = lspace:5 rspace:5 # ⪡̸ +operator.\u2AA2.infix = lspace:5 rspace:5 # ⪢ +operator.\u2AA2\u0338.infix = lspace:5 rspace:5 # ⪢̸ +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 # ⪯ +operator.\u2AAF\u0338.infix = lspace:5 rspace:5 # ⪯̸ +operator.\u2AB0.infix = lspace:5 rspace:5 # ⪰ +operator.\u2AB0\u0338.infix = lspace:5 rspace:5 # ⪰̸ +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 # ⪳ +operator.\u2AB4.infix = lspace:5 rspace:5 # ⪴ +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 # ⪷ +operator.\u2AB8.infix = lspace:5 rspace:5 # ⪸ +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 # ⫅ +operator.\u2AC6.infix = lspace:5 rspace:5 # ⫆ +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 # ⫤ +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 # & +operator.\u0026.prefix = lspace:0 rspace:5 # & +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 # <⃒ +operator.\u003E\u20D2.infix = lspace:5 rspace:5 # >⃒ +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 # _ +operator.\u2016.infix = lspace:5 rspace:5 stretchy direction:vertical # ‖ ‖ +operator.\u20D0.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⃐ +operator.\u20D1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⃑ +operator.\u20D6.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⃖ +operator.\u20D7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⃗ +operator.\u20E1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⃡ +operator.\u2190\u200B.infix = lspace:5 rspace:5 # ← +operator.\u2191\u200B.infix = lspace:2 rspace:2 # ↑ +operator.\u2192\u200B.infix = lspace:5 rspace:5 # → +operator.\u2193\u200B.infix = lspace:2 rspace:2 # ↓ +operator.\u2223.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ∣ +operator.\u2223.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ∣ +operator.\u2225.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ∥ +operator.\u2225.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ∥ +operator.\u2282\u020D2.infix = lspace:5 rspace:5 # ⊂⃒ +operator.\u2283\u020D2.infix = lspace:5 rspace:5 # ⊃⃒ +operator.\u228E.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # ⊎ +operator.\u2295.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # ⊕ +operator.\u2296.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # ⊖ +operator.\u2297.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # ⊗ +operator.\u2299.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # ⊙ +operator.\u23B0.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⎰ ⎰ +operator.\u23B1.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⎱ ⎱ +operator.\u2500.infix = lspace:0 rspace:0 stretchy direction:horizontal # ─ +operator.\u25A1.prefix = lspace:0 rspace:2 # □ +operator.\u2606.infix = lspace:3 rspace:3 # ☆ +operator.\u2AC5\u0338.infix = lspace:5 rspace:5 # ⫅̸ +operator.\u2AC6\u0338.infix = lspace:5 rspace:5 # ⫅̸ +operator.\u2AEC.prefix = lspace:0 rspace:5 # ⫬ +operator.\uFE35.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⏜ (MathML 2.0) +operator.\uFE36.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⏝ (MathML 2.0) +operator.\uFE37.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⏞ (MathML 2.0) +operator.\uFE38.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⏟ (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 0000000000..55229f90b0 --- /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 # ⏞ (Unicode) +\uFE37 = \uE13B@7\uE140@7\uE13C@7\uE14A@7\uFFFD\u23DE@1\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # ⏞ (MathML 2.0) +\u23B4 = \uE146@7\uFFFD\uE147@7\uE14A@7\uFFFD\u23B4@1\u23B4@2\u23B4@3\u23B4@4\u23B4@5 # ⎴ +\u23DC = \uE142@7\uFFFD\uE143@7\uE14A@7\uFFFD\u23DC@1\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # ⏜ (Unicode) +\uFE35 = \uE142@7\uFFFD\uE143@7\uE14A@7\uFFFD\u23DC@1\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # ⏜ (MathML 2.0) +\u23DF = \uE13D@7\uE141@7\uE13E@7\uE13F@7\uFFFD\u23DF@1\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # ⏟ (Unicode) +\uFE38 = \uE13D@7\uE141@7\uE13E@7\uE13F@7\uFFFD\u23DF@1\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # ⏟ (MathML 2.0) +\u23B5 = \uE148@7\uFFFD\uE149@7\uE14B@7\uFFFD\u23B5@1\u23B5@2\u23B5@3\u23B5@4\u23B5@5 # ⎵ +\u23DD = \uE144@7\uFFFD\uE145@7\uE14B@7\uFFFD\u23DD@1\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # ⏝ (Unicode) +\uFE36 = \uE144@7\uFFFD\uE145@7\uE14B@7\uFFFD\u23DD@1\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # ⏝ (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 0000000000..32984094ce --- /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 0000000000..18bf5b1c0b --- /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 0000000000..44a4c31c91 --- /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 0000000000..f40b2c2c65 --- /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 0000000000..3d3c67018e --- /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 0000000000..349e099ab1 --- /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 0000000000..507e32b22d --- /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(¶ms, 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 0000000000..fc61d1939a --- /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 0000000000..ad1b13efde --- /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 ∑ might appear big in the following situation + // <math xmlns='http://www.w3.org/1998/Math/MathML'> + // <mstyle> + // <msub> + // <msub><mo>∑</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub> + // <msub><mo>∑</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>⁡</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>⁡</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 0000000000..94ccf70d24 --- /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 0000000000..acb69e3879 --- /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 0000000000..1df3e3a782 --- /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 0000000000..bbb03014b7 --- /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 0000000000..d850c4b9db --- /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 0000000000..5d4285ee90 --- /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 0000000000..2378d07632 --- /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 0000000000..ad523a594d --- /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 0000000000..7558664f21 --- /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 0000000000..dec2e3c132 --- /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 0000000000..1e027ceab8 --- /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 0000000000..9f2a149b82 --- /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 0000000000..64277a92e9 --- /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 0000000000..82d73b2cfc --- /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 0000000000..ca780e649b --- /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 0000000000..2d7535d0c5 --- /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 0000000000..4e74faea2a --- /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 0000000000..330a311803 --- /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 0000000000..3504bdafd5 --- /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 0000000000..f3907e7309 --- /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 0000000000..591b46309a --- /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) || // − + (ch == 0x2264) || // ≤ + (ch == 0x2265) || // ≥ + (ch == 0x00D7)) { // × + 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 0000000000..f25107a20b --- /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 0000000000..5b2e8282fa --- /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 0000000000..ef76a9c7a6 --- /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 0000000000..4c81bde3d9 --- /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 0000000000..0996af26b8 --- /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 0000000000..4fe3bbafb6 --- /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 0000000000..5aab800f5b --- /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 0000000000..54f2f325a7 --- /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 0000000000..0dfff3a4c9 --- /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 0000000000..096d547693 --- /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 0000000000..0d38c91c38 --- /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 0000000000..a706fb4830 --- /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 0000000000..5cb4fc4a2f --- /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 0000000000..8fb932719d --- /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 ∑. + +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 0000000000..bbb0ea8f3e --- /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 0000000000..59029e209b --- /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 0000000000..aedb8af8b8 --- /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 0000000000..702b230ee1 --- /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 0000000000..0885f4c6b3 --- /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 0000000000..6eee75d013 --- /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 0000000000..6a66515096 --- /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 0000000000..13fe14aa71 --- /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 0000000000..775e028408 --- /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 0000000000..d0599a97b7 --- /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 0000000000..439ddbdd68 --- /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 0000000000..76cfc63526 --- /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 0000000000..b7d27504da --- /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 0000000000..ebc46d333e --- /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 0000000000..854693eada --- /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 0000000000..9b649d0f9c --- /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><foo></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&2<3>4 \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&2<3>4 \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 0000000000..dff4011bb7 --- /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 0000000000..cfab51d401 --- /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 0000000000..ad25b2e4d3 --- /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 0000000000..596838789c --- /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 0000000000..b073eb3586 --- /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 0000000000..7ad0e90ab0 --- /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 0000000000..0cbaabb977 --- /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 0000000000..ff9344cc83 --- /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; +} |