summaryrefslogtreecommitdiffstats
path: root/layout/mathml
diff options
context:
space:
mode:
Diffstat (limited to 'layout/mathml')
-rw-r--r--layout/mathml/jar.mn6
-rw-r--r--layout/mathml/mathfont.properties1377
-rw-r--r--layout/mathml/mathfontSTIXGeneral.properties128
-rw-r--r--layout/mathml/mathfontUnicode.properties92
-rw-r--r--layout/mathml/mathml.css330
-rw-r--r--layout/mathml/moz.build62
-rw-r--r--layout/mathml/nsIMathMLFrame.h391
-rw-r--r--layout/mathml/nsMathMLAtoms.h12
-rw-r--r--layout/mathml/nsMathMLChar.cpp2261
-rw-r--r--layout/mathml/nsMathMLChar.h222
-rw-r--r--layout/mathml/nsMathMLContainerFrame.cpp1499
-rw-r--r--layout/mathml/nsMathMLContainerFrame.h516
-rw-r--r--layout/mathml/nsMathMLFrame.cpp383
-rw-r--r--layout/mathml/nsMathMLFrame.h298
-rw-r--r--layout/mathml/nsMathMLOperators.cpp431
-rw-r--r--layout/mathml/nsMathMLOperators.h174
-rw-r--r--layout/mathml/nsMathMLParts.h70
-rw-r--r--layout/mathml/nsMathMLSelectedFrame.cpp165
-rw-r--r--layout/mathml/nsMathMLSelectedFrame.h56
-rw-r--r--layout/mathml/nsMathMLTokenFrame.cpp196
-rw-r--r--layout/mathml/nsMathMLTokenFrame.h69
-rw-r--r--layout/mathml/nsMathMLmactionFrame.cpp309
-rw-r--r--layout/mathml/nsMathMLmactionFrame.h75
-rw-r--r--layout/mathml/nsMathMLmencloseFrame.cpp828
-rw-r--r--layout/mathml/nsMathMLmencloseFrame.h116
-rw-r--r--layout/mathml/nsMathMLmfracFrame.cpp373
-rw-r--r--layout/mathml/nsMathMLmfracFrame.h114
-rw-r--r--layout/mathml/nsMathMLmmultiscriptsFrame.cpp630
-rw-r--r--layout/mathml/nsMathMLmmultiscriptsFrame.h54
-rw-r--r--layout/mathml/nsMathMLmoFrame.cpp1061
-rw-r--r--layout/mathml/nsMathMLmoFrame.h100
-rw-r--r--layout/mathml/nsMathMLmpaddedFrame.cpp441
-rw-r--r--layout/mathml/nsMathMLmpaddedFrame.h99
-rw-r--r--layout/mathml/nsMathMLmrootFrame.cpp399
-rw-r--r--layout/mathml/nsMathMLmrootFrame.h67
-rw-r--r--layout/mathml/nsMathMLmrowFrame.cpp68
-rw-r--r--layout/mathml/nsMathMLmrowFrame.h56
-rw-r--r--layout/mathml/nsMathMLmspaceFrame.cpp124
-rw-r--r--layout/mathml/nsMathMLmspaceFrame.h61
-rw-r--r--layout/mathml/nsMathMLmsqrtFrame.cpp52
-rw-r--r--layout/mathml/nsMathMLmsqrtFrame.h69
-rw-r--r--layout/mathml/nsMathMLmtableFrame.cpp1219
-rw-r--r--layout/mathml/nsMathMLmtableFrame.h295
-rw-r--r--layout/mathml/nsMathMLmunderoverFrame.cpp700
-rw-r--r--layout/mathml/nsMathMLmunderoverFrame.h88
-rw-r--r--layout/mathml/nsMathMLsemanticsFrame.cpp117
-rw-r--r--layout/mathml/nsMathMLsemanticsFrame.h37
-rw-r--r--layout/mathml/operatorDictionary.xsl58
-rw-r--r--layout/mathml/tests/chrome.ini6
-rw-r--r--layout/mathml/tests/file_bug706406_iframe.html61
-rw-r--r--layout/mathml/tests/file_disabled_iframe.html33
-rw-r--r--layout/mathml/tests/mathml_example_test.html28
-rw-r--r--layout/mathml/tests/mochitest.ini14
-rw-r--r--layout/mathml/tests/test_bug330964.html98
-rw-r--r--layout/mathml/tests/test_bug553917.html221
-rw-r--r--layout/mathml/tests/test_bug706406.html19
-rw-r--r--layout/mathml/tests/test_bug827713-2.html123
-rw-r--r--layout/mathml/tests/test_disabled.html23
-rw-r--r--layout/mathml/tests/test_disabled_chrome.html51
-rw-r--r--layout/mathml/tests/test_opentype-axis-height.html58
-rw-r--r--layout/mathml/tests/test_opentype-limits.html68
-rwxr-xr-xlayout/mathml/updateOperatorDictionary.pl459
62 files changed, 17610 insertions, 0 deletions
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..be4b62dafd
--- /dev/null
+++ b/layout/mathml/mathfont.properties
@@ -0,0 +1,1377 @@
+# 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.prefix = lspace:0 rspace:0 # !
+operator.\u0021.postfix = lspace:0 rspace:0 # !
+operator.\u0021\u0021.postfix = lspace:0 rspace:0 # !!
+operator.\u0021\u003D.infix = lspace:5 rspace:5 # !=
+operator.\u0022.postfix = lspace:0 rspace:0 # quotation mark
+operator.\u0025.infix = lspace:3 rspace:3 # percent sign
+operator.\u0025.postfix = lspace:0 rspace:0 # 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 # (
+operator.\u0029.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # )
+operator.\u002A.infix = lspace:3 rspace:3 # *
+operator.\u002A\u002A.infix = lspace:3 rspace:3 # **
+operator.\u002A\u003D.infix = lspace:5 rspace:5 # *=
+operator.\u002B.infix = lspace:4 rspace:4 # +
+operator.\u002B.prefix = lspace:0 rspace:0 # +
+operator.\u002B\u002B.postfix = lspace:0 rspace:0 # ++
+operator.\u002B\u003D.infix = lspace:5 rspace:5 # +=
+operator.\u002C.infix = lspace:0 rspace:3 separator # ,
+operator.\u002D.infix = lspace:4 rspace:4 # -
+operator.\u002D.prefix = lspace:0 rspace:0 # -
+operator.\u002D\u002D.postfix = lspace:0 rspace:0 # --
+operator.\u002D\u003D.infix = lspace:5 rspace:5 # -=
+operator.\u002D\u003E.infix = lspace:5 rspace:5 # ->
+operator.\u002E.infix = lspace:3 rspace:3 # .
+operator.\u002F.infix = lspace:4 rspace:4 direction:vertical # solidus
+operator.\u002F\u002F.infix = lspace:5 rspace:5 # //
+operator.\u002F\u003D.infix = lspace:5 rspace:5 # /=
+operator.\u003A.infix = lspace:0 rspace:3 # :
+operator.\u003A\u003D.infix = lspace:5 rspace:5 # :=
+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:3 rspace:3 # <>
+operator.\u003D.infix = lspace:5 rspace:5 direction:horizontal # =
+operator.\u003D\u003D.infix = lspace:5 rspace:5 # ==
+operator.\u003E.infix = lspace:5 rspace:5 # >
+operator.\u003E\u003D.infix = lspace:5 rspace:5 # >=
+operator.\u003F.infix = lspace:3 rspace:3 # ?
+operator.\u0040.infix = lspace:3 rspace:3 # @
+operator.\u005B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # [
+operator.\u005C.infix = lspace:0 rspace:0 # reverse solidus
+operator.\u005D.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ]
+operator.\u005E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ^ circumflex accent
+operator.\u005E.infix = lspace:3 rspace:3 direction:horizontal # ^
+operator.\u005F.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # _ low line
+operator.\u005F.infix = lspace:0 rspace:0 direction:horizontal # _ low line
+operator.\u0060.postfix = lspace:0 rspace:0 accent # `
+operator.\u007B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # {
+operator.\u007C.infix = lspace:5 rspace:5 fence 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:5 rspace:5 fence direction:vertical # ||
+operator.\u007C\u007C.prefix = lspace:0 rspace:0 fence direction:vertical # multiple character operator: ||
+operator.\u007C\u007C.postfix = lspace:0 rspace:0 fence direction:vertical # multiple character operator: ||
+operator.\u007D.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # }
+operator.\u007E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ~ tilde
+operator.\u00A8.postfix = lspace:0 rspace:0 accent # ¨
+operator.\u00AC.prefix = lspace:0 rspace:0 # 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:0 # ±
+operator.\u00B2.postfix = lspace:0 rspace:0 # superscript two
+operator.\u00B3.postfix = lspace:0 rspace:0 # superscript three
+operator.\u00B4.postfix = lspace:0 rspace:0 accent # ´
+operator.\u00B7.infix = lspace:3 rspace:3 # ·
+operator.\u00B8.postfix = lspace:0 rspace:0 accent # ¸
+operator.\u00B9.postfix = lspace:0 rspace:0 # superscript one
+operator.\u00D7.infix = lspace:3 rspace:3 # 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.\u2016.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ‖ ‖
+operator.\u2016.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ‖ ‖
+operator.\u2018.prefix = lspace:0 rspace:0 fence # ‘
+operator.\u2019.postfix = lspace:0 rspace:0 fence # ’
+operator.\u201A.postfix = lspace:0 rspace:0 # single low-9 quotation mark
+operator.\u201B.postfix = lspace:0 rspace:0 # single high-reversed-9 quotation mark
+operator.\u201C.prefix = lspace:0 rspace:0 fence # “
+operator.\u201D.postfix = lspace:0 rspace:0 fence # ”
+operator.\u201E.postfix = lspace:0 rspace:0 # double low-9 quotation mark
+operator.\u201F.postfix = lspace:0 rspace:0 # double high-reversed-9 quotation mark
+operator.\u2022.infix = lspace:3 rspace:3 # bullet
+operator.\u2032.postfix = lspace:0 rspace:0 # prime
+operator.\u2033.postfix = lspace:0 rspace:0 # double prime
+operator.\u2034.postfix = lspace:0 rspace:0 # triple prime
+operator.\u2035.postfix = lspace:0 rspace:0 # reversed prime
+operator.\u2036.postfix = lspace:0 rspace:0 # reversed double prime
+operator.\u2037.postfix = lspace:0 rspace:0 # reversed triple prime
+operator.\u203E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # overline
+operator.\u2043.infix = lspace:3 rspace:3 # hyphen bullet
+operator.\u2044.infix = lspace:4 rspace:4 direction:vertical # fraction slash
+operator.\u2057.postfix = lspace:0 rspace:0 # quadruple prime
+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:3 rspace:0 # ⅅ
+operator.\u2146.prefix = lspace:3 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 direction:vertical # ↖
+operator.\u2197.infix = lspace:5 rspace:5 direction:vertical # ↗
+operator.\u2198.infix = lspace:5 rspace:5 direction:horizontal # ↘
+operator.\u2199.infix = lspace:5 rspace:5 direction:horizontal # ↙
+operator.\u219A.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow with stroke
+operator.\u219B.infix = lspace:5 rspace:5 stretchy 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 stretchy accent # left right arrow with stroke
+operator.\u21AF.infix = lspace:5 rspace:5 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 stretchy accent # leftwards double arrow with stroke
+operator.\u21CE.infix = lspace:5 rspace:5 stretchy accent # left right double arrow with stroke
+operator.\u21CF.infix = lspace:5 rspace:5 stretchy 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.\u21D6.infix = lspace:5 rspace:5 # north west double arrow
+operator.\u21D7.infix = lspace:5 rspace:5 # north east double arrow
+operator.\u21D8.infix = lspace:5 rspace:5 # south east double arrow
+operator.\u21D9.infix = lspace:5 rspace:5 # south west double arrow
+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 stretchy # upwards arrow with double stroke
+operator.\u21DF.infix = lspace:5 rspace:5 stretchy # 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 stretchy 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 stretchy accent # leftwards arrow with vertical stroke
+operator.\u21F8.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with vertical stroke
+operator.\u21F9.infix = lspace:5 rspace:5 stretchy accent # left right arrow with vertical stroke
+operator.\u21FA.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow with double vertical stroke
+operator.\u21FB.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with double vertical stroke
+operator.\u21FC.infix = lspace:5 rspace:5 stretchy 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:0 rspace:0 # ∀
+operator.\u2201.prefix = lspace:0 rspace:0 # complement
+operator.\u2202.prefix = lspace:3 rspace:0 # ∂
+operator.\u2203.prefix = lspace:0 rspace:0 # ∃
+operator.\u2204.prefix = lspace:0 rspace:0 # ∄
+operator.\u2206.infix = lspace:0 rspace:0 # increment
+operator.\u2207.prefix = lspace:0 rspace:0 # ∇
+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.\u220F.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ∏
+operator.\u2210.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ∐
+operator.\u2211.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ∑
+operator.\u2212.infix = lspace:4 rspace:4 # official Unicode minus sign
+operator.\u2212.prefix = lspace:0 rspace:0 # official Unicode minus sign
+operator.\u2213.infix = lspace:4 rspace:4 # ∓
+operator.\u2213.prefix = lspace:0 rspace:0 # ∓
+operator.\u2214.infix = lspace:4 rspace:4 # dot plus
+operator.\u2215.infix = lspace:4 rspace:4 direction:vertical # division slash
+operator.\u2216.infix = lspace:4 rspace:4 direction:vertical # set minus
+operator.\u2217.infix = lspace:3 rspace:3 # asterisk operator
+operator.\u2218.infix = lspace:3 rspace:3 # ∘
+operator.\u2219.infix = lspace:3 rspace:3 # bullet operator
+operator.\u221A.prefix = lspace:3 rspace:0 direction:vertical # √
+operator.\u221B.prefix = lspace:3 rspace:0 # cube root
+operator.\u221C.prefix = lspace:3 rspace:0 # fourth root
+operator.\u221D.infix = lspace:5 rspace:5 # ∝
+operator.\u221F.prefix = lspace:0 rspace:0 # 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:3 rspace:3 largeop symmetric direction:vertical # ∫
+operator.\u222C.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # double integral
+operator.\u222D.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # triple integral
+operator.\u222E.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # ∮
+operator.\u222F.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # ∯
+operator.\u2230.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # volume integral
+operator.\u2231.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # clockwise integral
+operator.\u2232.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # ∲
+operator.\u2233.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # &CounterClockwiseContourIntegral;
+operator.\u2234.prefix = lspace:0 rspace:0 # therefore
+operator.\u2235.prefix = lspace:0 rspace:0 # because
+operator.\u2236.infix = lspace:4 rspace:4 # 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:5 rspace:5 # geometric proportion
+operator.\u223B.infix = lspace:5 rspace:5 # homothetic
+operator.\u223C.infix = lspace:5 rspace:5 # ∼
+operator.\u223C.prefix = lspace:0 rspace:0 # tilde operator
+operator.\u223D.infix = lspace:5 rspace:5 # reversed tilde
+operator.\u223E.infix = lspace:5 rspace:5 # inverted lazy s
+operator.\u2240.infix = lspace:3 rspace:3 # ≀
+operator.\u2241.infix = lspace:5 rspace:5 # ≁
+operator.\u2242.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.\u224F.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.\u225B.infix = lspace:5 rspace:5 # star equals
+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.\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.\u226B.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.\u2280.infix = lspace:5 rspace:5 # ⊀
+operator.\u2281.infix = lspace:5 rspace:5 # ⊁
+operator.\u2282.infix = lspace:5 rspace:5 # ⊂
+operator.\u2283.infix = lspace:5 rspace:5 # ⊃
+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.\u2290.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 direction:vertical # ⊕
+operator.\u2296.infix = lspace:4 rspace:4 direction:vertical # ⊖
+operator.\u2297.infix = lspace:3 rspace:3 direction:vertical # ⊗
+operator.\u2298.infix = lspace:4 rspace:4 # circled division slash
+operator.\u2299.infix = lspace:3 rspace:3 direction:vertical # ⊙
+operator.\u229A.infix = lspace:3 rspace:3 # circled ring operator
+operator.\u229B.infix = lspace:3 rspace:3 # circled asterisk operator
+operator.\u229C.infix = lspace:5 rspace:5 # 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:3 rspace:3 # squared times
+operator.\u22A1.infix = lspace:3 rspace:3 # squared dot operator
+operator.\u22A2.infix = lspace:5 rspace:5 # ⊢
+operator.\u22A3.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.\u22BA.infix = lspace:3 rspace:3 # 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.prefix = lspace:0 rspace:0 # right angle with arc
+operator.\u22BF.prefix = lspace:0 rspace:0 # right triangle
+operator.\u22C0.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⋀
+operator.\u22C1.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⋁
+operator.\u22C2.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⋂
+operator.\u22C3.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⋃
+operator.\u22C4.infix = lspace:3 rspace:3 # ⋄
+operator.\u22C5.infix = lspace:3 rspace:3 # ċ
+operator.\u22C6.infix = lspace:3 rspace:3 # ⋆
+operator.\u22C7.infix = lspace:3 rspace:3 # division times
+operator.\u22C8.infix = lspace:5 rspace:5 # bowtie
+operator.\u22C9.infix = lspace:3 rspace:3 # left normal factor semidirect product
+operator.\u22CA.infix = lspace:3 rspace:3 # right normal factor semidirect product
+operator.\u22CB.infix = lspace:3 rspace:3 # left semidirect product
+operator.\u22CC.infix = lspace:3 rspace:3 # 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.\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.\u2301.infix = lspace:5 rspace:5 # electric arrow
+operator.\u2305.infix = lspace:3 rspace:3 # projective
+operator.\u2306.infix = lspace:3 rspace:3 # perspective
+operator.\u2308.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⌈
+operator.\u2309.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⌉
+operator.\u230A.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⌊
+operator.\u230B.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⌋
+operator.\u2310.prefix = lspace:0 rspace:0 # reversed not sign
+operator.\u2319.prefix = lspace:0 rspace:0 # turned not sign
+operator.\u2322.postfix = lspace:0 rspace:0 stretchy # frown
+operator.\u2323.postfix = lspace:0 rspace:0 stretchy # smile
+operator.\u2329.prefix = lspace:0 rspace:0 stretchy fence symmetric # left-pointing angle bracket
+operator.\u232A.postfix = lspace:0 rspace:0 stretchy fence symmetric # right-pointing angle bracket
+operator.\u237C.infix = lspace:5 rspace:5 # right angle with downwards zigzag arrow
+operator.\u238B.infix = lspace:5 rspace:5 # broken circle with northwest arrow
+operator.\u23B4.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⎴
+operator.\u23B5.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ⎵
+operator.\u23CD.postfix = lspace:0 rspace:0 # square foot
+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.\u2772.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # light left tortoise shell bracket ornament
+operator.\u2773.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # light right tortoise shell bracket ornament
+operator.\u2794.infix = lspace:5 rspace:5 stretchy # heavy wide-headed rightwards arrow
+operator.\u2795.infix = lspace:4 rspace:4 # heavy plus sign
+operator.\u2795.prefix = lspace:0 rspace:0 # heavy plus sign
+operator.\u2796.infix = lspace:4 rspace:4 # heavy minus sign
+operator.\u2796.prefix = lspace:0 rspace:0 # heavy minus sign
+operator.\u2797.infix = lspace:4 rspace:4 # heavy division sign
+operator.\u2798.infix = lspace:5 rspace:5 # heavy south east arrow
+operator.\u2799.infix = lspace:5 rspace:5 stretchy # heavy rightwards arrow
+operator.\u279A.infix = lspace:5 rspace:5 # heavy north east arrow
+operator.\u279B.infix = lspace:5 rspace:5 stretchy # drafting point rightwards arrow
+operator.\u279C.infix = lspace:5 rspace:5 stretchy # heavy round-tipped rightwards arrow
+operator.\u279D.infix = lspace:5 rspace:5 stretchy # triangle-headed rightwards arrow
+operator.\u279E.infix = lspace:5 rspace:5 stretchy # heavy triangle-headed rightwards arrow
+operator.\u279F.infix = lspace:5 rspace:5 stretchy # dashed triangle-headed rightwards arrow
+operator.\u27A0.infix = lspace:5 rspace:5 stretchy # heavy dashed triangle-headed rightwards arrow
+operator.\u27A1.infix = lspace:5 rspace:5 stretchy # black rightwards arrow
+operator.\u27A5.infix = lspace:5 rspace:5 stretchy # heavy black curved downwards and rightwards arrow
+operator.\u27A6.infix = lspace:5 rspace:5 stretchy # heavy black curved upwards and rightwards arrow
+operator.\u27A7.infix = lspace:5 rspace:5 # squat black rightwards arrow
+operator.\u27A8.infix = lspace:5 rspace:5 stretchy # heavy concave-pointed black rightwards arrow
+operator.\u27A9.infix = lspace:5 rspace:5 stretchy # right-shaded white rightwards arrow
+operator.\u27AA.infix = lspace:5 rspace:5 stretchy # left-shaded white rightwards arrow
+operator.\u27AB.infix = lspace:5 rspace:5 stretchy # back-tilted shadowed white rightwards arrow
+operator.\u27AC.infix = lspace:5 rspace:5 stretchy # front-tilted shadowed white rightwards arrow
+operator.\u27AD.infix = lspace:5 rspace:5 stretchy # heavy lower right-shadowed white rightwards arrow
+operator.\u27AE.infix = lspace:5 rspace:5 stretchy # heavy upper right-shadowed white rightwards arrow
+operator.\u27AF.infix = lspace:5 rspace:5 stretchy # notched lower right-shadowed white rightwards arrow
+operator.\u27B1.infix = lspace:5 rspace:5 stretchy # notched upper right-shadowed white rightwards arrow
+operator.\u27B2.infix = lspace:5 rspace:5 # circled heavy white rightwards arrow
+operator.\u27B3.infix = lspace:5 rspace:5 stretchy # white-feathered rightwards arrow
+operator.\u27B4.infix = lspace:5 rspace:5 # black-feathered south east arrow
+operator.\u27B5.infix = lspace:5 rspace:5 stretchy # black-feathered rightwards arrow
+operator.\u27B6.infix = lspace:5 rspace:5 # black-feathered north east arrow
+operator.\u27B7.infix = lspace:5 rspace:5 # heavy black-feathered south east arrow
+operator.\u27B8.infix = lspace:5 rspace:5 stretchy # heavy black-feathered rightwards arrow
+operator.\u27B9.infix = lspace:5 rspace:5 # heavy black-feathered north east arrow
+operator.\u27BA.infix = lspace:5 rspace:5 stretchy # teardrop-barbed rightwards arrow
+operator.\u27BB.infix = lspace:5 rspace:5 stretchy # heavy teardrop-shanked rightwards arrow
+operator.\u27BC.infix = lspace:5 rspace:5 stretchy # wedge-tailed rightwards arrow
+operator.\u27BD.infix = lspace:5 rspace:5 stretchy # heavy wedge-tailed rightwards arrow
+operator.\u27BE.infix = lspace:5 rspace:5 stretchy # open-outlined rightwards arrow
+operator.\u27C0.prefix = lspace:0 rspace:0 # three dimensional angle
+operator.\u27C2.infix = lspace:5 rspace:5 # perpendicular
+operator.\u27CB.infix = lspace:3 rspace:3 # mathematical rising diagonal
+operator.\u27CD.infix = lspace:3 rspace:3 # mathematical falling diagonal
+operator.\u27E6.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⟦
+operator.\u27E7.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⟧
+operator.\u27E8.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⟨
+operator.\u27E9.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ⟩
+operator.\u27EA.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical left double angle bracket
+operator.\u27EB.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical right double angle bracket
+operator.\u27EC.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical left white tortoise shell bracket
+operator.\u27ED.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical right white tortoise shell bracket
+operator.\u27EE.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical left flattened parenthesis
+operator.\u27EF.postfix = lspace:0 rspace:0 stretchy fence symmetric 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.\u27F2.infix = lspace:5 rspace:5 # anticlockwise gapped circle arrow
+operator.\u27F3.infix = lspace:5 rspace:5 # clockwise gapped circle arrow
+operator.\u27F4.infix = lspace:5 rspace:5 stretchy # right arrow with circled plus
+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 stretchy accent # rightwards two-headed arrow with vertical stroke
+operator.\u2901.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with double vertical stroke
+operator.\u2902.infix = lspace:5 rspace:5 stretchy accent # leftwards double arrow with vertical stroke
+operator.\u2903.infix = lspace:5 rspace:5 stretchy accent # rightwards double arrow with vertical stroke
+operator.\u2904.infix = lspace:5 rspace:5 stretchy accent # left right double arrow with vertical stroke
+operator.\u2905.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow from bar
+operator.\u2906.infix = lspace:5 rspace:5 stretchy accent # leftwards double arrow from bar
+operator.\u2907.infix = lspace:5 rspace:5 stretchy accent # rightwards double arrow from bar
+operator.\u2908.infix = lspace:5 rspace:5 stretchy # downwards arrow with horizontal stroke
+operator.\u2909.infix = lspace:5 rspace:5 stretchy # 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 stretchy 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 stretchy accent # rightwards arrow with tail with vertical stroke
+operator.\u2915.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with tail with double vertical stroke
+operator.\u2916.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with tail
+operator.\u2917.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with tail with vertical stroke
+operator.\u2918.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with tail with double vertical stroke
+operator.\u2919.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow-tail
+operator.\u291A.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow-tail
+operator.\u291B.infix = lspace:5 rspace:5 stretchy accent # leftwards double arrow-tail
+operator.\u291C.infix = lspace:5 rspace:5 stretchy accent # rightwards double arrow-tail
+operator.\u291D.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow to black diamond
+operator.\u291E.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow to black diamond
+operator.\u291F.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow from bar to black diamond
+operator.\u2920.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow from bar to black diamond
+operator.\u2921.infix = lspace:5 rspace:5 # north west and south east arrow
+operator.\u2922.infix = lspace:5 rspace:5 # north east and south west arrow
+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 stretchy # arrow pointing rightwards then curving upwards
+operator.\u2935.infix = lspace:5 rspace:5 stretchy # arrow pointing rightwards then curving downwards
+operator.\u2936.infix = lspace:5 rspace:5 stretchy # arrow pointing downwards then curving leftwards
+operator.\u2937.infix = lspace:5 rspace:5 stretchy # 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 stretchy accent # rightwards arrow above short leftwards arrow
+operator.\u2943.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow above short rightwards arrow
+operator.\u2944.infix = lspace:5 rspace:5 stretchy accent # short rightwards arrow above leftwards arrow
+operator.\u2945.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with plus below
+operator.\u2946.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow with plus below
+operator.\u2947.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow through x
+operator.\u2948.infix = lspace:5 rspace:5 stretchy accent # left right arrow through small circle
+operator.\u2949.infix = lspace:5 rspace:5 stretchy # upwards two-headed arrow from small circle
+operator.\u294A.infix = lspace:5 rspace:5 stretchy accent # left barb up right barb down harpoon
+operator.\u294B.infix = lspace:5 rspace:5 stretchy accent # left barb down right barb up harpoon
+operator.\u294C.infix = lspace:5 rspace:5 stretchy # up barb right down barb left harpoon
+operator.\u294D.infix = lspace:5 rspace:5 stretchy # 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 stretchy accent # leftwards harpoon with barb up above leftwards harpoon with barb down
+operator.\u2963.infix = lspace:5 rspace:5 stretchy # upwards harpoon with barb left beside upwards harpoon with barb right
+operator.\u2964.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb up above rightwards harpoon with barb down
+operator.\u2965.infix = lspace:5 rspace:5 stretchy # downwards harpoon with barb left beside downwards harpoon with barb right
+operator.\u2966.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb up above rightwards harpoon with barb up
+operator.\u2967.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb down above rightwards harpoon with barb down
+operator.\u2968.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb up above leftwards harpoon with barb up
+operator.\u2969.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb down above leftwards harpoon with barb down
+operator.\u296A.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb up above long dash
+operator.\u296B.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb down below long dash
+operator.\u296C.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb up above long dash
+operator.\u296D.infix = lspace:5 rspace:5 stretchy 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 stretchy accent # ⥰
+operator.\u2971.infix = lspace:5 rspace:5 stretchy accent # equals sign above rightwards arrow
+operator.\u2972.infix = lspace:5 rspace:5 stretchy accent # tilde operator above rightwards arrow
+operator.\u2973.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow above tilde operator
+operator.\u2974.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow above tilde operator
+operator.\u2975.infix = lspace:5 rspace:5 stretchy 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 stretchy accent # left fish tail
+operator.\u297D.infix = lspace:5 rspace:5 stretchy accent # right fish tail
+operator.\u297E.infix = lspace:5 rspace:5 stretchy # up fish tail
+operator.\u297F.infix = lspace:5 rspace:5 stretchy # down fish tail
+operator.\u2980.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # triple direction:vertical bar delimiter
+operator.\u2980.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # triple direction:vertical bar delimiter
+operator.\u2981.infix = lspace:5 rspace:5 # z notation spot
+operator.\u2982.infix = lspace:5 rspace:5 # z notation type colon
+operator.\u2983.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left white curly bracket
+operator.\u2984.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right white curly bracket
+operator.\u2985.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left white parenthesis
+operator.\u2986.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right white parenthesis
+operator.\u2987.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # z notation left image bracket
+operator.\u2988.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # z notation right image bracket
+operator.\u2989.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # z notation left binding bracket
+operator.\u298A.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # z notation right binding bracket
+operator.\u298B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left square bracket with underbar
+operator.\u298C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right square bracket with underbar
+operator.\u298D.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left square bracket with tick in top corner
+operator.\u298E.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right square bracket with tick in bottom corner
+operator.\u298F.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left square bracket with tick in bottom corner
+operator.\u2990.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right square bracket with tick in top corner
+operator.\u2991.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left angle bracket with dot
+operator.\u2992.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right angle bracket with dot
+operator.\u2993.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left arc less-than bracket
+operator.\u2994.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right arc greater-than bracket
+operator.\u2995.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # double left arc greater-than bracket
+operator.\u2996.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # double right arc less-than bracket
+operator.\u2997.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left black tortoise shell bracket
+operator.\u2998.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right black tortoise shell bracket
+operator.\u2999.prefix = lspace:0 rspace:0 stretchy fence symmetric # dotted fence
+operator.\u2999.postfix = lspace:0 rspace:0 stretchy fence symmetric # dotted fence
+operator.\u299B.prefix = lspace:0 rspace:0 # measured angle opening left
+operator.\u299C.prefix = lspace:0 rspace:0 # right angle variant with square
+operator.\u299D.prefix = lspace:0 rspace:0 # measured right angle with dot
+operator.\u299E.prefix = lspace:0 rspace:0 # angle with s inside
+operator.\u299F.prefix = lspace:0 rspace:0 # acute angle
+operator.\u29A0.prefix = lspace:0 rspace:0 # spherical angle opening left
+operator.\u29A1.prefix = lspace:0 rspace:0 # spherical angle opening up
+operator.\u29A2.prefix = lspace:0 rspace:0 # turned angle
+operator.\u29A3.prefix = lspace:0 rspace:0 # reversed angle
+operator.\u29A4.prefix = lspace:0 rspace:0 # angle with underbar
+operator.\u29A5.prefix = lspace:0 rspace:0 # reversed angle with underbar
+operator.\u29A6.prefix = lspace:0 rspace:0 # oblique angle opening up
+operator.\u29A7.prefix = lspace:0 rspace:0 # oblique angle opening down
+operator.\u29A8.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing up and right
+operator.\u29A9.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing up and left
+operator.\u29AA.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing down and right
+operator.\u29AB.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing down and left
+operator.\u29AC.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing right and up
+operator.\u29AD.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing left and up
+operator.\u29AE.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing right and down
+operator.\u29AF.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing left and down
+operator.\u29B6.infix = lspace:5 rspace:5 # circled vertical bar
+operator.\u29B7.infix = lspace:5 rspace:5 # circled parallel
+operator.\u29B8.infix = lspace:4 rspace:4 # circled reverse solidus
+operator.\u29B9.infix = lspace:5 rspace:5 # circled perpendicular
+operator.\u29BC.infix = lspace:4 rspace:4 # circled anticlockwise-rotated division sign
+operator.\u29C0.infix = lspace:5 rspace:5 # circled less-than
+operator.\u29C1.infix = lspace:5 rspace:5 # circled greater-than
+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:3 rspace:3 # squared asterisk
+operator.\u29C7.infix = lspace:3 rspace:3 # squared small circle
+operator.\u29C8.infix = lspace:3 rspace:3 # squared square
+operator.\u29CE.infix = lspace:5 rspace:5 # right triangle above left triangle
+operator.\u29CF.infix = lspace:5 rspace:5 # ⧏
+operator.\u29D0.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:3 rspace:3 # times with left half black
+operator.\u29D5.infix = lspace:3 rspace:3 # times with right half black
+operator.\u29D6.infix = lspace:3 rspace:3 # white hourglass
+operator.\u29D7.infix = lspace:3 rspace:3 # black hourglass
+operator.\u29D8.prefix = lspace:0 rspace:0 stretchy fence symmetric # left wiggly fence
+operator.\u29D9.postfix = lspace:0 rspace:0 stretchy fence symmetric # right wiggly fence
+operator.\u29DA.prefix = lspace:0 rspace:0 stretchy fence symmetric # left double wiggly fence
+operator.\u29DB.postfix = lspace:0 rspace:0 stretchy fence symmetric # right double wiggly fence
+operator.\u29DF.infix = lspace:5 rspace:5 # double-ended multimap
+operator.\u29E1.infix = lspace:5 rspace:5 # increases as
+operator.\u29E2.infix = lspace:3 rspace:3 # 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.\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:4 rspace:4 # big solidus
+operator.\u29F9.infix = lspace:4 rspace:4 # big reverse solidus
+operator.\u29FA.infix = lspace:4 rspace:4 # double plus
+operator.\u29FB.infix = lspace:4 rspace:4 # triple plus
+operator.\u29FC.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left-pointing curved angle bracket
+operator.\u29FD.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right-pointing curved angle bracket
+operator.\u2A00.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⨀
+operator.\u2A01.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⨁
+operator.\u2A02.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⨂
+operator.\u2A03.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # n-ary union operator with dot
+operator.\u2A04.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⨄
+operator.\u2A05.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # n-ary square intersection operator
+operator.\u2A06.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # ⨆
+operator.\u2A07.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # two logical and operator
+operator.\u2A08.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # two logical or operator
+operator.\u2A09.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # n-ary times operator
+operator.\u2A0A.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # modulo two sum
+operator.\u2A0B.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # summation with integral
+operator.\u2A0C.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # quadruple integral operator
+operator.\u2A0D.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # finite part integral
+operator.\u2A0E.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with double stroke
+operator.\u2A0F.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral average with slash
+operator.\u2A10.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # circulation function
+operator.\u2A11.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # anticlockwise integration
+operator.\u2A12.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # line integration with rectangular path around pole
+operator.\u2A13.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # line integration with semicircular path around pole
+operator.\u2A14.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # line integration not including the pole
+operator.\u2A15.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral around a point operator
+operator.\u2A16.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # quaternion integral operator
+operator.\u2A17.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with leftwards arrow with hook
+operator.\u2A18.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with times sign
+operator.\u2A19.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with intersection
+operator.\u2A1A.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with union
+operator.\u2A1B.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with overbar
+operator.\u2A1C.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with underbar
+operator.\u2A1D.infix = lspace:3 rspace:3 direction:vertical # join
+operator.\u2A1D.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # join
+operator.\u2A1E.infix = lspace:3 rspace:3 direction:vertical # large left triangle operator
+operator.\u2A1E.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # large left triangle operator
+operator.\u2A1F.infix = lspace:4 rspace:4 # z notation schema composition
+operator.\u2A20.infix = lspace:4 rspace:4 # z notation schema piping
+operator.\u2A21.infix = lspace:4 rspace:4 # 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:3 rspace:3 # ⨯
+operator.\u2A30.infix = lspace:3 rspace:3 # multiplication sign with dot above
+operator.\u2A31.infix = lspace:3 rspace:3 # multiplication sign with underbar
+operator.\u2A32.infix = lspace:3 rspace:3 # semidirect product with bottom closed
+operator.\u2A33.infix = lspace:3 rspace:3 # smash product
+operator.\u2A34.infix = lspace:3 rspace:3 # multiplication sign in left half circle
+operator.\u2A35.infix = lspace:3 rspace:3 # multiplication sign in right half circle
+operator.\u2A36.infix = lspace:3 rspace:3 # circled multiplication sign with circumflex accent
+operator.\u2A37.infix = lspace:3 rspace:3 # 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:3 rspace:3 # multiplication sign in triangle
+operator.\u2A3C.infix = lspace:3 rspace:3 # interior product
+operator.\u2A3D.infix = lspace:3 rspace:3 # righthand interior product
+operator.\u2A3E.infix = lspace:4 rspace:4 # z notation relational composition
+operator.\u2A3F.infix = lspace:3 rspace:3 # 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:3 rspace:3 # 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:4 rspace:4 # 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:3 rspace:3 # z notation domain antirestriction
+operator.\u2A65.infix = lspace:3 rspace:3 # 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:5 rspace:5 # equals sign above plus sign
+operator.\u2A72.infix = lspace:5 rspace:5 # 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.\u2A7E.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.\u2AA2.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.\u2AB0.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:4 rspace:4 # transversal intersection
+operator.\u2ADC.infix = lspace:3 rspace:3 # forking
+operator.\u2ADD.infix = lspace:3 rspace:3 # nonforking
+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.prefix = lspace:0 rspace:0 # ⫬
+operator.\u2AED.prefix = lspace:0 rspace:0 # reversed double stroke not sign
+operator.\u2AEE.infix = lspace:5 rspace:5 # does not divide with reversed negation slash
+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:5 rspace:5 # triple vertical bar binary relation
+operator.\u2AF5.infix = lspace:5 rspace:5 # 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:3 rspace:3 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:3 rspace:3 largeop movablelimits symmetric direction:vertical # n-ary white vertical bar
+operator.\u2B00.infix = lspace:5 rspace:5 # north east white arrow
+operator.\u2B01.infix = lspace:5 rspace:5 # north west white arrow
+operator.\u2B02.infix = lspace:5 rspace:5 # south east white arrow
+operator.\u2B03.infix = lspace:5 rspace:5 # south west white arrow
+operator.\u2B04.infix = lspace:5 rspace:5 stretchy # left right white arrow
+operator.\u2B05.infix = lspace:5 rspace:5 stretchy # leftwards black arrow
+operator.\u2B06.infix = lspace:5 rspace:5 stretchy # upwards black arrow
+operator.\u2B07.infix = lspace:5 rspace:5 stretchy # downwards black arrow
+operator.\u2B08.infix = lspace:5 rspace:5 # north east black arrow
+operator.\u2B09.infix = lspace:5 rspace:5 # north west black arrow
+operator.\u2B0A.infix = lspace:5 rspace:5 # south east black arrow
+operator.\u2B0B.infix = lspace:5 rspace:5 # south west black arrow
+operator.\u2B0C.infix = lspace:5 rspace:5 stretchy # left right black arrow
+operator.\u2B0D.infix = lspace:5 rspace:5 stretchy # up down black arrow
+operator.\u2B0E.infix = lspace:5 rspace:5 stretchy # rightwards arrow with tip downwards
+operator.\u2B0F.infix = lspace:5 rspace:5 stretchy # rightwards arrow with tip upwards
+operator.\u2B10.infix = lspace:5 rspace:5 stretchy # leftwards arrow with tip downwards
+operator.\u2B11.infix = lspace:5 rspace:5 stretchy # leftwards arrow with tip upwards
+operator.\u2B30.infix = lspace:5 rspace:5 stretchy # left arrow with small circle
+operator.\u2B31.infix = lspace:5 rspace:5 stretchy # three leftwards arrows
+operator.\u2B32.infix = lspace:5 rspace:5 stretchy # left arrow with circled plus
+operator.\u2B33.infix = lspace:5 rspace:5 stretchy # long leftwards squiggle arrow
+operator.\u2B34.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with vertical stroke
+operator.\u2B35.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with double vertical stroke
+operator.\u2B36.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow from bar
+operator.\u2B37.infix = lspace:5 rspace:5 stretchy # leftwards two-headed triple dash arrow
+operator.\u2B38.infix = lspace:5 rspace:5 stretchy # leftwards arrow with dotted stem
+operator.\u2B39.infix = lspace:5 rspace:5 stretchy # leftwards arrow with tail with vertical stroke
+operator.\u2B3A.infix = lspace:5 rspace:5 stretchy # leftwards arrow with tail with double vertical stroke
+operator.\u2B3B.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with tail
+operator.\u2B3C.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with tail with vertical stroke
+operator.\u2B3D.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with tail with double vertical stroke
+operator.\u2B3E.infix = lspace:5 rspace:5 stretchy # leftwards arrow through x
+operator.\u2B3F.infix = lspace:5 rspace:5 # wave arrow pointing directly left
+operator.\u2B40.infix = lspace:5 rspace:5 stretchy # equals sign above leftwards arrow
+operator.\u2B41.infix = lspace:5 rspace:5 stretchy # reverse tilde operator above leftwards arrow
+operator.\u2B42.infix = lspace:5 rspace:5 stretchy # leftwards arrow above reverse almost equal to
+operator.\u2B43.infix = lspace:5 rspace:5 stretchy # rightwards arrow through greater-than
+operator.\u2B44.infix = lspace:5 rspace:5 stretchy # rightwards arrow through superset
+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
+operator.\u2B47.infix = lspace:5 rspace:5 stretchy # reverse tilde operator above rightwards arrow
+operator.\u2B48.infix = lspace:5 rspace:5 stretchy # rightwards arrow above reverse almost equal to
+operator.\u2B49.infix = lspace:5 rspace:5 stretchy # tilde operator above leftwards arrow
+operator.\u2B4A.infix = lspace:5 rspace:5 stretchy # leftwards arrow above almost equal to
+operator.\u2B4B.infix = lspace:5 rspace:5 stretchy # leftwards arrow above reverse tilde operator
+operator.\u2B4C.infix = lspace:5 rspace:5 stretchy # rightwards arrow above reverse tilde operator
+operator.\u2B4D.infix = lspace:5 rspace:5 # downwards triangle-headed zigzag arrow
+operator.\u2B4E.infix = lspace:5 rspace:5 # short slanted north arrow
+operator.\u2B4F.infix = lspace:5 rspace:5 # short backslanted south arrow
+operator.\u2B5A.infix = lspace:5 rspace:5 # slanted north arrow with hooked head
+operator.\u2B5B.infix = lspace:5 rspace:5 # backslanted south arrow with hooked tail
+operator.\u2B5C.infix = lspace:5 rspace:5 # slanted north arrow with horizontal tail
+operator.\u2B5D.infix = lspace:5 rspace:5 # backslanted south arrow with horizontal tail
+operator.\u2B5E.infix = lspace:5 rspace:5 # bent arrow pointing downwards then north east
+operator.\u2B5F.infix = lspace:5 rspace:5 # short bent arrow pointing downwards then north east
+operator.\u2B60.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow
+operator.\u2B61.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow
+operator.\u2B62.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow
+operator.\u2B63.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow
+operator.\u2B64.infix = lspace:5 rspace:5 stretchy # left right triangle-headed arrow
+operator.\u2B65.infix = lspace:5 rspace:5 stretchy # up down triangle-headed arrow
+operator.\u2B66.infix = lspace:5 rspace:5 # north west triangle-headed arrow
+operator.\u2B67.infix = lspace:5 rspace:5 # north east triangle-headed arrow
+operator.\u2B68.infix = lspace:5 rspace:5 # south east triangle-headed arrow
+operator.\u2B69.infix = lspace:5 rspace:5 # south west triangle-headed arrow
+operator.\u2B6A.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed dashed arrow
+operator.\u2B6B.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed dashed arrow
+operator.\u2B6C.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed dashed arrow
+operator.\u2B6D.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed dashed arrow
+operator.\u2B6E.infix = lspace:5 rspace:5 # clockwise triangle-headed open circle arrow
+operator.\u2B6F.infix = lspace:5 rspace:5 # anticlockwise triangle-headed open circle arrow
+operator.\u2B70.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow to bar
+operator.\u2B71.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow to bar
+operator.\u2B72.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow to bar
+operator.\u2B73.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow to bar
+operator.\u2B76.infix = lspace:5 rspace:5 # north west triangle-headed arrow to bar
+operator.\u2B77.infix = lspace:5 rspace:5 # north east triangle-headed arrow to bar
+operator.\u2B78.infix = lspace:5 rspace:5 # south east triangle-headed arrow to bar
+operator.\u2B79.infix = lspace:5 rspace:5 # south west triangle-headed arrow to bar
+operator.\u2B7A.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow with double horizontal stroke
+operator.\u2B7B.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow with double horizontal stroke
+operator.\u2B7C.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow with double horizontal stroke
+operator.\u2B7D.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow with double horizontal stroke
+operator.\u2B80.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow over rightwards triangle-headed arrow
+operator.\u2B81.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow leftwards of downwards triangle-headed arrow
+operator.\u2B82.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow over leftwards triangle-headed arrow
+operator.\u2B83.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow leftwards of upwards triangle-headed arrow
+operator.\u2B84.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed paired arrows
+operator.\u2B85.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed paired arrows
+operator.\u2B86.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed paired arrows
+operator.\u2B87.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed paired arrows
+operator.\u2B88.infix = lspace:5 rspace:5 # leftwards black circled white arrow
+operator.\u2B89.infix = lspace:5 rspace:5 # upwards black circled white arrow
+operator.\u2B8A.infix = lspace:5 rspace:5 # rightwards black circled white arrow
+operator.\u2B8B.infix = lspace:5 rspace:5 # downwards black circled white arrow
+operator.\u2B8C.infix = lspace:5 rspace:5 # anticlockwise triangle-headed right u-shaped arrow
+operator.\u2B8D.infix = lspace:5 rspace:5 # anticlockwise triangle-headed bottom u-shaped arrow
+operator.\u2B8E.infix = lspace:5 rspace:5 # anticlockwise triangle-headed left u-shaped arrow
+operator.\u2B8F.infix = lspace:5 rspace:5 # anticlockwise triangle-headed top u-shaped arrow
+operator.\u2B94.infix = lspace:5 rspace:5 # four corner arrows circling anticlockwise
+operator.\u2B95.infix = lspace:5 rspace:5 stretchy # rightwards black arrow
+operator.\u2BA0.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow with long tip leftwards
+operator.\u2BA1.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow with long tip rightwards
+operator.\u2BA2.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow with long tip leftwards
+operator.\u2BA3.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow with long tip rightwards
+operator.\u2BA4.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow with long tip upwards
+operator.\u2BA5.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow with long tip upwards
+operator.\u2BA6.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow with long tip downwards
+operator.\u2BA7.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow with long tip downwards
+operator.\u2BA8.infix = lspace:5 rspace:5 stretchy # black curved downwards and leftwards arrow
+operator.\u2BA9.infix = lspace:5 rspace:5 stretchy # black curved downwards and rightwards arrow
+operator.\u2BAA.infix = lspace:5 rspace:5 stretchy # black curved upwards and leftwards arrow
+operator.\u2BAB.infix = lspace:5 rspace:5 stretchy # black curved upwards and rightwards arrow
+operator.\u2BAC.infix = lspace:5 rspace:5 stretchy # black curved leftwards and upwards arrow
+operator.\u2BAD.infix = lspace:5 rspace:5 stretchy # black curved rightwards and upwards arrow
+operator.\u2BAE.infix = lspace:5 rspace:5 stretchy # black curved leftwards and downwards arrow
+operator.\u2BAF.infix = lspace:5 rspace:5 stretchy # black curved rightwards and downwards arrow
+operator.\u2BB0.infix = lspace:5 rspace:5 # ribbon arrow down left
+operator.\u2BB1.infix = lspace:5 rspace:5 # ribbon arrow down right
+operator.\u2BB2.infix = lspace:5 rspace:5 # ribbon arrow up left
+operator.\u2BB3.infix = lspace:5 rspace:5 # ribbon arrow up right
+operator.\u2BB4.infix = lspace:5 rspace:5 # ribbon arrow left up
+operator.\u2BB5.infix = lspace:5 rspace:5 # ribbon arrow right up
+operator.\u2BB6.infix = lspace:5 rspace:5 # ribbon arrow left down
+operator.\u2BB7.infix = lspace:5 rspace:5 # ribbon arrow right down
+operator.\u2BB8.infix = lspace:5 rspace:5 stretchy # upwards white arrow from bar with horizontal bar
+operator.\u2BD1.infix = lspace:5 rspace:5 # uncertainty sign
+
+# Entries below are not part of the official MathML dictionary
+
+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.\u002E\u002E.postfix = lspace:0 rspace:0 # ..
+operator.\u003B.postfix = lspace:0 rspace:0 separator # ;
+operator.\u007E.infix = lspace:2 rspace:2 stretchy direction:horizontal # ~
+operator.\u0332.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # _
+operator.\u03F6.infix = lspace:5 rspace:5 # greek reversed lunate epsilon symbol
+operator.\u2016.infix = lspace:5 rspace:5 stretchy direction:vertical # ‖ ‖
+operator.\u2026.infix = lspace:0 rspace:0 # horizontal ellipsis
+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.\u2201.infix = lspace:1 rspace:2 # complement
+operator.\u220E.infix = lspace:3 rspace:3 # end of proof
+operator.\u221F.infix = lspace:5 rspace:5 # right angle
+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.\u2234.infix = lspace:5 rspace:5 # ∴
+operator.\u2235.infix = lspace:5 rspace:5 # ∵
+operator.\u223D\u0331.infix = lspace:3 rspace:3 # reversed tilde with underline
+operator.\u223F.infix = lspace:3 rspace:3 # sine wave
+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.\u22A4.infix = lspace:5 rspace:5 # ⊤
+operator.\u22A5.infix = lspace:5 rspace:5 # ⊥
+operator.\u22B9.infix = lspace:5 rspace:5 # hermitian conjugate matrix
+operator.\u22BE.infix = lspace:3 rspace:3 # right angle with arc
+operator.\u22BF.infix = lspace:3 rspace:3 # right triangle
+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.\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.\u25A0.infix = lspace:3 rspace:3 # black square
+operator.\u25A1.infix = lspace:3 rspace:3 # white square
+operator.\u25A1.prefix = lspace:0 rspace:2 # □
+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.\u2606.infix = lspace:3 rspace:3 # ☆
+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.\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.\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.\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.\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.\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.\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.\u29E0.infix = lspace:3 rspace:3 # square with contoured outline
+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.\u29FE.infix = lspace:4 rspace:4 # tiny
+operator.\u29FF.infix = lspace:4 rspace:4 # miny
+operator.\u2AEC.infix = lspace:5 rspace:5 # double stroke not sign
+operator.\u2AED.infix = lspace:5 rspace:5 # reversed double stroke not sign
+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.\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/mathfontUnicode.properties b/layout/mathml/mathfontUnicode.properties
new file mode 100644
index 0000000000..69106fb8ff
--- /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..597aedabb7
--- /dev/null
+++ b/layout/mathml/mathml.css
@@ -0,0 +1,330 @@
+/* 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);
+
+* {
+ writing-mode: horizontal-tb !important;
+}
+
+/**************************************************************************/
+/* <math> - outermost math element */
+/**************************************************************************/
+
+math {
+ direction: ltr;
+ unicode-bidi: embed;
+ display: inline;
+ font-size: inherit;
+ font-style: normal;
+ font-weight: normal;
+ text-indent: 0;
+ font-family: serif;
+ line-height: normal;
+ word-spacing: normal;
+ letter-spacing: normal;
+ text-rendering: optimizeLegibility;
+ -moz-float-edge: margin-box;
+ math-style: compact;
+}
+math[display="block" i] {
+ display: block;
+ text-align: -moz-center;
+ math-style: normal;
+}
+math[display="inline" i] {
+ display: inline;
+ math-style: compact;
+}
+
+/**************************************************************************/
+/* Token elements */
+/**************************************************************************/
+
+@media not (-moz-mathml-core-ms) {
+ 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 MathMLElement.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 -> math-depth
+ - 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, 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 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 math-style for <math> involves the displaystyle
+ and display attributes. See the <math> section above.
+*/
+
+/* 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 { math-depth: add(1); }
+
+/*
+ The mfrac element sets displaystyle to "false", or if it was already false
+ increments scriptlevel by 1, within numerator and denominator.
+*/
+mfrac > * {
+ math-depth: auto-add;
+ math-style: compact;
+}
+
+/*
+ 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) {
+ math-depth: add(2);
+ math-style: compact;
+}
+
+/*
+ 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) {
+ math-depth: add(1);
+ math-style: compact;
+}
+
+/*
+ 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) {
+ math-style: compact;
+}
+
+/*
+ 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 { math-style: compact; }
+
+/*
+ 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 {
+ math-depth: add(1);
+ math-style: compact;
+}
+*/
+
+/* "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;
+}
+
+@media (-moz-mathml-core-maction-and-semantics) {
+ /* Implement MathML Core's semantics/maction support
+ https://w3c.github.io/mathml-core/#dfn-semantics
+ https://w3c.github.io/mathml-core/#dfn-maction */
+ maction > :not(:first-child),
+ semantics > :not(:first-child) {
+ display: none;
+ }
+}
diff --git a/layout/mathml/moz.build b/layout/mathml/moz.build
new file mode 100644
index 0000000000..6a80ce8270
--- /dev/null
+++ b/layout/mathml/moz.build
@@ -0,0 +1,62 @@
+# -*- 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 += [
+ "tests/mochitest.ini",
+ ]
+ MOCHITEST_CHROME_MANIFESTS += [
+ "tests/chrome.ini",
+ ]
+
+UNIFIED_SOURCES += [
+ "nsMathMLChar.cpp",
+ "nsMathMLContainerFrame.cpp",
+ "nsMathMLFrame.cpp",
+ "nsMathMLmactionFrame.cpp",
+ "nsMathMLmencloseFrame.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"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "../base",
+ "../generic",
+ "../painting",
+ "../style",
+ "../tables",
+ "../xul",
+ "/dom/base",
+ "/dom/mathml",
+]
+
+JAR_MANIFESTS += ["jar.mn"]
+
+RESOURCE_FILES.fonts += [
+ "mathfont.properties",
+ "mathfontSTIXGeneral.properties",
+ "mathfontUnicode.properties",
+]
diff --git a/layout/mathml/nsIMathMLFrame.h b/layout/mathml/nsIMathMLFrame.h
new file mode 100644
index 0000000000..eb1be05784
--- /dev/null
+++ b/layout/mathml/nsIMathMLFrame.h
@@ -0,0 +1,391 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 gfxContext;
+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..e38c68193a
--- /dev/null
+++ b/layout/mathml/nsMathMLAtoms.h
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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..49e98d25aa
--- /dev/null
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -0,0 +1,2261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gfxContext.h"
+#include "gfxTextRun.h"
+#include "gfxUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/StaticPrefs_mathml.h"
+
+#include "nsCOMPtr.h"
+#include "nsDeviceContext.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsUnicharUtils.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"
+
+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) {
+ aFont.size.ScaleBy(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() = default;
+
+ virtual const nsCString& 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 nsACString& aName,
+ nsCOMPtr<nsIPersistentProperties>& aProperties) {
+ nsAutoCString 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),
+ uriStr);
+}
+
+class nsPropertiesTable final : public nsGlyphTable {
+ public:
+ explicit nsPropertiesTable(const nsACString& aPrimaryFontName)
+ : mState(NS_TABLE_STATE_EMPTY) {
+ MOZ_COUNT_CTOR(nsPropertiesTable);
+ mGlyphCodeFonts.AppendElement(aPrimaryFontName);
+ }
+
+ MOZ_COUNTED_DTOR(nsPropertiesTable)
+
+ const nsCString& PrimaryFontName() const { return mGlyphCodeFonts[0]; }
+
+ const nsCString& 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<nsCString> 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) {
+ nsresult rv = LoadProperties(PrimaryFontName(), mGlyphProperties);
+#ifdef DEBUG
+ nsAutoCString uriStr;
+ uriStr.AssignLiteral("resource://gre/res/fonts/mathfont");
+ uriStr.Append(PrimaryFontName());
+ 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(NS_ConvertUTF16toUTF8(value));
+ }
+ }
+
+ // 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;
+ }
+ }
+ 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, gfx::ShapedTextFlags(),
+ nsTextFrameUtils::Flags(), 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:
+ MOZ_COUNTED_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 nsCString& 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 UniquePtr<nsOpenTypeTable> Create(gfxFont* aFont) {
+ if (!aFont->TryGetMathTable()) {
+ return nullptr;
+ }
+ return WrapUnique(new nsOpenTypeTable(aFont));
+ }
+
+ private:
+ RefPtr<gfxFont> mFont;
+ nsCString mFontFamilyName;
+ uint32_t mGlyphID;
+
+ explicit nsOpenTypeTable(gfxFont* aFont)
+ : mFont(aFont),
+ mFontFamilyName(aFont->GetFontEntry()->FamilyName()),
+ mGlyphID(0) {
+ 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, gfx::ShapedTextFlags(),
+ nsTextFrameUtils::Flags(), nullptr);
+ const gfxTextRun::CompressedGlyph& data = textRun->GetCharacterGlyphs()[0];
+ if (data.IsSimpleGlyph()) {
+ mGlyphID = data.GetSimpleGlyph();
+ } else if (data.GetGlyphCount() == 1) {
+ mGlyphID = textRun->GetDetailedGlyphs(0)->mGlyphID;
+ } else {
+ mGlyphID = 0;
+ }
+ mCharCache = aChar;
+ }
+}
+
+/* virtual */
+nsGlyphCode nsOpenTypeTable::ElementAt(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup, char16_t aChar,
+ bool aVertical, uint32_t aPosition) {
+ UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+ uint32_t parts[4];
+ if (!mFont->MathTable()->VariantsParts(mGlyphID, aVertical, parts)) {
+ return kNullGlyph;
+ }
+
+ uint32_t glyphID = parts[aPosition];
+ if (!glyphID) {
+ return kNullGlyph;
+ }
+ nsGlyphCode glyph;
+ glyph.glyphID = glyphID;
+ glyph.font = -1;
+ return glyph;
+}
+
+/* virtual */
+nsGlyphCode nsOpenTypeTable::BigOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup, char16_t aChar,
+ bool aVertical, uint32_t aSize) {
+ UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+ uint32_t glyphID =
+ mFont->MathTable()->VariantsSize(mGlyphID, aVertical, aSize);
+ if (!glyphID) {
+ return kNullGlyph;
+ }
+
+ nsGlyphCode glyph;
+ glyph.glyphID = glyphID;
+ glyph.font = -1;
+ return glyph;
+}
+
+/* virtual */
+bool nsOpenTypeTable::HasPartsOf(DrawTarget* aDrawTarget,
+ int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup, char16_t aChar,
+ bool aVertical) {
+ UpdateCache(aDrawTarget, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+ uint32_t parts[4];
+ if (!mFont->MathTable()->VariantsParts(mGlyphID, aVertical, parts)) {
+ return false;
+ }
+
+ return parts[0] || parts[1] || parts[2] || parts[3];
+}
+
+/* virtual */
+already_AddRefed<gfxTextRun> nsOpenTypeTable::MakeTextRun(
+ DrawTarget* aDrawTarget, int32_t aAppUnitsPerDevPixel,
+ gfxFontGroup* aFontGroup, const nsGlyphCode& aGlyph) {
+ NS_ASSERTION(aGlyph.IsGlyphID(),
+ "nsOpenTypeTable can only access glyphs by id");
+
+ gfxTextRunFactory::Parameters params = {
+ aDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel};
+ RefPtr<gfxTextRun> textRun =
+ gfxTextRun::Create(&params, 1, aFontGroup, gfx::ShapedTextFlags(),
+ nsTextFrameUtils::Flags());
+ RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont();
+ textRun->AddGlyphRun(font, FontMatchType::Kind::kFontGroup, 0, false,
+ gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL, false);
+ // 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 * font->GetGlyphAdvance(aGlyph.glyphID));
+ textRun->SetDetailedGlyphs(0, 1, &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("Unicode"_ns) {}
+
+ nsresult Initialize();
+ nsresult Finalize();
+
+ // Add a glyph table in the list, return the new table that was added
+ nsGlyphTable* AddGlyphTable(const nsACString& aPrimaryFontName);
+
+ // Find the glyph table in the list corresponding to the given font family.
+ nsGlyphTable* GetGlyphTableFor(const nsACString& aFamily);
+
+ private:
+ ~nsGlyphTableList() = default;
+
+ 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 nsACString& 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 nsACString& aFamily) {
+ for (int32_t i = 0; i < PropertiesTableCount(); i++) {
+ nsPropertiesTable* glyphTable = PropertiesTableAt(i);
+ const nsCString& primaryFontName = glyphTable->PrimaryFontName();
+ // TODO: would be nice to consider StripWhitespace and other aliasing
+ if (primaryFontName.Equals(aFamily, nsCaseInsensitiveCStringComparator)) {
+ 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("STIXGeneral"_ns)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ glyphTableList.forget(&gGlyphTableList);
+ return rv;
+}
+
+// -----------------------------------------------------------------------------
+// And now the implementation of nsMathMLChar
+
+nsMathMLChar::~nsMathMLChar() { MOZ_COUNT_DTOR(nsMathMLChar); }
+
+ComputedStyle* nsMathMLChar::GetComputedStyle() const {
+ NS_ASSERTION(mComputedStyle, "chars should always have a ComputedStyle");
+ return mComputedStyle;
+}
+
+void nsMathMLChar::SetComputedStyle(ComputedStyle* aComputedStyle) {
+ MOZ_ASSERT(aComputedStyle);
+ mComputedStyle = aComputedStyle;
+}
+
+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
+ ComputedStyles in use throughout the process. The leaf style
+ context of the char holds fonts with which to try to stretch
+ the char. The parent ComputedStyle 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 ComputedStyle (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 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 StyleFontFamilyList& aDefaultFamilyList,
+ nsFont& aFont,
+ RefPtr<gfxFontGroup>* aFontGroup) {
+ StyleFontFamilyList glyphCodeFont;
+ if (aGlyphCode.font) {
+ glyphCodeFont = StyleFontFamilyList::WithOneUnquotedFamily(
+ aGlyphTable->FontNameFor(aGlyphCode));
+ }
+
+ const StyleFontFamilyList& familyList =
+ aGlyphCode.font ? glyphCodeFont : aDefaultFamilyList;
+
+ if (!*aFontGroup || aFont.family.families != familyList) {
+ nsFont font = aFont;
+ font.family.families = familyList;
+ const nsStyleFont* styleFont = mComputedStyle->StyleFont();
+ nsFontMetrics::Params params;
+ params.language = styleFont->mLanguage;
+ params.explicitLanguage = styleFont->mExplicitLanguage;
+ params.userFontSet = aPresContext->GetUserFontSet();
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
+ RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params);
+ // Set the font if it is an unicode table or if the same family name has
+ // been found.
+ const bool shouldSetFont = [&] {
+ if (aGlyphTable == &gGlyphTableList->mUnicodeTable) {
+ return true;
+ }
+
+ if (familyList.list.IsEmpty()) {
+ return false;
+ }
+
+ const auto& firstFontInList = familyList.list.AsSpan()[0];
+
+ RefPtr<gfxFont> firstFont = fm->GetThebesFontGroup()->GetFirstValidFont();
+ RefPtr<nsAtom> firstFontName =
+ NS_Atomize(firstFont->GetFontEntry()->FamilyName());
+
+ return firstFontInList.IsFamilyName() &&
+ firstFontInList.AsFamilyName().name.AsAtom() == firstFontName;
+ }();
+ if (!shouldSetFont) {
+ return false;
+ }
+ aFont.family.families = familyList;
+ *aFontGroup = fm->GetThebesFontGroup();
+ }
+ 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 StyleFontFamilyList& 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 StyleSingleFontFamily& aFamily, void* aData);
+
+ private:
+ bool TryVariants(nsGlyphTable* aGlyphTable, RefPtr<gfxFontGroup>* aFontGroup,
+ const StyleFontFamilyList& aFamilyList);
+ bool TryParts(nsGlyphTable* aGlyphTable, RefPtr<gfxFontGroup>* aFontGroup,
+ const StyleFontFamilyList& 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 StyleFontFamilyList& 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 StyleFontFamilyList& aFamilyList) {
+ // Use our stretchy ComputedStyle now that stretching is in progress
+ ComputedStyle* sc = mChar->mComputedStyle;
+ 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()) {
+ RefPtr<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 (nsMathMLOperators::IsIntegralOperator(mChar->mData)) {
+ // 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()) {
+ RefPtr<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] = std::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 StyleFontFamilyList& aFamilyList) {
+ // Use our stretchy ComputedStyle now that stretching is in progress
+ nsFont font = mChar->mComputedStyle->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] = std::move(textRun[i]);
+ mChar->mBmData[i] = bmdata[i];
+ }
+
+ return IsSizeOK(computedSize, mTargetSize, mStretchHint);
+}
+
+// Returns true iff stretching succeeded with the given family.
+// This is called for each family, whether it exists or not.
+bool nsMathMLChar::StretchEnumContext::EnumCallback(
+ const StyleSingleFontFamily& aFamily, void* aData) {
+ StretchEnumContext* context = static_cast<StretchEnumContext*>(aData);
+
+ // for comparisons, force use of unquoted names
+ StyleFontFamilyList family;
+ if (aFamily.IsFamilyName()) {
+ family = StyleFontFamilyList::WithOneUnquotedFamily(
+ nsAtomCString(aFamily.AsFamilyName().name.AsAtom()));
+ }
+
+ // Check font family if it is not a generic one
+ // We test with the kNullGlyph
+ ComputedStyle* sc = context->mChar->mComputedStyle;
+ nsFont font = sc->StyleFont()->mFont;
+ NormalizeDefaultFont(font, context->mFontSizeInflation);
+ RefPtr<gfxFontGroup> fontGroup;
+ if (!aFamily.IsGeneric() &&
+ !context->mChar->SetFontFamily(context->mPresContext, nullptr, kNullGlyph,
+ family, font, &fontGroup)) {
+ return false; // Could not set the family
+ }
+
+ // Determine the glyph table to use for this font.
+ UniquePtr<nsOpenTypeTable> openTypeTable;
+ nsGlyphTable* glyphTable;
+ if (aFamily.IsGeneric()) {
+ // This is a generic font, use the Unicode table.
+ glyphTable = &gGlyphTableList->mUnicodeTable;
+ } else {
+ // If the font contains an Open Type MATH table, use it.
+ RefPtr<gfxFont> font = fontGroup->GetFirstValidFont();
+ openTypeTable = nsOpenTypeTable::Create(font);
+ if (openTypeTable) {
+ glyphTable = openTypeTable.get();
+ } else if (StaticPrefs::mathml_stixgeneral_operator_stretching_disabled()) {
+ glyphTable = &gGlyphTableList->mUnicodeTable;
+ } else {
+ // Otherwise try to find a .properties file corresponding to that font
+ // family or fallback to the Unicode table.
+ glyphTable = gGlyphTableList->GetGlyphTableFor(
+ nsAtomCString(aFamily.AsFamilyName().name.AsAtom()));
+ }
+ }
+
+ if (!openTypeTable) {
+ if (context->mTablesTried.Contains(glyphTable))
+ return false; // 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 StyleFontFamilyList& familyList =
+ glyphTable == &gGlyphTableList->mUnicodeTable ? context->mFamilyList
+ : family;
+
+ return (context->mTryVariants &&
+ context->TryVariants(glyphTable, &fontGroup, familyList)) ||
+ (context->mTryParts &&
+ context->TryParts(glyphTable, &fontGroup, familyList));
+}
+
+static void AppendFallbacks(nsTArray<StyleSingleFontFamily>& aNames,
+ const nsTArray<nsCString>& aFallbacks) {
+ for (const nsCString& fallback : aFallbacks) {
+ aNames.AppendElement(StyleSingleFontFamily::FamilyName(
+ StyleFamilyName{StyleAtom(NS_Atomize(fallback)),
+ StyleFontFamilyNameSyntax::Identifiers}));
+ }
+}
+
+// insert math fallback families just before the first generic or at the end
+// when no generic present
+static void InsertMathFallbacks(StyleFontFamilyList& aFamilyList,
+ nsTArray<nsCString>& aFallbacks) {
+ nsTArray<StyleSingleFontFamily> mergedList;
+
+ bool inserted = false;
+ for (const auto& name : aFamilyList.list.AsSpan()) {
+ if (!inserted && name.IsGeneric()) {
+ inserted = true;
+ AppendFallbacks(mergedList, aFallbacks);
+ }
+ mergedList.AppendElement(name);
+ }
+
+ if (!inserted) {
+ AppendFallbacks(mergedList, aFallbacks);
+ }
+ aFamilyList = StyleFontFamilyList::WithNames(std::move(mergedList));
+}
+
+nsresult nsMathMLChar::StretchInternal(
+ nsIFrame* aForFrame, 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) {
+ nsPresContext* presContext = aForFrame->PresContext();
+
+ // 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
+ // mComputedStyle is a leaf context used only when stretching happens.
+ // For the base size, the default font should come from the parent context
+ nsFont font = aForFrame->StyleFont()->mFont;
+ NormalizeDefaultFont(font, aFontSizeInflation);
+
+ const nsStyleFont* styleFont = mComputedStyle->StyleFont();
+ nsFontMetrics::Params params;
+ params.language = styleFont->mLanguage;
+ params.explicitLanguage = styleFont->mExplicitLanguage;
+ params.userFontSet = presContext->GetUserFontSet();
+ params.textPerf = presContext->GetTextPerfMetrics();
+ RefPtr<nsFontMetrics> fm = presContext->GetMetricsFor(font, params);
+ uint32_t len = uint32_t(mData.Length());
+ mGlyphs[0] = fm->GetThebesFontGroup()->MakeTextRun(
+ static_cast<const char16_t*>(mData.get()), len, aDrawTarget,
+ presContext->AppUnitsPerDevPixel(), gfx::ShapedTextFlags(),
+ nsTextFrameUtils::Flags(), presContext->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 = mComputedStyle->StyleFont()->mFont;
+ NormalizeDefaultFont(font, aFontSizeInflation);
+
+ // really shouldn't be doing things this way but for now
+ // insert fallbacks into the list
+ AutoTArray<nsCString, 16> mathFallbacks;
+ nsAutoCString value;
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ pfl->Lock();
+ if (pfl->GetFontPrefs()->LookupName("serif.x-math"_ns, value)) {
+ gfxFontUtils::ParseFontList(value, mathFallbacks);
+ }
+ if (pfl->GetFontPrefs()->LookupNameList("serif.x-math"_ns, value)) {
+ gfxFontUtils::ParseFontList(value, mathFallbacks);
+ }
+ pfl->Unlock();
+ InsertMathFallbacks(font.family.families, 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, presContext, aDrawTarget,
+ aFontSizeInflation, aStretchDirection,
+ targetSize, aStretchHint, aDesiredStretchSize,
+ font.family.families, glyphFound);
+ enumData.mTryParts = !largeopOnly;
+
+ for (const StyleSingleFontFamily& name :
+ font.family.families.list.AsSpan()) {
+ if (StretchEnumContext::EnumCallback(name, &enumData)) {
+ if (name.IsNamedFamily(u"STIXGeneral"_ns)) {
+ AutoTArray<nsString, 1> params{
+ u"https://developer.mozilla.org/docs/Mozilla/"
+ "MathML_Project/Fonts"_ns};
+ aForFrame->PresContext()->Document()->WarnOnceAbout(
+ dom::DeprecatedOperations::
+ eMathML_DeprecatedStixgeneralOperatorStretching,
+ false, params);
+ }
+ break;
+ }
+ }
+ }
+
+ 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 = presContext->MissingFontRecorder();
+ RefPtr<gfxFont> firstMathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
+ if (MFR && !firstMathFont) {
+ MFR->RecordScript(intl::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 (nsMathMLOperators::IsIntegralOperator(mData)) {
+ // 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(nsIFrame* aForFrame, 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)),
+ "Unexpected stretch flags");
+
+ mDraw = DRAW_NORMAL;
+ mMirrored = aRTL && nsMathMLOperators::IsMirrorableOperator(mData);
+ mScaleY = mScaleX = 1.0;
+ mDirection = aStretchDirection;
+ nsresult rv =
+ StretchInternal(aForFrame, 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(nsIFrame* aForFrame, DrawTarget* aDrawTarget,
+ float aFontSizeInflation,
+ uint32_t aStretchHint) {
+ nsBoundingMetrics bm;
+ nsStretchDirection direction = NS_STRETCH_DIRECTION_VERTICAL;
+ const nsBoundingMetrics container; // zero target size
+
+ StretchInternal(aForFrame, aDrawTarget, aFontSizeInflation, direction,
+ container, bm, aStretchHint | NS_STRETCH_MAXWIDTH);
+
+ return std::max(bm.width, bm.rightBearing) - std::min(0, bm.leftBearing);
+}
+
+namespace mozilla {
+
+class nsDisplayMathMLSelectionRect final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayMathMLSelectionRect(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLSelectionRect);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLSelectionRect)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLSelectionRect", TYPE_MATHML_SELECTION_RECT)
+ private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLSelectionRect::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* 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::Color(LookAndFeel::ColorID::Highlight, mFrame);
+ drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(bgColor)));
+}
+
+class nsDisplayMathMLCharForeground final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayMathMLCharForeground(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsMathMLChar* aChar,
+ const bool aIsSelected)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mChar(aChar),
+ mIsSelected(aIsSelected) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLCharForeground);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLCharForeground)
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const 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,
+ gfxContext* aCtx) override {
+ mChar->PaintForeground(mFrame, *aCtx, ToReferenceFrame(), mIsSelected);
+ }
+
+ NS_DISPLAY_DECL_NAME("MathMLCharForeground", TYPE_MATHML_CHAR_FOREGROUND)
+
+ virtual nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+
+ private:
+ nsMathMLChar* mChar;
+ bool mIsSelected;
+};
+
+#ifdef DEBUG
+class nsDisplayMathMLCharDebug final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayMathMLCharDebug(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLCharDebug);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLCharDebug)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLCharDebug", TYPE_MATHML_CHAR_DEBUG)
+
+ private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLCharDebug::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ // for visual debug
+ Sides skipSides;
+ nsPresContext* presContext = mFrame->PresContext();
+ ComputedStyle* computedStyle = mFrame->Style();
+ nsRect rect = mRect + ToReferenceFrame();
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SyncDecodeImages
+ : PaintBorderFlags();
+
+ // Since this is used only for debugging, we don't need to worry about
+ // tracking the ImgDrawResult.
+ Unused << nsCSSRendering::PaintBorder(presContext, *aCtx, mFrame,
+ GetPaintRect(aBuilder, aCtx), rect,
+ computedStyle, flags, skipSides);
+
+ nsCSSRendering::PaintNonThemedOutline(presContext, *aCtx, mFrame,
+ GetPaintRect(aBuilder, aCtx), rect,
+ computedStyle);
+}
+#endif
+
+} // namespace mozilla
+
+void nsMathMLChar::Display(nsDisplayListBuilder* aBuilder, nsIFrame* aForFrame,
+ const nsDisplayListSet& aLists, uint32_t aIndex,
+ const nsRect* aSelectedRect) {
+ ComputedStyle* computedStyle = mComputedStyle;
+ if (!computedStyle->StyleVisibility()->IsVisible()) {
+ return;
+ }
+
+ const bool isSelected = aSelectedRect && !aSelectedRect->IsEmpty();
+
+ if (isSelected) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayMathMLSelectionRect>(
+ aBuilder, aForFrame, *aSelectedRect);
+ } else if (mRect.width && mRect.height) {
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ // for visual debug
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayMathMLCharDebug>(
+ aBuilder, aForFrame, mRect);
+#endif
+ }
+ aLists.Content()->AppendNewToTopWithIndex<nsDisplayMathMLCharForeground>(
+ aBuilder, aForFrame, aIndex, this, isSelected);
+}
+
+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->SetMatrixDouble(aThebesContext->CurrentMatrixDouble()
+ .PreTranslate(devPixelOffset)
+ .PreScale(-mScaleX, mScaleY));
+ } else {
+ nsPoint pt = r.TopLeft();
+ gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit),
+ NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit));
+ aThebesContext->SetMatrixDouble(aThebesContext->CurrentMatrixDouble()
+ .PreTranslate(devPixelOffset)
+ .PreScale(mScaleX, mScaleY));
+ }
+
+ // update the bounding rectangle.
+ r.x = r.y = 0;
+ r.width /= mScaleX;
+ r.height /= mScaleY;
+}
+
+void nsMathMLChar::PaintForeground(nsIFrame* aForFrame,
+ gfxContext& aRenderingContext, nsPoint aPt,
+ bool aIsSelected) {
+ ComputedStyle* computedStyle = mComputedStyle;
+ nsPresContext* presContext = aForFrame->PresContext();
+
+ if (mDraw == DRAW_NORMAL) {
+ // normal drawing if there is nothing special about this char
+ // Use our parent element's style
+ computedStyle = aForFrame->Style();
+ }
+
+ // Set color ...
+ nscolor fgColor = computedStyle->GetVisitedDependentColor(
+ &nsStyleText::mWebkitTextFillColor);
+ if (aIsSelected) {
+ // get color to use for selection from the look&feel object
+ fgColor = LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext, aForFrame,
+ fgColor);
+ }
+ aRenderingContext.SetColor(sRGBColor::FromABGR(fgColor));
+ aRenderingContext.Save();
+ nsRect r = mRect + aPt;
+ ApplyTransforms(&aRenderingContext,
+ aForFrame->PresContext()->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()),
+ gfx::Point(0.0, mUnscaledAscent),
+ gfxTextRun::DrawParams(&aRenderingContext));
+ }
+ break;
+ case DRAW_PARTS: {
+ // paint by parts
+ if (NS_STRETCH_DIRECTION_VERTICAL == mDirection)
+ PaintVertically(presContext, &aRenderingContext, r, fgColor);
+ else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection)
+ PaintHorizontally(presContext, &aRenderingContext, r, fgColor);
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown drawing method");
+ break;
+ }
+
+ aRenderingContext.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();
+ gfxRect clip = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerGfxUnit);
+ mThebesContext->SnappedClip(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);
+
+ 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()), gfx::Point(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()), gfx::Point(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);
+
+ 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()), gfx::Point(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()), gfx::Point(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..07d803b00c
--- /dev/null
+++ b/layout/mathml/nsMathMLChar.h
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 gfxContext;
+class nsGlyphTable;
+class nsIFrame;
+class nsPresContext;
+struct nsBoundingMetrics;
+struct nsFont;
+
+namespace mozilla {
+class nsDisplayListBuilder;
+class nsDisplayListSet;
+class ComputedStyle;
+} // namespace mozilla
+
+// 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,
+
+ // Intended for internal use:
+ // Find the widest metrics that might be returned from a vertical stretch
+ NS_STRETCH_MAXWIDTH = 0x20
+};
+
+// 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() : mDirection(NS_STRETCH_DIRECTION_DEFAULT) {
+ MOZ_COUNT_CTOR(nsMathMLChar);
+ mComputedStyle = 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(mozilla::nsDisplayListBuilder* aBuilder, nsIFrame* aForFrame,
+ const mozilla::nsDisplayListSet& aLists, uint32_t aIndex,
+ const nsRect* aSelectedRect = nullptr);
+
+ void PaintForeground(nsIFrame* aForFrame, gfxContext& 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(nsIFrame* aForFrame, 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(nsIFrame* aForFrame, 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 ComputedStyles, 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 ComputedStyles given to the MathMLChars.
+ // They provide an interface to make them accessible to the Style System via
+ // the Get/Set AdditionalComputedStyle() APIs. Owners of MathMLChars
+ // should honor these APIs.
+ mozilla::ComputedStyle* GetComputedStyle() const;
+
+ void SetComputedStyle(mozilla::ComputedStyle* aComputedStyle);
+
+ protected:
+ friend class nsGlyphTable;
+ friend class nsPropertiesTable;
+ friend class nsOpenTypeTable;
+ nsString mData;
+
+ private:
+ nsRect mRect;
+ nsStretchDirection mDirection;
+ nsBoundingMetrics mBoundingMetrics;
+ RefPtr<mozilla::ComputedStyle> mComputedStyle;
+ // 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::StyleFontFamilyList& aDefaultFamily,
+ nsFont& aFont, RefPtr<gfxFontGroup>* aFontGroup);
+
+ nsresult StretchInternal(nsIFrame* aForFrame, 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..edf3c5b5cb
--- /dev/null
+++ b/layout/mathml/nsMathMLContainerFrame.cpp
@@ -0,0 +1,1499 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsDisplayList.h"
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/MathMLElement.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;
+}
+
+namespace mozilla {
+
+class nsDisplayMathMLError : public nsPaintedDisplayItem {
+ public:
+ nsDisplayMathMLError(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLError);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLError)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLError", TYPE_MATHML_ERROR)
+};
+
+void nsDisplayMathMLError::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* 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(sRGBColor(1.f, 0.f, 0.f, 1.f)));
+ drawTarget->FillRect(rect, red);
+
+ aCtx->SetColor(sRGBColor::OpaqueWhite());
+ nscoord ascent = fm->MaxAscent();
+ constexpr auto errorMsg = u"invalid-markup"_ns;
+ nsLayoutUtils::DrawUniDirString(errorMsg.get(), uint32_t(errorMsg.Length()),
+ nsPoint(pt.x, pt.y + ascent), *fm, *aCtx);
+}
+
+} // namespace mozilla
+
+/* /////////////
+ * 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->IsBlockFrame();
+}
+
+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->SetProperty(HTMLReflowOutputProperty(), reflowOutput);
+}
+
+// helper method to facilitate getting the reflow and bounding metrics
+/* static */
+void nsMathMLContainerFrame::GetReflowAndBoundingMetricsFor(
+ nsIFrame* aFrame, ReflowOutput& aReflowOutput,
+ nsBoundingMetrics& aBoundingMetrics, eMathMLFrameType* aMathMLFrameType) {
+ MOZ_ASSERT(aFrame, "null arg");
+
+ ReflowOutput* reflowOutput = aFrame->GetProperty(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();
+ while (childFrame) {
+ childFrame->RemoveProperty(HTMLReflowOutputProperty());
+ childFrame = childFrame->GetNextSibling();
+ }
+}
+
+// helper to get the preferred size that a container frame should use to fire
+// the stretch on its stretchy child frames.
+void nsMathMLContainerFrame::GetPreferredStretchSize(
+ DrawTarget* aDrawTarget, uint32_t aOptions,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aPreferredStretchSize) {
+ if (aOptions & STRETCH_CONSIDER_ACTUAL_SIZE) {
+ // when our actual size is ok, just use it
+ aPreferredStretchSize = mBoundingMetrics;
+ } else if (aOptions & STRETCH_CONSIDER_EMBELLISHMENTS) {
+ // compute our up-to-date size using Place()
+ ReflowOutput reflowOutput(GetWritingMode());
+ Place(aDrawTarget, false, reflowOutput);
+ aPreferredStretchSize = reflowOutput.mBoundingMetrics;
+ } else {
+ // compute a size that includes embellishments iff the container stretches
+ // in the same direction as the embellished operator.
+ bool stretchAll = aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL
+ ? NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
+ mPresentationData.flags)
+ : NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(
+ mPresentationData.flags);
+ NS_ASSERTION(aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL ||
+ aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL,
+ "You must specify a direction in which to stretch");
+ NS_ASSERTION(
+ NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) || stretchAll,
+ "invalid call to GetPreferredStretchSize");
+ bool firstTime = true;
+ nsBoundingMetrics bm, bmChild;
+ nsIFrame* childFrame = stretchAll ? PrincipalChildList().FirstChild()
+ : mPresentationData.baseFrame;
+ while (childFrame) {
+ // initializations in case this child happens not to be a MathML frame
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame);
+ if (mathMLFrame) {
+ nsEmbellishData embellishData;
+ nsPresentationData presentationData;
+ mathMLFrame->GetEmbellishData(embellishData);
+ mathMLFrame->GetPresentationData(presentationData);
+ if (NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags) &&
+ embellishData.direction == aStretchDirection &&
+ presentationData.baseFrame) {
+ // embellishements are not included, only consider the inner first
+ // child itself
+ // XXXkt Does that mean the core descendent frame should be used
+ // instead of the base child?
+ nsIMathMLFrame* mathMLchildFrame =
+ do_QueryFrame(presentationData.baseFrame);
+ if (mathMLchildFrame) {
+ mathMLFrame = mathMLchildFrame;
+ }
+ }
+ mathMLFrame->GetBoundingMetrics(bmChild);
+ } else {
+ ReflowOutput unused(GetWritingMode());
+ GetReflowAndBoundingMetricsFor(childFrame, unused, bmChild);
+ }
+
+ if (firstTime) {
+ firstTime = false;
+ bm = bmChild;
+ if (!stretchAll) {
+ // we may get here for cases such as <msup><mo>...</mo> ... </msup>,
+ // or <maction>...<mo>...</mo></maction>.
+ break;
+ }
+ } else {
+ if (aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL) {
+ // if we get here, it means this is container that will stack its
+ // children vertically and fire an horizontal stretch on each them.
+ // This is the case for \munder, \mover, \munderover. We just sum-up
+ // the size vertically.
+ bm.descent += bmChild.ascent + bmChild.descent;
+ // Sometimes non-spacing marks (when width is zero) are positioned
+ // to the left of the origin, but it is the distance between left
+ // and right bearing that is important rather than the offsets from
+ // the origin.
+ if (bmChild.width == 0) {
+ bmChild.rightBearing -= bmChild.leftBearing;
+ bmChild.leftBearing = 0;
+ }
+ if (bm.leftBearing > bmChild.leftBearing)
+ bm.leftBearing = bmChild.leftBearing;
+ if (bm.rightBearing < bmChild.rightBearing)
+ bm.rightBearing = bmChild.rightBearing;
+ } else if (aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL) {
+ // just sum-up the sizes horizontally.
+ bm += bmChild;
+ } else {
+ NS_ERROR("unexpected case in GetPreferredStretchSize");
+ break;
+ }
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ aPreferredStretchSize = bm;
+ }
+}
+
+NS_IMETHODIMP
+nsMathMLContainerFrame::Stretch(DrawTarget* aDrawTarget,
+ nsStretchDirection aStretchDirection,
+ nsBoundingMetrics& aContainerSize,
+ ReflowOutput& aDesiredStretchSize) {
+ if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) {
+ if (NS_MATHML_STRETCH_WAS_DONE(mPresentationData.flags)) {
+ NS_WARNING("it is wrong to fire stretch more than once on a frame");
+ return NS_OK;
+ }
+ mPresentationData.flags |= NS_MATHML_STRETCH_DONE;
+
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
+ NS_WARNING("it is wrong to fire stretch on a erroneous frame");
+ return NS_OK;
+ }
+
+ // Pass the stretch to the base child ...
+
+ nsIFrame* baseFrame = mPresentationData.baseFrame;
+ if (baseFrame) {
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(baseFrame);
+ NS_ASSERTION(mathMLFrame, "Something is wrong somewhere");
+ if (mathMLFrame) {
+ // And the trick is that the child's rect.x is still holding the
+ // descent, and rect.y is still holding the ascent ...
+ ReflowOutput childSize(aDesiredStretchSize);
+ GetReflowAndBoundingMetricsFor(baseFrame, childSize,
+ childSize.mBoundingMetrics);
+
+ // See if we should downsize and confine the stretch to us...
+ // XXX there may be other cases where we can downsize the stretch,
+ // e.g., the first &Sum; might appear big in the following situation
+ // <math xmlns='http://www.w3.org/1998/Math/MathML'>
+ // <mstyle>
+ // <msub>
+ // <msub><mo>&Sum;</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub>
+ // <msub><mo>&Sum;</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub>
+ // </msub>
+ // </mstyle>
+ // </math>
+ nsBoundingMetrics containerSize = aContainerSize;
+ if (aStretchDirection != mEmbellishData.direction &&
+ mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED) {
+ NS_ASSERTION(
+ mEmbellishData.direction != NS_STRETCH_DIRECTION_DEFAULT,
+ "Stretches may have a default direction, operators can not.");
+ if (mEmbellishData.direction == NS_STRETCH_DIRECTION_VERTICAL
+ ? NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
+ mPresentationData.flags)
+ : NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(
+ mPresentationData.flags)) {
+ GetPreferredStretchSize(aDrawTarget, 0, mEmbellishData.direction,
+ containerSize);
+ // Stop further recalculations
+ aStretchDirection = mEmbellishData.direction;
+ } else {
+ // We aren't going to stretch the child, so just use the child
+ // metrics.
+ containerSize = childSize.mBoundingMetrics;
+ }
+ }
+
+ // do the stretching...
+ mathMLFrame->Stretch(aDrawTarget, aStretchDirection, containerSize,
+ childSize);
+ // store the updated metrics
+ SaveReflowAndBoundingMetricsFor(baseFrame, childSize,
+ childSize.mBoundingMetrics);
+
+ // Remember the siblings which were _deferred_.
+ // Now that this embellished child may have changed, we need to
+ // fire the stretch on its siblings using our updated size
+
+ if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
+ mPresentationData.flags) ||
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(
+ mPresentationData.flags)) {
+ nsStretchDirection stretchDir =
+ NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
+ mPresentationData.flags)
+ ? NS_STRETCH_DIRECTION_VERTICAL
+ : NS_STRETCH_DIRECTION_HORIZONTAL;
+
+ GetPreferredStretchSize(aDrawTarget, STRETCH_CONSIDER_EMBELLISHMENTS,
+ stretchDir, containerSize);
+
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ if (childFrame != mPresentationData.baseFrame) {
+ mathMLFrame = do_QueryFrame(childFrame);
+ if (mathMLFrame) {
+ // retrieve the metrics that was stored at the previous pass
+ GetReflowAndBoundingMetricsFor(childFrame, childSize,
+ childSize.mBoundingMetrics);
+ // do the stretching...
+ mathMLFrame->Stretch(aDrawTarget, stretchDir, containerSize,
+ childSize);
+ // store the updated metrics
+ SaveReflowAndBoundingMetricsFor(childFrame, childSize,
+ childSize.mBoundingMetrics);
+ }
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ }
+
+ // re-position all our children
+ nsresult rv = Place(aDrawTarget, true, aDesiredStretchSize);
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
+ // Make sure the child frames get their DidReflow() calls.
+ DidReflowChildren(mFrames.FirstChild());
+ }
+
+ // If our parent is not embellished, it means we are the outermost
+ // embellished container and so we put the spacing, otherwise we don't
+ // include the spacing, the outermost embellished container will take
+ // care of it.
+
+ nsEmbellishData parentData;
+ GetEmbellishDataFrom(GetParent(), parentData);
+ // ensure that we are the embellished child, not just a sibling
+ // (need to test coreFrame since <mfrac> resets other things)
+ if (parentData.coreFrame != mEmbellishData.coreFrame) {
+ // (we fetch values from the core since they may use units that depend
+ // on style data, and style changes could have occurred in the core
+ // since our last visit there)
+ nsEmbellishData coreData;
+ GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
+
+ mBoundingMetrics.width +=
+ coreData.leadingSpace + coreData.trailingSpace;
+ aDesiredStretchSize.Width() = mBoundingMetrics.width;
+ aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width;
+
+ nscoord dx = StyleVisibility()->mDirection == StyleDirection::Rtl
+ ? 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->HasAnyStateBits(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 nsDisplayListSet& aLists) {
+ // report an error if something wrong was found in this frame
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
+ if (!IsVisibleForPainting()) return;
+
+ aLists.Content()->AppendNewToTop<nsDisplayMathMLError>(aBuilder, this);
+ return;
+ }
+
+ BuildDisplayListForInline(aBuilder, aLists);
+
+#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->PresShell()->FrameNeedsReflow(
+ frame, IntrinsicDirty::FrameAncestorsAndDescendants, 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 == FrameChildListID::Principal);
+ mFrames.AppendFrames(this, std::move(aFrameList));
+ ChildListChanged(dom::MutationEvent_Binding::ADDITION);
+}
+
+void nsMathMLContainerFrame::InsertFrames(
+ ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal);
+ mFrames.InsertFrames(this, aPrevFrame, std::move(aFrameList));
+ ChildListChanged(dom::MutationEvent_Binding::ADDITION);
+}
+
+void nsMathMLContainerFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal);
+ mFrames.DestroyFrame(aOldFrame);
+ ChildListChanged(dom::MutationEvent_Binding::REMOVAL);
+}
+
+nsresult nsMathMLContainerFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* 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!
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants, 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(
+ OverflowAreas& 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 ink 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,
+ ReflowChildFlags::NoMoveFrame, 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 nsIFrame::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();
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ 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);
+ 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(childStatus.IsComplete(), "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);
+}
+
+static nscoord AddInterFrameSpacingToSize(ReflowOutput& aDesiredSize,
+ nsMathMLContainerFrame* aFrame);
+
+/* virtual */
+void nsMathMLContainerFrame::MarkIntrinsicISizesDirty() {
+ mIntrinsicWidth = NS_INTRINSIC_ISIZE_UNKNOWN;
+ nsContainerFrame::MarkIntrinsicISizesDirty();
+}
+
+void nsMathMLContainerFrame::UpdateIntrinsicWidth(
+ gfxContext* aRenderingContext) {
+ if (mIntrinsicWidth == NS_INTRINSIC_ISIZE_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(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ UpdateIntrinsicWidth(aRenderingContext);
+ result = mIntrinsicWidth;
+ return result;
+}
+
+/* virtual */
+nscoord nsMathMLContainerFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ UpdateIntrinsicWidth(aRenderingContext);
+ result = mIntrinsicWidth;
+ return result;
+}
+
+/* virtual */
+void nsMathMLContainerFrame::GetIntrinsicISizeMetrics(
+ gfxContext* 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, IntrinsicISizeType::PrefISize);
+
+ 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)) {
+ PlaceForError(aRenderingContext->GetDrawTarget(), false, aDesiredSize);
+ }
+
+ ClearSavedChildMetrics();
+}
+
+/* virtual */
+nsresult nsMathMLContainerFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) {
+ return Place(aDrawTarget, false, aDesiredSize);
+}
+
+// see spacing table in Chapter 18, TeXBook (p.170)
+// Our table isn't quite identical to TeX because operators have
+// built-in values for lspace & rspace in the Operator Dictionary.
+static int32_t
+ kInterFrameSpacingTable[eMathMLFrameType_COUNT][eMathMLFrameType_COUNT] = {
+ // in units of muspace.
+ // upper half of the byte is set if the
+ // spacing is not to be used for scriptlevel > 0
+
+ /* Ord OpOrd OpInv OpUsr Inner Italic Upright */
+ /*Ord */ {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00},
+ /*OpOrd */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ /*OpInv */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ /*OpUsr */ {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01},
+ /*Inner */ {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01},
+ /*Italic */ {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01},
+ /*Upright*/ {0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00}};
+
+#define GET_INTERSPACE(scriptlevel_, frametype1_, frametype2_, space_) \
+ /* no space if there is a frame that we know nothing about */ \
+ if (frametype1_ == eMathMLFrameType_UNKNOWN || \
+ frametype2_ == eMathMLFrameType_UNKNOWN) \
+ space_ = 0; \
+ else { \
+ space_ = kInterFrameSpacingTable[frametype1_][frametype2_]; \
+ space_ = (scriptlevel_ > 0 && (space_ & 0xF0)) \
+ ? 0 /* spacing is disabled */ \
+ : space_ & 0x0F; \
+ }
+
+// This function computes the inter-space between two frames. However,
+// since invisible operators need special treatment, the inter-space may
+// be delayed when an invisible operator is encountered. In this case,
+// the function will carry the inter-space forward until it is determined
+// that it can be applied properly (i.e., until we encounter a visible
+// frame where to decide whether to accept or reject the inter-space).
+// aFromFrameType: remembers the frame when the carry-forward initiated.
+// aCarrySpace: keeps track of the inter-space that is delayed.
+// @returns: current inter-space (which is 0 when the true inter-space is
+// delayed -- and thus has no effect since the frame is invisible anyway).
+static nscoord GetInterFrameSpacing(int32_t aScriptLevel,
+ eMathMLFrameType aFirstFrameType,
+ eMathMLFrameType aSecondFrameType,
+ eMathMLFrameType* aFromFrameType, // IN/OUT
+ int32_t* aCarrySpace) // IN/OUT
+{
+ eMathMLFrameType firstType = aFirstFrameType;
+ eMathMLFrameType secondType = aSecondFrameType;
+
+ int32_t space;
+ GET_INTERSPACE(aScriptLevel, firstType, secondType, space);
+
+ // feedback control to avoid the inter-space to be added when not necessary
+ if (secondType == eMathMLFrameType_OperatorInvisible) {
+ // see if we should start to carry the space forward until we
+ // encounter a visible frame
+ if (*aFromFrameType == eMathMLFrameType_UNKNOWN) {
+ *aFromFrameType = firstType;
+ *aCarrySpace = space;
+ }
+ // keep carrying *aCarrySpace forward, while returning 0 for this stage
+ space = 0;
+ } else if (*aFromFrameType != eMathMLFrameType_UNKNOWN) {
+ // no carry-forward anymore, get the real inter-space between
+ // the two frames of interest
+
+ firstType = *aFromFrameType;
+
+ // But... the invisible operator that we encountered earlier could
+ // be sitting between italic and upright identifiers, e.g.,
+ //
+ // 1. <mi>sin</mi> <mo>&ApplyFunction;</mo> <mi>x</mi>
+ // 2. <mi>x</mi> <mo>&InvisibileTime;</mo> <mi>sin</mi>
+ //
+ // the trick to get the inter-space in either situation
+ // is to promote "<mi>sin</mi><mo>&ApplyFunction;</mo>" and
+ // "<mo>&InvisibileTime;</mo><mi>sin</mi>" to user-defined operators...
+ if (firstType == eMathMLFrameType_UprightIdentifier) {
+ firstType = eMathMLFrameType_OperatorUserDefined;
+ } else if (secondType == eMathMLFrameType_UprightIdentifier) {
+ secondType = eMathMLFrameType_OperatorUserDefined;
+ }
+
+ GET_INTERSPACE(aScriptLevel, firstType, secondType, space);
+
+ // Now, we have two values: the computed space and the space that
+ // has been carried forward until now. Which value do we pick?
+ // If the second type is an operator (e.g., fence), it already has
+ // built-in lspace & rspace, so we let them win. Otherwise we pick
+ // the max between the two values that we have.
+ if (secondType != eMathMLFrameType_OperatorOrdinary && space < *aCarrySpace)
+ space = *aCarrySpace;
+
+ // reset everything now that the carry-forward is done
+ *aFromFrameType = eMathMLFrameType_UNKNOWN;
+ *aCarrySpace = 0;
+ }
+
+ return space;
+}
+
+static nscoord GetThinSpace(const nsStyleFont* aStyleFont) {
+ return aStyleFont->mFont.size.ScaledBy(3.0f / 18.0f).ToAppUnits();
+}
+
+class nsMathMLContainerFrame::RowChildFrameIterator {
+ public:
+ explicit RowChildFrameIterator(nsMathMLContainerFrame* aParentFrame)
+ : mParentFrame(aParentFrame),
+ mReflowOutput(aParentFrame->GetWritingMode()),
+ mX(0),
+ mChildFrameType(eMathMLFrameType_UNKNOWN),
+ mCarrySpace(0),
+ mFromFrameType(eMathMLFrameType_UNKNOWN),
+ mRTL(aParentFrame->StyleVisibility()->mDirection ==
+ StyleDirection::Rtl) {
+ 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->mMathDepth, 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;
+}
+
+nsresult nsMathMLContainerFrame::PlaceForError(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) {
+ return StaticPrefs::mathml_error_message_layout_for_invalid_markup_disabled()
+ ? nsMathMLContainerFrame::Place(aDrawTarget, aPlaceOrigin,
+ aDesiredSize)
+ : ReflowError(aDrawTarget, aDesiredSize);
+}
+
+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, ReflowChildFlags::Default);
+ ++child;
+ }
+}
+
+// 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
+ ComputedStyle* parentContext = aParentFrame->Style();
+ nscoord thinSpace = GetThinSpace(parentContext->StyleFont());
+ // we are done
+ return space * thinSpace;
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ MOZ_ASSERT_UNREACHABLE("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()->mMathDepth, parent,
+ aFrame);
+ // add our own italic correction
+ nscoord leftCorrection = 0, italicCorrection = 0;
+ nsMathMLContainerFrame::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->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
+ // finish off principal descendants, too
+ nsIFrame* grandchild = frame->PrincipalChildList().FirstChild();
+ if (grandchild) DidReflowChildren(grandchild, nullptr);
+
+ frame->DidReflow(frame->PresContext(), nullptr);
+ }
+ }
+}
+
+// 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 nsTArray<nsString>& aParams) {
+ return nsContentUtils::ReportToConsole(
+ nsIScriptError::errorFlag, "Layout: MathML"_ns, mContent->OwnerDoc(),
+ nsContentUtils::eMATHML_PROPERTIES, errorMsgId, aParams);
+}
+
+nsresult nsMathMLContainerFrame::ReportParseError(const char16_t* aAttribute,
+ const char16_t* aValue) {
+ AutoTArray<nsString, 3> argv;
+ argv.AppendElement(aValue);
+ argv.AppendElement(aAttribute);
+ argv.AppendElement(nsDependentAtomString(mContent->NodeInfo()->NameAtom()));
+ return ReportErrorToConsole("AttributeParsingError", argv);
+}
+
+nsresult nsMathMLContainerFrame::ReportChildCountError() {
+ AutoTArray<nsString, 1> arg = {
+ nsDependentAtomString(mContent->NodeInfo()->NameAtom())};
+ return ReportErrorToConsole("ChildCountIncorrect", arg);
+}
+
+nsresult nsMathMLContainerFrame::ReportInvalidChildError(nsAtom* aChildTag) {
+ AutoTArray<nsString, 2> argv = {
+ nsDependentAtomString(aChildTag),
+ nsDependentAtomString(mContent->NodeInfo()->NameAtom())};
+ return ReportErrorToConsole("InvalidChild", argv);
+}
+
+//==========================
+
+nsContainerFrame* NS_NewMathMLmathBlockFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ auto newFrame = new (aPresShell)
+ nsMathMLmathBlockFrame(aStyle, aPresShell->GetPresContext());
+ newFrame->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
+ return newFrame;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathBlockFrame)
+
+NS_QUERYFRAME_HEAD(nsMathMLmathBlockFrame)
+ NS_QUERYFRAME_ENTRY(nsMathMLmathBlockFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
+
+nsContainerFrame* NS_NewMathMLmathInlineFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmathInlineFrame(aStyle, aPresShell->GetPresContext());
+}
+
+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..49afd1d1fa
--- /dev/null
+++ b/layout/mathml/nsMathMLContainerFrame.h
@@ -0,0 +1,516 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/*
+ * 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 {
+ public:
+ nsMathMLContainerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : nsContainerFrame(aStyle, aPresContext, aID),
+ mIntrinsicWidth(NS_INTRINSIC_ISIZE_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;
+ }
+
+ // --------------------------------------------------------------------------
+ // Overloaded nsContainerFrame methods -- see documentation in nsIFrame.h
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & (eLineParticipant | eSupportsContainLayoutAndPaint)) {
+ return false;
+ }
+ return nsContainerFrame::IsFrameOfType(aFlags & ~eMathML);
+ }
+
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ 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(gfxContext* aRenderingContext) override;
+ virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ /**
+ * Return the intrinsic horizontal metrics of the frame's content area.
+ */
+ virtual void GetIntrinsicISizeMetrics(gfxContext* aRenderingContext,
+ ReflowOutput& aDesiredSize);
+
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput) override
+
+ {
+ mPresentationData.flags &= ~NS_MATHML_STRETCH_DONE;
+ return nsContainerFrame::DidReflow(aPresContext, aReflowInput);
+ }
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ bool ComputeCustomOverflow(mozilla::OverflowAreas& 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, nsAtom* 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 == mozilla::StyleDirection::Rtl
+ ? 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:
+ /*
+ * Helper to render the frame as a default mrow-like container or as a visual
+ * feedback to the user when an error (typically invalid markup) was
+ * encountered during reflow. Parameters are the same as Place().
+ */
+ nsresult PlaceForError(DrawTarget* aDrawTarget, bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize);
+
+ // 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(nsAtom* aChildTag);
+
+ /*
+ * Helper to call ReportToConsole when an error occurs.
+ * @param aParams see nsContentUtils::ReportToConsole
+ */
+ nsresult ReportErrorToConsole(
+ const char* aErrorMsgId,
+ const nsTArray<nsString>& aParams = nsTArray<nsString>());
+
+ // 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(gfxContext* 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 final : public nsBlockFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmathBlockFrame)
+
+ friend nsContainerFrame* NS_NewMathMLmathBlockFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ // beware, mFrames is not set by nsBlockFrame
+ // cannot use mFrames{.FirstChild()|.etc} since the block code doesn't set
+ // mFrames
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override {
+ MOZ_ASSERT(aListID == mozilla::FrameChildListID::Principal ||
+ aListID == mozilla::FrameChildListID::Backdrop,
+ "unexpected frame list");
+ nsBlockFrame::SetInitialChildList(aListID, std::move(aChildList));
+ if (aListID == mozilla::FrameChildListID::Principal) {
+ // re-resolve our subtree to set any mathml-expected data
+ nsMathMLContainerFrame::RebuildAutomaticDataForChildren(this);
+ }
+ }
+
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override {
+ NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal ||
+ aListID == mozilla::FrameChildListID::NoReflowPrincipal,
+ "unexpected frame list");
+ nsBlockFrame::AppendFrames(aListID, std::move(aFrameList));
+ if (MOZ_LIKELY(aListID == mozilla::FrameChildListID::Principal))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override {
+ NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal ||
+ aListID == mozilla::FrameChildListID::NoReflowPrincipal,
+ "unexpected frame list");
+ nsBlockFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aFrameList));
+ if (MOZ_LIKELY(aListID == mozilla::FrameChildListID::Principal))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual void RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override {
+ NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal ||
+ aListID == mozilla::FrameChildListID::NoReflowPrincipal,
+ "unexpected frame list");
+ nsBlockFrame::RemoveFrame(aListID, aOldFrame);
+ if (MOZ_LIKELY(aListID == mozilla::FrameChildListID::Principal))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override {
+ return nsBlockFrame::IsFrameOfType(aFlags & ~nsIFrame::eMathML);
+ }
+
+ // See nsIMathMLFrame.h
+ bool IsMrowLike() {
+ return mFrames.FirstChild() != mFrames.LastChild() || !mFrames.FirstChild();
+ }
+
+ protected:
+ explicit nsMathMLmathBlockFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsBlockFrame(aStyle, aPresContext, kClassID) {
+ // 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() = default;
+};
+
+// --------------
+
+class nsMathMLmathInlineFrame final : public nsInlineFrame,
+ public nsMathMLFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmathInlineFrame)
+
+ friend nsContainerFrame* NS_NewMathMLmathInlineFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override {
+ NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal,
+ "unexpected frame list");
+ nsInlineFrame::SetInitialChildList(aListID, std::move(aChildList));
+ // re-resolve our subtree to set any mathml-expected data
+ nsMathMLContainerFrame::RebuildAutomaticDataForChildren(this);
+ }
+
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override {
+ NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal ||
+ aListID == mozilla::FrameChildListID::NoReflowPrincipal,
+ "unexpected frame list");
+ nsInlineFrame::AppendFrames(aListID, std::move(aFrameList));
+ if (MOZ_LIKELY(aListID == mozilla::FrameChildListID::Principal))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override {
+ NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal ||
+ aListID == mozilla::FrameChildListID::NoReflowPrincipal,
+ "unexpected frame list");
+ nsInlineFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aFrameList));
+ if (MOZ_LIKELY(aListID == mozilla::FrameChildListID::Principal))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual void RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override {
+ NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal ||
+ aListID == mozilla::FrameChildListID::NoReflowPrincipal,
+ "unexpected frame list");
+ nsInlineFrame::RemoveFrame(aListID, aOldFrame);
+ if (MOZ_LIKELY(aListID == mozilla::FrameChildListID::Principal))
+ nsMathMLContainerFrame::ReLayoutChildren(this);
+ }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override {
+ return nsInlineFrame::IsFrameOfType(aFlags & ~nsIFrame::eMathML);
+ }
+
+ bool IsMrowLike() override {
+ return mFrames.FirstChild() != mFrames.LastChild() || !mFrames.FirstChild();
+ }
+
+ protected:
+ explicit nsMathMLmathInlineFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsInlineFrame(aStyle, aPresContext, kClassID) {}
+
+ virtual ~nsMathMLmathInlineFrame() = default;
+};
+
+#endif /* nsMathMLContainerFrame_h___ */
diff --git a/layout/mathml/nsMathMLFrame.cpp b/layout/mathml/nsMathMLFrame.cpp
new file mode 100644
index 0000000000..9a7d94a992
--- /dev/null
+++ b/layout/mathml/nsMathMLFrame.cpp
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "nsCSSValue.h"
+#include "nsLayoutUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsMathMLChar.h"
+#include "nsCSSPseudoElements.h"
+#include "mozilla/dom/MathMLElement.h"
+#include "gfxMathTable.h"
+#include "nsPresContextInlines.h"
+
+// used to map attributes into CSS rules
+#include "mozilla/ServoStyleSet.h"
+#include "nsDisplayList.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;
+}
+
+/* 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) {
+ RefPtr<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,
+ ComputedStyle* aComputedStyle,
+ const nsCSSValue& aCSSValue,
+ float aFontSizeInflation) {
+ NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit");
+
+ if (aCSSValue.IsPixelLengthUnit()) {
+ return aCSSValue.GetPixelLength();
+ }
+
+ nsCSSUnit unit = aCSSValue.GetUnit();
+
+ if (eCSSUnit_EM == unit) {
+ const nsStyleFont* font = aComputedStyle->StyleFont();
+ return font->mFont.size.ScaledBy(aCSSValue.GetFloatValue()).ToAppUnits();
+ }
+
+ if (eCSSUnit_XHeight == unit) {
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle(
+ aComputedStyle, aPresContext, 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::GetSubDropFromChild(nsIFrame* aChild, nscoord& aSubDrop,
+ float aFontSizeInflation) {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation);
+ GetSubDrop(fm, aSubDrop);
+}
+
+/* static */
+void nsMathMLFrame::GetSupDropFromChild(nsIFrame* aChild, nscoord& aSupDrop,
+ float aFontSizeInflation) {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation);
+ GetSupDrop(fm, aSupDrop);
+}
+
+/* static */
+void nsMathMLFrame::ParseNumericValue(const nsString& aString,
+ nscoord* aLengthValue, uint32_t aFlags,
+ nsPresContext* aPresContext,
+ ComputedStyle* aComputedStyle,
+ float aFontSizeInflation) {
+ nsCSSValue cssValue;
+
+ if (!dom::MathMLElement::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, aComputedStyle, cssValue, aFontSizeInflation);
+}
+
+namespace mozilla {
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+class nsDisplayMathMLBoundingMetrics final : public nsDisplayItem {
+ public:
+ nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, const nsRect& aRect)
+ : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLBoundingMetrics);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLBoundingMetrics)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLBoundingMetrics", TYPE_MATHML_BOUNDING_METRICS)
+ private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLBoundingMetrics::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* 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<nsDisplayMathMLBoundingMetrics>(
+ aBuilder, aFrame, nsRect(x, y, w, h));
+}
+#endif
+
+class nsDisplayMathMLBar final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
+ MOZ_COUNT_CTOR(nsDisplayMathMLBar);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLBar)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR)
+
+ private:
+ nsRect mRect;
+};
+
+void nsDisplayMathMLBar::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* 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(&nsStyleText::mWebkitTextFillColor)));
+ drawTarget->FillRect(rect, color);
+}
+
+} // namespace mozilla
+
+void nsMathMLFrame::DisplayBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect,
+ const nsDisplayListSet& aLists,
+ uint32_t aIndex) {
+ if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty()) return;
+
+ aLists.Content()->AppendNewToTopWithIndex<nsDisplayMathMLBar>(
+ aBuilder, aFrame, aIndex, aRect);
+}
+
+void nsMathMLFrame::GetRadicalParameters(nsFontMetrics* aFontMetrics,
+ bool aDisplayStyle,
+ nscoord& aRadicalRuleThickness,
+ nscoord& aRadicalExtraAscender,
+ nscoord& aRadicalVerticalGap) {
+ nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel();
+ RefPtr<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..de814616ef
--- /dev/null
+++ b/layout/mathml/nsMathMLFrame.h
@@ -0,0 +1,298 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsBoundingMetrics.h"
+#include "nsIFrame.h"
+
+class nsMathMLChar;
+class nsCSSValue;
+
+namespace mozilla {
+class nsDisplayListBuilder;
+class nsDisplayListSet;
+} // namespace mozilla
+
+// 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 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,
+ mozilla::ComputedStyle* aComputedStyle,
+ float aFontSizeInflation);
+
+ static nscoord CalcLength(nsPresContext* aPresContext,
+ mozilla::ComputedStyle* aComputedStyle,
+ 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);
+
+ static void GetSupDropFromChild(nsIFrame* aChild, nscoord& aSupDrop,
+ float aFontSizeInflation);
+
+ 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 = fm->Font().size.ToAppUnits();
+#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(mozilla::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(mozilla::nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect, const mozilla::nsDisplayListSet& aLists,
+ uint32_t aIndex = 0);
+
+ // 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..4a78587eb8
--- /dev/null
+++ b/layout/mathml/nsMathMLOperators.cpp
@@ -0,0 +1,431 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsNetUtil.h"
+#include "nsTArray.h"
+
+#include "mozilla/intl/UnicodeProperties.h"
+#include "nsIPersistentProperties2.h"
+#include "nsISimpleEnumerator.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 nsTHashMap<nsStringHashKey, OperatorData*>* gOperatorTable = nullptr;
+static bool gGlobalsInitialized = false;
+
+static const char16_t kDashCh = char16_t('#');
+static const char16_t kColonCh = char16_t(':');
+
+static uint32_t ToUnicodeCodePoint(const nsString& aOperator) {
+ if (aOperator.Length() == 1) {
+ return aOperator[0];
+ }
+ if (aOperator.Length() == 2 &&
+ NS_IS_SURROGATE_PAIR(aOperator[0], aOperator[1])) {
+ return SURROGATE_TO_UCS4(aOperator[0], aOperator[1]);
+ }
+ return 0;
+}
+
+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;
+}
+
+static void SetProperty(OperatorData* aOperatorData, nsString aName,
+ nsString aValue) {
+ if (aName.IsEmpty() || aValue.IsEmpty()) return;
+
+ 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->InsertOrUpdate(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),
+ "resource://gre/res/fonts/mathfont.properties"_ns);
+
+ 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];
+ }
+ 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 nsTHashMap<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,
+ const uint8_t aForm) {
+ nsAutoString key(aOperator);
+ key.AppendInt(aForm);
+ return gOperatorTable->Get(key);
+}
+
+bool nsMathMLOperators::LookupOperator(const nsString& aOperator,
+ const uint8_t aForm,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace) {
+ NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage");
+ NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***");
+
+ // Operator strings must be of length 1 or 2 in UTF-16.
+ // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator
+ if (aOperator.IsEmpty() || aOperator.Length() > 2) {
+ return false;
+ }
+
+ if (aOperator.Length() == 2) {
+ // Try and handle Arabic operators.
+ // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator
+ if (auto codePoint = ToUnicodeCodePoint(aOperator)) {
+ if (aForm == NS_MATHML_OPERATOR_FORM_POSTFIX &&
+ (codePoint == 0x1EEF0 || codePoint == 0x1EEF1)) {
+ // Use category I.
+ // https://w3c.github.io/mathml-core/#operator-dictionary-categories-values
+ *aFlags = NS_MATHML_OPERATOR_FORM_POSTFIX |
+ NS_MATHML_OPERATOR_STRETCHY |
+ NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL;
+ *aLeadingSpace = 0;
+ *aTrailingSpace = 0;
+ return true;
+ }
+ return false;
+ }
+
+ // Ignore the combining "negation" suffix for 2-character strings.
+ // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator
+ if (aOperator[1] == 0x0338 || aOperator[1] == 0x20D2) {
+ nsAutoString newOperator;
+ newOperator.Append(aOperator[0]);
+ return LookupOperator(newOperator, aForm, aFlags, aLeadingSpace,
+ aTrailingSpace);
+ }
+ }
+
+ if (!gGlobalsInitialized) {
+ InitOperatorGlobals();
+ }
+ if (gOperatorTable) {
+ if (OperatorData* data = GetOperatorData(aOperator, aForm)) {
+ NS_ASSERTION(data->mStr.Equals(aOperator), "bad setup");
+ *aFlags = data->mFlags;
+ *aLeadingSpace = data->mLeadingSpace;
+ *aTrailingSpace = data->mTrailingSpace;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool nsMathMLOperators::LookupOperatorWithFallback(const nsString& aOperator,
+ const uint8_t aForm,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace) {
+ if (LookupOperator(aOperator, aForm, aFlags, aLeadingSpace, aTrailingSpace)) {
+ return true;
+ }
+ for (const auto& form :
+ {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX,
+ NS_MATHML_OPERATOR_FORM_PREFIX}) {
+ if (form == aForm) {
+ // This form was tried above, skip it.
+ continue;
+ }
+ if (LookupOperator(aOperator, form, aFlags, aLeadingSpace,
+ aTrailingSpace)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator) {
+ if (auto codePoint = ToUnicodeCodePoint(aOperator)) {
+ return mozilla::intl::UnicodeProperties::IsMirrored(codePoint);
+ }
+ return false;
+}
+
+/* static */
+bool nsMathMLOperators::IsIntegralOperator(const nsString& aOperator) {
+ if (auto codePoint = ToUnicodeCodePoint(aOperator)) {
+ return (0x222B <= codePoint && codePoint <= 0x2233) ||
+ (0x2A0B <= codePoint && codePoint <= 0x2A1C);
+ }
+ return false;
+}
+
+/* static */
+nsStretchDirection nsMathMLOperators::GetStretchyDirection(
+ const nsString& aOperator) {
+ // Search any entry for that operator and return the corresponding direction.
+ // It is assumed that all the forms have same direction.
+ for (const auto& form :
+ {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX,
+ NS_MATHML_OPERATOR_FORM_PREFIX}) {
+ nsOperatorFlags flags;
+ float dummy;
+ if (nsMathMLOperators::LookupOperator(aOperator, form, &flags, &dummy,
+ &dummy)) {
+ if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) {
+ return NS_STRETCH_DIRECTION_VERTICAL;
+ }
+ if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) {
+ return NS_STRETCH_DIRECTION_HORIZONTAL;
+ }
+ }
+ }
+ return NS_STRETCH_DIRECTION_UNSUPPORTED;
+}
diff --git a/layout/mathml/nsMathMLOperators.h b/layout/mathml/nsMathMLOperators.h
new file mode 100644
index 0000000000..2fca9360dc
--- /dev/null
+++ b/layout/mathml/nsMathMLOperators.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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,
+
+ // Additional bits not stored in the dictionary
+ NS_MATHML_OPERATOR_MINSIZE_ABSOLUTE = 1 << 11,
+ NS_MATHML_OPERATOR_MAXSIZE_ABSOLUTE = 1 << 12,
+ NS_MATHML_OPERATOR_LSPACE_ATTR = 1 << 13,
+ NS_MATHML_OPERATOR_RSPACE_ATTR = 1 << 14
+};
+
+#define NS_MATHML_OPERATOR_SIZE_INFINITY (mozilla::PositiveInfinity<float>())
+
+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 attributes of the operator in the output
+ // parameters. The return value indicates whether an entry was found.
+ static bool LookupOperator(const nsString& aOperator, const uint8_t aForm,
+ nsOperatorFlags* aFlags, float* aLeadingSpace,
+ float* aTrailingSpace);
+
+ // LookupOperatorWithFallback:
+ // Same as LookupOperator but if the operator is not found under the supplied
+ // form, then the other forms are tried in the following order: infix, postfix
+ // prefix. The caller can test the output parameter aFlags to know exactly
+ // under which form the operator was found in the Operator Dictionary.
+ static bool LookupOperatorWithFallback(const nsString& aOperator,
+ const uint8_t aForm,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace);
+
+ // Helper functions used by the nsMathMLChar class.
+ static bool IsMirrorableOperator(const nsString& aOperator);
+
+ // Helper functions used by the nsMathMLChar class to determine whether
+ // aOperator corresponds to an integral operator.
+ static bool IsIntegralOperator(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_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..f0c4553958
--- /dev/null
+++ b/layout/mathml/nsMathMLParts.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+// Factory methods for creating MathML objects
+nsIFrame* NS_NewMathMLTokenFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmoFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmrowFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmpaddedFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmspaceFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmsFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmfracFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmsubFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmsupFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmsubsupFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmunderoverFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmmultiscriptsFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewMathMLmtableOuterFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewMathMLmtableFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewMathMLmtrFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewMathMLmtdFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle,
+ nsTableFrame* aTableFrame);
+nsContainerFrame* NS_NewMathMLmtdInnerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmsqrtFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmrootFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmactionFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLmencloseFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMathMLsemanticsFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+nsContainerFrame* NS_NewMathMLmathBlockFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewMathMLmathInlineFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+#endif /* nsMathMLParts_h___ */
diff --git a/layout/mathml/nsMathMLSelectedFrame.cpp b/layout/mathml/nsMathMLSelectedFrame.cpp
new file mode 100644
index 0000000000..924fed60e6
--- /dev/null
+++ b/layout/mathml/nsMathMLSelectedFrame.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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() = default;
+
+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, std::move(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 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, 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, set);
+ }
+
+#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
+ // visual debug
+ DisplayBoundingMetrics(aBuilder, this, mReference, mBoundingMetrics, aLists);
+#endif
+}
+
+/* virtual */
+nsIFrame::SizeComputationResult nsMathMLSelectedFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ 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...
+ const nscoord availableISize =
+ aAvailableISize - aBorderPadding.ISize(aWM) - aMargin.ISize(aWM);
+ const LogicalSize cbSize = aCBSize - aBorderPadding - aMargin;
+ SizeComputationInput offsetState(childFrame, aRenderingContext, aWM,
+ availableISize);
+ const auto bpSize = offsetState.ComputedLogicalBorderPadding(aWM).Size(aWM);
+ auto size = childFrame->ComputeSize(
+ aRenderingContext, aWM, cbSize, availableISize,
+ offsetState.ComputedLogicalMargin(aWM).Size(aWM), bpSize,
+ aSizeOverrides, aFlags);
+ return {size.mLogicalSize + bpSize, size.mAspectRatioUsage};
+ }
+ return {LogicalSize(aWM), AspectRatioUsage::None};
+}
+
+// Only reflow the selected child ...
+void nsMathMLSelectedFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ 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);
+}
+
+// Only place the selected child ...
+/* virtual */
+nsresult nsMathMLSelectedFrame::Place(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) {
+ nsIFrame* childFrame = GetSelectedFrame();
+
+ if (mInvalidMarkup) {
+ // Calling PlaceForError when mathml.error_message_layout_for_invalid_markup
+ // is disabled causes assertion failures because nsMathMLSelectedFrame only
+ // performs layout of the selected child. However, this code is only reached
+ // when mathml.legacy_maction_and_semantics_implementations is enabled, so
+ // it is out the scope of the mrow fallback described in MathML Core and
+ // nsMathMLSelectedFrame will go away in the future. So for now let's
+ // continue to always layout this case as an 'invalid-markup' message.
+ 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,
+ ReflowChildFlags::Default);
+ }
+ 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..97f3fa6b66
--- /dev/null
+++ b/layout/mathml/nsMathMLSelectedFrame.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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:
+ NS_DECL_ABSTRACT_FRAME(nsMathMLSelectedFrame)
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+
+ virtual nsresult ChildListChanged(int32_t aModType) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsresult Place(DrawTarget* aDrawTarget, bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ protected:
+ nsMathMLSelectedFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : nsMathMLContainerFrame(aStyle, aPresContext, aID),
+ mSelectedFrame(nullptr),
+ mInvalidMarkup(false) {}
+ virtual ~nsMathMLSelectedFrame();
+
+ virtual nsIFrame* GetSelectedFrame() = 0;
+ nsIFrame* mSelectedFrame;
+
+ bool mInvalidMarkup;
+};
+
+#endif /* nsMathMLSelectedFrame_h___ */
diff --git a/layout/mathml/nsMathMLTokenFrame.cpp b/layout/mathml/nsMathMLTokenFrame.cpp
new file mode 100644
index 0000000000..5481e6faec
--- /dev/null
+++ b/layout/mathml/nsMathMLTokenFrame.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/PresShell.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsContentUtils.h"
+#include "nsTextFrame.h"
+#include "gfxContext.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+nsIFrame* NS_NewMathMLTokenFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLTokenFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLTokenFrame)
+
+nsMathMLTokenFrame::~nsMathMLTokenFrame() = default;
+
+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;
+ }
+
+ StyleMathVariant mathVariant = StyleFont()->mMathVariant;
+ if ((mathVariant == StyleMathVariant::None &&
+ (StyleFont()->mFont.style.IsItalic() ||
+ HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI))) ||
+ mathVariant == StyleMathVariant::Italic ||
+ mathVariant == StyleMathVariant::BoldItalic ||
+ mathVariant == StyleMathVariant::SansSerifItalic ||
+ mathVariant == StyleMathVariant::SansSerifBoldItalic) {
+ 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->IsTextFrame()) {
+ 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, std::move(aChildList));
+ MarkTextFramesAsTokenMathML();
+}
+
+void nsMathMLTokenFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ nsMathMLContainerFrame::AppendFrames(aListID, std::move(aChildList));
+ MarkTextFramesAsTokenMathML();
+}
+
+void nsMathMLTokenFrame::InsertFrames(
+ ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aChildList) {
+ nsMathMLContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aChildList));
+ MarkTextFramesAsTokenMathML();
+}
+
+void nsMathMLTokenFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ 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());
+ WritingMode wm = childFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput childReflowInput(aPresContext, aReflowInput, childFrame,
+ availSize);
+ nsReflowStatus childStatus;
+ ReflowChild(childFrame, aPresContext, childDesiredSize, childReflowInput,
+ childStatus);
+ NS_ASSERTION(childStatus.IsComplete(),
+ "We gave the child unconstrained available block-size, so its "
+ "status should be complete!");
+ SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
+ childDesiredSize.mBoundingMetrics);
+ }
+
+ // place and size children
+ FinalizeReflow(aReflowInput.mRenderingContext->GetDrawTarget(), aDesiredSize);
+
+ aStatus.Reset(); // This type of frame can't be split.
+}
+
+// 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,
+ ReflowChildFlags::Default);
+ 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..e369e7876d
--- /dev/null
+++ b/layout/mathml/nsMathMLTokenFrame.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// Base class to handle token elements
+//
+
+class nsMathMLTokenFrame : public nsMathMLContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLTokenFrame)
+
+ friend nsIFrame* NS_NewMathMLTokenFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ 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;
+
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+
+ void AppendFrames(ChildListID aListID, nsFrameList&& aChildList) override;
+
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ 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(ComputedStyle* aStyle,
+ nsPresContext* aPresContext,
+ ClassID aID = kClassID)
+ : nsMathMLContainerFrame(aStyle, aPresContext, aID) {}
+ 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..6da6f88906
--- /dev/null
+++ b/layout/mathml/nsMathMLmactionFrame.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsDocShell.h"
+#include "nsPresContext.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsTextFragment.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+
+using namespace mozilla;
+using mozilla::dom::Event;
+
+//
+// <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->IsElement() ||
+ !aContent->AsElement()->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(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmactionFrame(aStyle, aPresShell->GetPresContext());
+}
+
+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(u"click"_ns, mListener, false);
+ mContent->RemoveSystemEventListener(u"mouseover"_ns, mListener, false);
+ mContent->RemoveSystemEventListener(u"mouseout"_ns, 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->AsElement()->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, std::move(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(u"click"_ns, mListener, false, false);
+ mContent->AddSystemEventListener(u"mouseover"_ns, mListener, false, false);
+ mContent->AddSystemEventListener(u"mouseout"_ns, mListener, false, false);
+ }
+}
+
+nsresult nsMathMLmactionFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ bool needsReflow = false;
+
+ InvalidateFrame();
+
+ 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) {
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
+ 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 ...
+static 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->SetLinkStatus(aStatusMsg);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsMathMLmactionFrame::MouseListener::HandleEvent(Event* 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 {
+ MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
+ }
+
+ 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->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::selection_,
+ value, notify);
+
+ // Now trigger a content-changed reflow...
+ PresShell()->FrameNeedsReflow(
+ mSelectedFrame, IntrinsicDirty::FrameAndAncestors, NS_FRAME_IS_DIRTY);
+ }
+ }
+}
diff --git a/layout/mathml/nsMathMLmactionFrame.h b/layout/mathml/nsMathMLmactionFrame.h
new file mode 100644
index 0000000000..c2fd7089d1
--- /dev/null
+++ b/layout/mathml/nsMathMLmactionFrame.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <maction> -- bind actions to a subexpression
+//
+
+class nsMathMLmactionFrame final : public nsMathMLSelectedFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmactionFrame)
+
+ friend nsIFrame* NS_NewMathMLmactionFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+
+ virtual nsresult ChildListChanged(int32_t aModType) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ private:
+ void MouseClick();
+ void MouseOver();
+ void MouseOut();
+
+ class MouseListener final : public nsIDOMEventListener {
+ private:
+ ~MouseListener() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ explicit MouseListener(nsMathMLmactionFrame* aOwner) : mOwner(aOwner) {}
+
+ nsMathMLmactionFrame* mOwner;
+ };
+
+ protected:
+ explicit nsMathMLmactionFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLSelectedFrame(aStyle, aPresContext, kClassID) {}
+ 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..1a907c783a
--- /dev/null
+++ b/layout/mathml/nsMathMLmencloseFrame.cpp
@@ -0,0 +1,828 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.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(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmencloseFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmencloseFrame)
+
+nsMathMLmencloseFrame::nsMathMLmencloseFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext,
+ ClassID aID)
+ : nsMathMLContainerFrame(aStyle, aPresContext, aID),
+ mRuleThickness(0),
+ mRadicalRuleThickness(0),
+ mLongDivCharIndex(-1),
+ mRadicalCharIndex(-1),
+ mContentWidth(0) {}
+
+nsMathMLmencloseFrame::~nsMathMLmencloseFrame() = default;
+
+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 ComputedStyle given to our MathML chars.
+ // The Style System will use Get/SetAdditionalComputedStyle() to keep it
+ // up-to-date if dynamic changes arise.
+ uint32_t i = mMathMLChar.Length();
+ nsAutoString Char;
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ mMathMLChar.AppendElement();
+
+ if (mask == NOTATION_LONGDIV) {
+ Char.Assign(kLongDivChar);
+ mLongDivCharIndex = i;
+ } else if (mask == NOTATION_RADICAL) {
+ Char.Assign(kRadicalChar);
+ mRadicalCharIndex = i;
+ }
+
+ mMathMLChar[i].SetData(Char);
+ mMathMLChar[i].SetComputedStyle(Style());
+
+ 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;
+ mNotationsToDraw += NOTATION_TOP;
+ } else if (aNotation.EqualsLiteral("box")) {
+ mNotationsToDraw += NOTATION_LEFT;
+ mNotationsToDraw += NOTATION_RIGHT;
+ mNotationsToDraw += NOTATION_TOP;
+ mNotationsToDraw += 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;
+ mNotationsToDraw += NOTATION_BOTTOM;
+ } else if (aNotation.EqualsLiteral("phasorangle")) {
+ mNotationsToDraw += NOTATION_BOTTOM;
+ mNotationsToDraw += NOTATION_PHASORANGLE;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Initialize the list of notations to draw
+ */
+void nsMathMLmencloseFrame::InitNotations() {
+ MarkNeedsDisplayItemRebuild();
+ mNotationsToDraw.clear();
+ mLongDivCharIndex = mRadicalCharIndex = -1;
+ mMathMLChar.Clear();
+
+ nsAutoString value;
+
+ if (mContent->AsElement()->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 nsDisplayListSet& aLists) {
+ /////////////
+ // paint the menclosed content
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, 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 == StyleDirection::Rtl
+ ? -mContentWidth
+ : rect.width,
+ 0);
+ rect.SizeTo(mContentWidth, mRadicalRuleThickness);
+ DisplayBar(aBuilder, this, rect, aLists, NOTATION_RADICAL);
+ }
+
+ 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, NOTATION_LONGDIV);
+ }
+
+ if (IsToDraw(NOTATION_TOP)) {
+ nsRect rect(0, 0, mencloseRect.width, mRuleThickness);
+ DisplayBar(aBuilder, this, rect, aLists, NOTATION_TOP);
+ }
+
+ if (IsToDraw(NOTATION_BOTTOM)) {
+ nsRect rect(0, mencloseRect.height - mRuleThickness, mencloseRect.width,
+ mRuleThickness);
+ DisplayBar(aBuilder, this, rect, aLists, NOTATION_BOTTOM);
+ }
+
+ if (IsToDraw(NOTATION_LEFT)) {
+ nsRect rect(0, 0, mRuleThickness, mencloseRect.height);
+ DisplayBar(aBuilder, this, rect, aLists, NOTATION_LEFT);
+ }
+
+ if (IsToDraw(NOTATION_RIGHT)) {
+ nsRect rect(mencloseRect.width - mRuleThickness, 0, mRuleThickness,
+ mencloseRect.height);
+ DisplayBar(aBuilder, this, rect, aLists, NOTATION_RIGHT);
+ }
+
+ 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, NOTATION_HORIZONTALSTRIKE);
+ }
+
+ if (IsToDraw(NOTATION_VERTICALSTRIKE)) {
+ nsRect rect(mencloseRect.width / 2 - mRuleThickness / 2, 0, mRuleThickness,
+ mencloseRect.height);
+ DisplayBar(aBuilder, this, rect, aLists, NOTATION_VERTICALSTRIKE);
+ }
+}
+
+/* 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()->mMathStyle == StyleMathStyle::Normal,
+ 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(
+ this, 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(
+ this, 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 == StyleDirection::Rtl
+ ? &dx_right
+ : &dx_left;
+
+ if (aWidthOnly) {
+ nscoord radical_width = mMathMLChar[mRadicalCharIndex].GetMaxWidth(
+ this, 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(
+ this, aDrawTarget, fontSizeInflation, NS_STRETCH_DIRECTION_VERTICAL,
+ contSize, bmRadicalChar, NS_STRETCH_LARGER,
+ StyleVisibility()->mDirection == StyleDirection::Rtl);
+ 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 == StyleDirection::Rtl
+ ? 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,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aAttribute == nsGkAtoms::notation_) {
+ InitNotations();
+ }
+
+ return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+void nsMathMLmencloseFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
+ nsMathMLContainerFrame::DidSetComputedStyle(aOldStyle);
+ for (auto& ch : mMathMLChar) {
+ ch.SetComputedStyle(Style());
+ }
+}
+
+//////////////////
+
+namespace mozilla {
+
+class nsDisplayNotation final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayNotation(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect, nscoord aThickness,
+ nsMencloseNotation aType)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mRect(aRect),
+ mThickness(aThickness),
+ mType(aType) {
+ MOZ_COUNT_CTOR(nsDisplayNotation);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayNotation)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("MathMLMencloseNotation", TYPE_MATHML_MENCLOSE_NOTATION)
+
+ private:
+ nsRect mRect;
+ nscoord mThickness;
+ nsMencloseNotation mType;
+};
+
+void nsDisplayNotation::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* 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(&nsStyleText::mWebkitTextFillColor)));
+
+ 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:
+ MOZ_ASSERT_UNREACHABLE(
+ "This notation can not be drawn using "
+ "nsDisplayNotation");
+ }
+}
+
+} // namespace mozilla
+
+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;
+
+ const uint16_t index = aType;
+ aLists.Content()->AppendNewToTopWithIndex<nsDisplayNotation>(
+ aBuilder, aFrame, index, aRect, aThickness, aType);
+}
diff --git a/layout/mathml/nsMathMLmencloseFrame.h b/layout/mathml/nsMathMLmencloseFrame.h
new file mode 100644
index 0000000000..7465a4342e
--- /dev/null
+++ b/layout/mathml/nsMathMLmencloseFrame.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/EnumSet.h"
+#include "nsMathMLChar.h"
+#include "nsMathMLContainerFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <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,
+ NOTATION_RADICAL,
+ NOTATION_ROUNDEDBOX,
+ NOTATION_CIRCLE,
+ NOTATION_LEFT,
+ NOTATION_RIGHT,
+ NOTATION_TOP,
+ NOTATION_BOTTOM,
+ NOTATION_UPDIAGONALSTRIKE,
+ NOTATION_DOWNDIAGONALSTRIKE,
+ NOTATION_VERTICALSTRIKE,
+ NOTATION_HORIZONTALSTRIKE,
+ NOTATION_UPDIAGONALARROW,
+ NOTATION_PHASORANGLE
+};
+
+class nsMathMLmencloseFrame : public nsMathMLContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmencloseFrame)
+
+ friend nsIFrame* NS_NewMathMLmencloseFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ virtual nsresult Place(DrawTarget* aDrawTarget, bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual nsresult MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ void DidSetComputedStyle(ComputedStyle* aOldStyle) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ 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(ComputedStyle* aStyle,
+ nsPresContext* aPresContext,
+ ClassID aID = kClassID);
+ 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
+ mozilla::EnumSet<nsMencloseNotation> mNotationsToDraw;
+ bool IsToDraw(nsMencloseNotation notation) {
+ return mNotationsToDraw.contains(notation);
+ }
+
+ 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/nsMathMLmfracFrame.cpp b/layout/mathml/nsMathMLmfracFrame.cpp
new file mode 100644
index 0000000000..613d8d49d2
--- /dev/null
+++ b/layout/mathml/nsMathMLmfracFrame.cpp
@@ -0,0 +1,373 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/PresShell.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsDisplayList.h"
+#include "gfxContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MathMLElement.h"
+#include <algorithm>
+#include "gfxMathTable.h"
+#include "gfxTextRun.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+//
+// <mfrac> -- form a fraction from two subexpressions - implementation
+//
+
+nsIFrame* NS_NewMathMLmfracFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmfracFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfracFrame)
+
+nsMathMLmfracFrame::~nsMathMLmfracFrame() = default;
+
+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()->mMathStyle == StyleMathStyle::Compact && 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()->mMathStyle == StyleMathStyle::Compact) {
+ 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,
+ ComputedStyle* aComputedStyle,
+ nsString& aThicknessAttribute,
+ nscoord onePixel,
+ nscoord aDefaultRuleThickness,
+ float aFontSizeInflation) {
+ nscoord defaultThickness = aDefaultRuleThickness;
+ nscoord lineThickness = aDefaultRuleThickness;
+ nscoord minimumThickness = onePixel;
+
+ // linethickness
+ // https://w3c.github.io/mathml-core/#dfn-linethickness
+ if (!aThicknessAttribute.IsEmpty()) {
+ lineThickness = defaultThickness;
+ ParseNumericValue(aThicknessAttribute, &lineThickness, 0, aPresContext,
+ aComputedStyle, 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 nsDisplayListSet& aLists) {
+ /////////////
+ // paint the numerator and denominator
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists);
+
+ /////////////
+ // paint the fraction line
+ DisplayBar(aBuilder, this, mLineRect, aLists);
+}
+
+nsresult nsMathMLmfracFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* 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 PlaceForError(aDrawTarget, aPlaceOrigin, 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();
+ RefPtr<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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::linethickness_,
+ value);
+ mLineThickness =
+ CalcLineThickness(presContext, mComputedStyle, value, onePixel,
+ defaultRuleThickness, fontSizeInflation);
+
+ bool displayStyle = StyleFont()->mMathStyle == StyleMathStyle::Normal;
+
+ 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) {
+ const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
+ nsEmbellishData coreData;
+ GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
+ leftSpace += isRTL ? coreData.trailingSpace : coreData.leadingSpace;
+ rightSpace += isRTL ? 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);
+ }
+
+ 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;
+
+ 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,
+ ReflowChildFlags::Default);
+ // place denominator
+ dy = aDesiredSize.Height() - sizeDen.Height();
+ FinishReflowChild(frameDen, presContext, sizeDen, nullptr, dxDen, dy,
+ ReflowChildFlags::Default);
+ // place the fraction bar - dy is top of bar
+ dy = aDesiredSize.BlockStartAscent() -
+ (axisHeight + actualRuleThickness / 2);
+ mLineRect.SetRect(leftSpace, dy, width - (leftSpace + rightSpace),
+ actualRuleThickness);
+ }
+
+ return NS_OK;
+}
diff --git a/layout/mathml/nsMathMLmfracFrame.h b/layout/mathml/nsMathMLmfracFrame.h
new file mode 100644
index 0000000000..c457c96aa0
--- /dev/null
+++ b/layout/mathml/nsMathMLmfracFrame.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <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 final : public nsMathMLContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmfracFrame)
+
+ friend nsIFrame* NS_NewMathMLmfracFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ 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 nsDisplayListSet& aLists) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* 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
+ nscoord CalcLineThickness(nsPresContext* aPresContext,
+ ComputedStyle* aComputedStyle,
+ nsString& aThicknessAttribute, nscoord onePixel,
+ nscoord aDefaultRuleThickness,
+ float aFontSizeInflation);
+
+ uint8_t ScriptIncrement(nsIFrame* aFrame) override;
+
+ protected:
+ explicit nsMathMLmfracFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLContainerFrame(aStyle, aPresContext, kClassID),
+ mLineRect(),
+ mSlashChar(nullptr),
+ mLineThickness(0) {}
+ virtual ~nsMathMLmfracFrame();
+
+ nsresult PlaceInternal(DrawTarget* aDrawTarget, bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize, bool aWidthOnly);
+
+ // Display a slash
+ void DisplaySlash(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ nscoord aThickness, const nsDisplayListSet& aLists);
+
+ nsRect mLineRect;
+ nsMathMLChar* mSlashChar;
+ nscoord mLineThickness;
+};
+
+#endif /* nsMathMLmfracFrame_h___ */
diff --git a/layout/mathml/nsMathMLmmultiscriptsFrame.cpp b/layout/mathml/nsMathMLmmultiscriptsFrame.cpp
new file mode 100644
index 0000000000..304eff9693
--- /dev/null
+++ b/layout/mathml/nsMathMLmmultiscriptsFrame.cpp
@@ -0,0 +1,630 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include <algorithm>
+#include "gfxContext.h"
+#include "gfxMathTable.h"
+#include "gfxTextRun.h"
+
+using namespace mozilla;
+
+//
+// <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(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmmultiscriptsFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmmultiscriptsFrame)
+
+nsMathMLmmultiscriptsFrame::~nsMathMLmmultiscriptsFrame() = default;
+
+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);
+
+ 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) {
+ nsAtom* 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->PlaceForError(aDrawTarget, aPlaceOrigin, 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();
+ RefPtr<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->mMathDepth == 0 && font->mMathStyle == StyleMathStyle::Normal &&
+ !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;
+
+ // 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->PlaceForError(aDrawTarget, aPlaceOrigin, aDesiredSize);
+ }
+ if (prescriptsFrame) {
+ // duplicate <mprescripts/> found
+ // report an error, encourage people to get their markups in order
+ if (aPlaceOrigin) {
+ aFrame->ReportErrorToConsole("DuplicateMprescripts");
+ }
+ return aFrame->PlaceForError(aDrawTarget, aPlaceOrigin, aDesiredSize);
+ }
+ if (!isSubScript) {
+ if (aPlaceOrigin) {
+ aFrame->ReportErrorToConsole("SubSupMismatch");
+ }
+ return aFrame->PlaceForError(aDrawTarget, aPlaceOrigin, aDesiredSize);
+ }
+
+ prescriptsFrame = childFrame;
+ firstPrescriptsPair = true;
+ } else if (0 == count) {
+ // base
+ 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 (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 ||
+ (!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 (!baseFrame) {
+ aFrame->ReportErrorToConsole("NoBase");
+ } else {
+ aFrame->ReportErrorToConsole("SubSupMismatch");
+ }
+ }
+ return aFrame->PlaceForError(aDrawTarget, aPlaceOrigin, 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,
+ ReflowChildFlags::Default);
+ dx += bmBase.width;
+ } else if (prescriptsFrame == childFrame) {
+ // Clear reflow flags of prescripts frame.
+ prescriptsFrame->DidReflow(aPresContext, nullptr);
+ } 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, ReflowChildFlags::Default);
+ }
+
+ 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, ReflowChildFlags::Default);
+ }
+ 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..028be0cf2e
--- /dev/null
+++ b/layout/mathml/nsMathMLmmultiscriptsFrame.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <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 final : public nsMathMLContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmmultiscriptsFrame)
+
+ friend nsIFrame* NS_NewMathMLmmultiscriptsFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ 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(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLContainerFrame(aStyle, aPresContext, kClassID) {}
+ virtual ~nsMathMLmmultiscriptsFrame();
+};
+
+#endif /* nsMathMLmmultiscriptsFrame_h___ */
diff --git a/layout/mathml/nsMathMLmoFrame.cpp b/layout/mathml/nsMathMLmoFrame.cpp
new file mode 100644
index 0000000000..5c21b0911e
--- /dev/null
+++ b/layout/mathml/nsMathMLmoFrame.cpp
@@ -0,0 +1,1061 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gfxContext.h"
+#include "mozilla/PresShell.h"
+#include "nsCSSValue.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsContentUtils.h"
+#include "nsFrameSelection.h"
+#include "mozilla/dom/MathMLElement.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+//
+// <mo> -- operator, fence, or separator - implementation
+//
+
+nsIFrame* NS_NewMathMLmoFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsMathMLmoFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmoFrame)
+
+nsMathMLmoFrame::~nsMathMLmoFrame() = default;
+
+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();
+ UniquePtr<SelectionDetails> details =
+ frameSelection->LookUpSelection(aFrame->GetContent(), 0, 1, true);
+
+ return details != nullptr;
+}
+
+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 nsDisplayListSet& aLists) {
+ bool useMathMLChar = UseMathMLChar();
+
+ if (!useMathMLChar) {
+ // let the base class do everything
+ nsMathMLTokenFrame::BuildDisplayList(aBuilder, 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
+ if (mFrames.GetLength() != 1) {
+ data.Truncate(); // empty data to reset the char
+ mMathMLChar.SetData(data);
+ mMathMLChar.SetComputedStyle(Style());
+ 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 allFlags = 0;
+ for (const auto& form :
+ {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX,
+ NS_MATHML_OPERATOR_FORM_PREFIX}) {
+ nsOperatorFlags flags = 0;
+ float dummy;
+ if (nsMathMLOperators::LookupOperator(data, form, &flags, &dummy, &dummy)) {
+ allFlags |= flags;
+ }
+ }
+
+ mFlags |= allFlags & NS_MATHML_OPERATOR_ACCENT;
+ mFlags |= allFlags & NS_MATHML_OPERATOR_MOVABLELIMITS;
+
+ // see if this is an operator that should be centered to cater for
+ // fonts that are not math-aware
+ if (1 == length) {
+ if ((ch == '+') || (ch == '=') || (ch == '*') ||
+ (ch == 0x2212) || // &minus;
+ (ch == 0x2264) || // &le;
+ (ch == 0x2265) || // &ge;
+ (ch == 0x00D7)) { // &times;
+ mFlags |= NS_MATHML_OPERATOR_CENTERED;
+ }
+ }
+
+ // cache the operator
+ mMathMLChar.SetData(data);
+
+ // cache the native direction -- beware of bug 133429...
+ // mEmbellishData.direction must always retain our native direction, whereas
+ // mMathMLChar.GetStretchDirection() may change later, when Stretch() is
+ // called
+ mEmbellishData.direction = mMathMLChar.GetStretchDirection();
+
+ bool isMutable =
+ NS_MATHML_OPERATOR_IS_LARGEOP(allFlags) ||
+ (mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED);
+ if (isMutable) mFlags |= NS_MATHML_OPERATOR_MUTABLE;
+
+ mMathMLChar.SetComputedStyle(Style());
+}
+
+// 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
+ uint8_t 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->AsElement()->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->AsElement()->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->AsElement()->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);
+ nsOperatorFlags flags = 0;
+ if (nsMathMLOperators::LookupOperatorWithFallback(data, form, &flags,
+ &lspace, &rspace)) {
+ mFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the form bits
+ mFlags |= flags; // just add bits without overwriting
+ }
+
+ // 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()->mMathDepth > 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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value);
+ if (!value.IsEmpty()) {
+ nsCSSValue cssValue;
+ if (dom::MathMLElement::ParseNumericValue(value, cssValue, 0,
+ mContent->OwnerDoc())) {
+ if ((eCSSUnit_Number == cssValue.GetUnit()) && !cssValue.GetFloatValue())
+ leadingSpace = 0;
+ else if (cssValue.IsLengthUnit())
+ leadingSpace = CalcLength(presContext, mComputedStyle, 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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::rspace_, value);
+ if (!value.IsEmpty()) {
+ nsCSSValue cssValue;
+ if (dom::MathMLElement::ParseNumericValue(value, cssValue, 0,
+ mContent->OwnerDoc())) {
+ if ((eCSSUnit_Number == cssValue.GetUnit()) && !cssValue.GetFloatValue())
+ trailingSpace = 0;
+ else if (cssValue.IsLengthUnit())
+ trailingSpace = CalcLength(presContext, mComputedStyle, 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->AsElement()->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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::fence_, value);
+ if (value.EqualsLiteral("false"))
+ mFlags &= ~NS_MATHML_OPERATOR_FENCE;
+ else
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_FENCE;
+ }
+ mContent->AsElement()->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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::separator_,
+ value);
+ if (value.EqualsLiteral("false"))
+ mFlags &= ~NS_MATHML_OPERATOR_SEPARATOR;
+ else
+ mEmbellishData.flags |= NS_MATHML_EMBELLISH_SEPARATOR;
+ }
+ mContent->AsElement()->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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::minsize_, value);
+ if (!value.IsEmpty()) {
+ nsCSSValue cssValue;
+ if (dom::MathMLElement::ParseNumericValue(value, cssValue, 0,
+ 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, mComputedStyle, 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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::maxsize_, value);
+ if (!value.IsEmpty()) {
+ nsCSSValue cssValue;
+ if (dom::MathMLElement::ParseNumericValue(value, cssValue, 0,
+ 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, mComputedStyle, 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->mMathStyle == StyleMathStyle::Normal &&
+ NS_MATHML_OPERATOR_IS_LARGEOP(aFlags)) {
+ stretchHint = NS_STRETCH_LARGEOP; // (largeopOnly, not mask!)
+ 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(
+ this, aDrawTarget, fontSizeInflation, aStretchDirection, container,
+ charSize, stretchHint,
+ StyleVisibility()->mDirection == StyleDirection::Rtl);
+ 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 == StyleDirection::Rtl
+ ? 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, std::move(aChildList));
+ ProcessTextData();
+}
+
+void nsMathMLmoFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ // certain values use units that depend on our ComputedStyle, 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()->mMathStyle == StyleMathStyle::Normal &&
+ NS_MATHML_OPERATOR_IS_LARGEOP(mFlags) && UseMathMLChar()) {
+ nsBoundingMetrics newMetrics;
+ rv = mMathMLChar.Stretch(
+ this, aDrawTarget, nsLayoutUtils::FontSizeInflationFor(this),
+ NS_STRETCH_DIRECTION_VERTICAL, aDesiredSize.mBoundingMetrics,
+ newMetrics, NS_STRETCH_LARGEOP,
+ StyleVisibility()->mDirection == StyleDirection::Rtl);
+
+ 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(gfxContext* aRenderingContext,
+ ReflowOutput& aDesiredSize) {
+ ProcessOperatorData();
+ if (UseMathMLChar()) {
+ uint32_t stretchHint =
+ GetStretchHint(mFlags, mPresentationData, true, StyleFont());
+ aDesiredSize.Width() = mMathMLChar.GetMaxWidth(
+ this, 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 == StyleDirection::Rtl;
+ 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,
+ nsAtom* 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);
+}
+
+void nsMathMLmoFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
+ nsMathMLTokenFrame::DidSetComputedStyle(aOldStyle);
+ mMathMLChar.SetComputedStyle(Style());
+}
diff --git a/layout/mathml/nsMathMLmoFrame.h b/layout/mathml/nsMathMLmoFrame.h
new file mode 100644
index 0000000000..2dec3e2e9b
--- /dev/null
+++ b/layout/mathml/nsMathMLmoFrame.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <mo> -- operator, fence, or separator
+//
+
+class nsMathMLmoFrame final : public nsMathMLTokenFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmoFrame)
+
+ friend nsIFrame* NS_NewMathMLmoFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ eMathMLFrameType GetMathMLFrameType() override;
+
+ void DidSetComputedStyle(ComputedStyle* aOldStyle) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ NS_IMETHOD
+ TransmitAutomaticData() override;
+
+ 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(gfxContext* aRenderingContext,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* 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(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsMathMLTokenFrame(aStyle, aPresContext, kClassID),
+ mFlags(0),
+ mMinSize(0),
+ mMaxSize(0) {}
+ 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..2c73d60ab9
--- /dev/null
+++ b/layout/mathml/nsMathMLmpaddedFrame.cpp
@@ -0,0 +1,441 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/MathMLElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TextUtils.h"
+#include "nsLayoutUtils.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+//
+// <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(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmpaddedFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
+
+nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() = default;
+
+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() {
+ // clang-format off
+ /*
+ 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)
+ */
+ // clang-format on
+
+ nsAutoString value;
+
+ // width
+ mWidthSign = NS_MATHML_SIGN_INVALID;
+ mContent->AsElement()->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->AsElement()->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->AsElement()->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->AsElement()->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->AsElement()->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 (!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 (dom::MathMLElement::ParseNamedSpaceValue(
+ unit, aCSSValue, dom::MathMLElement::PARSE_ALLOW_NEGATIVE,
+ *mContent->OwnerDoc())) {
+ // 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 (dom::MathMLElement::ParseNumericValue(
+ number, aCSSValue, dom::MathMLElement::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(), mComputedStyle, 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) {
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ 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(aStatus.IsComplete(), "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.
+
+ const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
+ if ((isRTL ? 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 ((isRTL ? 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 = (isRTL ? 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..ac2f1480c2
--- /dev/null
+++ b/layout/mathml/nsMathMLmpaddedFrame.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsCSSValue.h"
+#include "nsMathMLContainerFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <mpadded> -- adjust space around content
+//
+
+class nsMathMLmpaddedFrame final : public nsMathMLContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
+
+ friend nsIFrame* NS_NewMathMLmpaddedFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ 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(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLContainerFrame(aStyle, aPresContext, kClassID),
+ mWidthSign(0),
+ mHeightSign(0),
+ mDepthSign(0),
+ mLeadingSpaceSign(0),
+ mVerticalOffsetSign(0),
+ mWidthPseudoUnit(0),
+ mHeightPseudoUnit(0),
+ mDepthPseudoUnit(0),
+ mLeadingSpacePseudoUnit(0),
+ mVerticalOffsetPseudoUnit(0) {}
+
+ 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();
+
+ 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..77637ffe30
--- /dev/null
+++ b/layout/mathml/nsMathMLmrootFrame.cpp
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include <algorithm>
+#include "gfxContext.h"
+#include "gfxMathTable.h"
+
+using namespace mozilla;
+
+//
+// <mroot> -- form a radical - implementation
+//
+
+static const char16_t kSqrChar = char16_t(0x221A);
+
+nsIFrame* NS_NewMathMLmrootFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmrootFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame)
+
+nsMathMLmrootFrame::nsMathMLmrootFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLContainerFrame(aStyle, aPresContext, kClassID),
+ mSqrChar(),
+ mBarRect() {}
+
+nsMathMLmrootFrame::~nsMathMLmrootFrame() = default;
+
+void nsMathMLmrootFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ nsAutoString sqrChar;
+ sqrChar.Assign(kSqrChar);
+ mSqrChar.SetData(sqrChar);
+ mSqrChar.SetComputedStyle(Style());
+}
+
+bool nsMathMLmrootFrame::ShouldUseRowFallback() {
+ if (!StaticPrefs::mathml_error_message_layout_for_invalid_markup_disabled()) {
+ return false;
+ }
+ nsIFrame* baseFrame = mFrames.FirstChild();
+ if (!baseFrame) {
+ return true;
+ }
+ nsIFrame* indexFrame = baseFrame->GetNextSibling();
+ return !indexFrame || indexFrame->GetNextSibling();
+}
+
+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 nsDisplayListSet& aLists) {
+ /////////////
+ // paint the content we are square-rooting
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists);
+
+ if (ShouldUseRowFallback()) return;
+
+ /////////////
+ // 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();
+ RefPtr<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) {
+ if (ShouldUseRowFallback()) {
+ ReportChildCountError();
+ nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
+ aStatus);
+ return;
+ }
+
+ MarkInReflow();
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ 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);
+ 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(childStatus.IsComplete(), "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();
+ }
+ // FIXME: Bug 1788223: Remove this code when the preference
+ // mathml.error_message_layout_for_invalid_markup.disabled no longer needed.
+ if (2 != count) {
+ // report an error, encourage people to get their markups in order
+ ReportChildCountError();
+ ReflowError(drawTarget, 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()->mMathStyle == StyleMathStyle::Normal,
+ 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(this, drawTarget, fontSizeInflation,
+ NS_STRETCH_DIRECTION_VERTICAL, contSize, radicalSize,
+ NS_STRETCH_LARGER,
+ StyleVisibility()->mDirection == StyleDirection::Rtl);
+ // 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;
+ RefPtr<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, ReflowChildFlags::Default);
+
+ // 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,
+ ReflowChildFlags::Default);
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+}
+
+/* virtual */
+void nsMathMLmrootFrame::GetIntrinsicISizeMetrics(gfxContext* aRenderingContext,
+ ReflowOutput& aDesiredSize) {
+ if (ShouldUseRowFallback()) {
+ nsMathMLContainerFrame::GetIntrinsicISizeMetrics(aRenderingContext,
+ aDesiredSize);
+ return;
+ }
+
+ 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, IntrinsicISizeType::PrefISize);
+ nscoord indexWidth = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, indexFrame, IntrinsicISizeType::PrefISize);
+ nscoord sqrWidth = mSqrChar.GetMaxWidth(
+ this, 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;
+}
+
+void nsMathMLmrootFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
+ nsMathMLContainerFrame::DidSetComputedStyle(aOldStyle);
+ mSqrChar.SetComputedStyle(Style());
+}
diff --git a/layout/mathml/nsMathMLmrootFrame.h b/layout/mathml/nsMathMLmrootFrame.h
new file mode 100644
index 0000000000..b12010b8e3
--- /dev/null
+++ b/layout/mathml/nsMathMLmrootFrame.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <msqrt> and <mroot> -- form a radical
+//
+
+class nsMathMLmrootFrame final : public nsMathMLContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmrootFrame)
+
+ friend nsIFrame* NS_NewMathMLmrootFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ void DidSetComputedStyle(ComputedStyle* aOldStyle) 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(gfxContext* aRenderingContext,
+ ReflowOutput& aDesiredSize) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ uint8_t ScriptIncrement(nsIFrame* aFrame) override {
+ return (aFrame && aFrame == mFrames.LastChild()) ? 2 : 0;
+ }
+
+ protected:
+ explicit nsMathMLmrootFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext);
+ virtual ~nsMathMLmrootFrame();
+
+ nsMathMLChar mSqrChar;
+ nsRect mBarRect;
+
+ private:
+ bool ShouldUseRowFallback();
+};
+
+#endif /* nsMathMLmrootFrame_h___ */
diff --git a/layout/mathml/nsMathMLmrowFrame.cpp b/layout/mathml/nsMathMLmrowFrame.cpp
new file mode 100644
index 0000000000..abff988585
--- /dev/null
+++ b/layout/mathml/nsMathMLmrowFrame.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/PresShell.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+
+//
+// <mrow> -- horizontally group any number of subexpressions - implementation
+//
+
+nsIFrame* NS_NewMathMLmrowFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmrowFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrowFrame)
+
+nsMathMLmrowFrame::~nsMathMLmrowFrame() = default;
+
+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,
+ nsAtom* 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->IsTableWrapperFrame())
+ return frame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ }
+ MOZ_ASSERT_UNREACHABLE("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..fd79b0c520
--- /dev/null
+++ b/layout/mathml/nsMathMLmrowFrame.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <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 final : public nsMathMLContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmrowFrame)
+
+ friend nsIFrame* NS_NewMathMLmrowFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* 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(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsMathMLContainerFrame(aStyle, aPresContext, kClassID) {}
+ virtual ~nsMathMLmrowFrame();
+};
+
+#endif /* nsMathMLmrowFrame_h___ */
diff --git a/layout/mathml/nsMathMLmspaceFrame.cpp b/layout/mathml/nsMathMLmspaceFrame.cpp
new file mode 100644
index 0000000000..9b6e54ae13
--- /dev/null
+++ b/layout/mathml/nsMathMLmspaceFrame.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/MathMLElement.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/gfx/2D.h"
+#include "nsLayoutUtils.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+//
+// <mspace> -- space - implementation
+//
+
+nsIFrame* NS_NewMathMLmspaceFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmspaceFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmspaceFrame)
+
+nsMathMLmspaceFrame::~nsMathMLmspaceFrame() = default;
+
+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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value);
+ if (!value.IsEmpty()) {
+ ParseNumericValue(value, &mWidth, dom::MathMLElement::PARSE_ALLOW_NEGATIVE,
+ aPresContext, mComputedStyle, 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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value);
+ if (!value.IsEmpty()) {
+ ParseNumericValue(value, &mHeight, 0, aPresContext, mComputedStyle,
+ 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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::depth_, value);
+ if (!value.IsEmpty()) {
+ ParseNumericValue(value, &mDepth, 0, aPresContext, mComputedStyle,
+ fontSizeInflation);
+ }
+}
+
+void nsMathMLmspaceFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ ProcessAttributes(aPresContext);
+
+ auto borderPadding = aReflowInput.ComputedPhysicalBorderPadding();
+ mBoundingMetrics = nsBoundingMetrics();
+ mBoundingMetrics.width = mWidth + borderPadding.LeftRight();
+ mBoundingMetrics.ascent = mHeight + borderPadding.Side(eSideTop);
+ mBoundingMetrics.descent = mDepth + borderPadding.Side(eSideBottom);
+ mBoundingMetrics.leftBearing = 0;
+ mBoundingMetrics.rightBearing = mBoundingMetrics.width;
+
+ aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent);
+ aDesiredSize.Width() = std::max(0, mBoundingMetrics.width);
+ aDesiredSize.Height() = mBoundingMetrics.ascent + mBoundingMetrics.descent;
+ // Also return our bounding metrics
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+}
+
+/* virtual */
+nsresult nsMathMLmspaceFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) {
+ ProcessAttributes(PresContext());
+ mBoundingMetrics = nsBoundingMetrics();
+ auto offsets = IntrinsicISizeOffsets();
+ mBoundingMetrics.width = mWidth + offsets.padding + offsets.border;
+ 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..d0692d2dfe
--- /dev/null
+++ b/layout/mathml/nsMathMLmspaceFrame.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <mspace> -- space
+//
+
+class nsMathMLmspaceFrame final : public nsMathMLContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmspaceFrame)
+
+ friend nsIFrame* NS_NewMathMLmspaceFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ 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 void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ protected:
+ explicit nsMathMLmspaceFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLContainerFrame(aStyle, aPresContext, kClassID),
+ mWidth(0),
+ mHeight(0),
+ mDepth(0) {}
+ 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..b9ad180179
--- /dev/null
+++ b/layout/mathml/nsMathMLmsqrtFrame.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/PresShell.h"
+#include "mozilla/gfx/2D.h"
+
+//
+// <msqrt> -- form a radical - implementation
+//
+
+using namespace mozilla;
+
+nsIFrame* NS_NewMathMLmsqrtFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmsqrtFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmsqrtFrame)
+
+nsMathMLmsqrtFrame::nsMathMLmsqrtFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLmencloseFrame(aStyle, aPresContext, kClassID) {}
+
+nsMathMLmsqrtFrame::~nsMathMLmsqrtFrame() = default;
+
+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,
+ nsAtom* 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..43c2702c6c
--- /dev/null
+++ b/layout/mathml/nsMathMLmsqrtFrame.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <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.)
+*/
+
+// XXXfredw: This class should share its layout logic with nsMathMLmrootFrame
+// when the menclose "radical" notation is removed.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1548522
+class nsMathMLmsqrtFrame final : public nsMathMLmencloseFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmsqrtFrame)
+
+ friend nsIFrame* NS_NewMathMLmsqrtFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ NS_IMETHOD
+ InheritAutomaticData(nsIFrame* aParent) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool IsMrowLike() override {
+ return mFrames.FirstChild() != mFrames.LastChild() || !mFrames.FirstChild();
+ }
+
+ protected:
+ explicit nsMathMLmsqrtFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext);
+ virtual ~nsMathMLmsqrtFrame();
+};
+
+#endif /* nsMathMLmsqrtFrame_h___ */
diff --git a/layout/mathml/nsMathMLmtableFrame.cpp b/layout/mathml/nsMathMLmtableFrame.cpp
new file mode 100644
index 0000000000..449114c494
--- /dev/null
+++ b/layout/mathml/nsMathMLmtableFrame.cpp
@@ -0,0 +1,1219 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gfxContext.h"
+#include "nsMathMLmtableFrame.h"
+#include "nsPresContext.h"
+#include "nsStyleConsts.h"
+#include "nsNameSpaceManager.h"
+#include "nsCSSRendering.h"
+#include "mozilla/dom/MathMLElement.h"
+
+#include "nsCRT.h"
+#include "nsTArray.h"
+#include "nsTableFrame.h"
+#include "celldata.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include <algorithm>
+
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::image;
+using mozilla::dom::Element;
+
+//
+// <mtable> -- table or matrix - implementation
+//
+
+static int8_t ParseStyleValue(nsAtom* aAttribute,
+ const nsAString& aAttributeValue) {
+ if (aAttribute == nsGkAtoms::rowalign_) {
+ if (aAttributeValue.EqualsLiteral("top")) {
+ return static_cast<int8_t>(StyleVerticalAlignKeyword::Top);
+ }
+ if (aAttributeValue.EqualsLiteral("bottom")) {
+ return static_cast<int8_t>(StyleVerticalAlignKeyword::Bottom);
+ }
+ if (aAttributeValue.EqualsLiteral("center")) {
+ return static_cast<int8_t>(StyleVerticalAlignKeyword::Middle);
+ }
+ return static_cast<int8_t>(StyleVerticalAlignKeyword::Baseline);
+ }
+
+ if (aAttribute == nsGkAtoms::columnalign_) {
+ if (aAttributeValue.EqualsLiteral("left")) {
+ return int8_t(StyleTextAlign::Left);
+ }
+ if (aAttributeValue.EqualsLiteral("right")) {
+ return int8_t(StyleTextAlign::Right);
+ }
+ return int8_t(StyleTextAlign::Center);
+ }
+
+ if (aAttribute == nsGkAtoms::rowlines_ ||
+ aAttribute == nsGkAtoms::columnlines_) {
+ if (aAttributeValue.EqualsLiteral("solid")) {
+ return static_cast<int8_t>(StyleBorderStyle::Solid);
+ }
+ if (aAttributeValue.EqualsLiteral("dashed")) {
+ return static_cast<int8_t>(StyleBorderStyle::Dashed);
+ }
+ return static_cast<int8_t>(StyleBorderStyle::None);
+ }
+
+ MOZ_CRASH("Unrecognized attribute.");
+ return -1;
+}
+
+static nsTArray<int8_t>* ExtractStyleValues(const nsAString& aString,
+ nsAtom* 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();
+
+ AutoTArray<nsString, 3> params;
+ params.AppendElement(aValue);
+ params.AppendElement(aAttribute);
+ params.AppendElement(nsDependentAtomString(content->NodeInfo()->NameAtom()));
+
+ return nsContentUtils::ReportToConsole(
+ nsIScriptError::errorFlag, "Layout: MathML"_ns, content->OwnerDoc(),
+ nsContentUtils::eMATHML_PROPERTIES, "AttributeParsingError", params);
+}
+
+// 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(
+ nsAtom* 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) {
+ propertyData = currentFrame->GetProperty(aFrameProperty);
+ bool frameIsTable = (currentFrame->IsTableFrame());
+
+ 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) {
+ uint32_t rowIndex = aFrame->RowIndex();
+ uint32_t columnIndex = aFrame->ColIndex();
+
+ nscoord borderWidth = nsPresContext::CSSPixelsToAppUnits(1);
+
+ 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.
+ uint32_t listLength = rowLinesList->Length();
+ if (rowIndex < listLength) {
+ aStyleBorder.SetBorderStyle(
+ eSideTop,
+ static_cast<StyleBorderStyle>(rowLinesList->ElementAt(rowIndex - 1)));
+ } else {
+ aStyleBorder.SetBorderStyle(eSideTop,
+ static_cast<StyleBorderStyle>(
+ rowLinesList->ElementAt(listLength - 1)));
+ }
+ aStyleBorder.SetBorderWidth(eSideTop, 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.
+ uint32_t listLength = columnLinesList->Length();
+ if (columnIndex < listLength) {
+ aStyleBorder.SetBorderStyle(
+ eSideLeft, static_cast<StyleBorderStyle>(
+ columnLinesList->ElementAt(columnIndex - 1)));
+ } else {
+ aStyleBorder.SetBorderStyle(
+ eSideLeft, static_cast<StyleBorderStyle>(
+ columnLinesList->ElementAt(listLength - 1)));
+ }
+ aStyleBorder.SetBorderWidth(eSideLeft, 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 final : public nsDisplayBorder {
+ public:
+ nsDisplaymtdBorder(nsDisplayListBuilder* aBuilder, nsMathMLmtdFrame* aFrame)
+ : nsDisplayBorder(aBuilder, aFrame) {}
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = true;
+ nsStyleBorder styleBorder = *mFrame->StyleBorder();
+ nsMathMLmtdFrame* frame = static_cast<nsMathMLmtdFrame*>(mFrame);
+ ApplyBorderToStyle(frame, styleBorder);
+ nsRect bounds = CalculateBounds<nsRect>(styleBorder);
+ nsMargin overflow = ComputeBorderOverflow(frame, styleBorder);
+ bounds.Inflate(overflow);
+ return bounds;
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* 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::SyncDecodeImages
+ : PaintBorderFlags();
+
+ Unused << nsCSSRendering::PaintBorderWithStyleBorder(
+ mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(aBuilder, aCtx),
+ bounds, styleBorder, mFrame->Style(), flags, mFrame->GetSkipSides());
+ }
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override {
+ return false;
+ }
+
+ virtual bool IsInvisibleInRect(const nsRect& aRect) const override {
+ return false;
+ }
+};
+
+#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, nsAtom* aAttribute,
+ bool aAllowMultiValues) {
+ nsAutoString attrValue;
+
+ Element* frameElement = aFrame->GetContent()->AsElement();
+ frameElement->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!");
+ aFrame->SetProperty(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, nsAtom* aAttribute,
+ nsTArray<nscoord>& aSpacingArray,
+ nsIFrame* aFrame, nscoord aDefaultValue0,
+ nscoord aDefaultValue1,
+ float aFontSizeInflation) {
+ nsPresContext* presContext = aFrame->PresContext();
+ ComputedStyle* computedStyle = aFrame->Style();
+
+ 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, 0, presContext,
+ computedStyle, aFontSizeInflation);
+ aSpacingArray.AppendElement(newValue);
+
+ startIndex += count;
+ count = 0;
+ elementNum++;
+ }
+ }
+}
+
+static void ParseSpacingAttribute(nsMathMLmtableFrame* aFrame,
+ nsAtom* aAttribute) {
+ NS_ASSERTION(aAttribute == nsGkAtoms::rowspacing_ ||
+ aAttribute == nsGkAtoms::columnspacing_ ||
+ aAttribute == nsGkAtoms::framespacing_,
+ "Non spacing attribute passed");
+
+ nsAutoString attrValue;
+ Element* frameElement = aFrame->GetContent()->AsElement();
+ frameElement->GetAttr(kNameSpaceID_None, aAttribute, attrValue);
+
+ if (nsGkAtoms::framespacing_ == aAttribute) {
+ nsAutoString frame;
+ frameElement->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 (frameElement->HasAttr(kNameSpaceID_None, aAttribute)) {
+ ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get());
+ }
+ valueList.AppendElement(value);
+ }
+ if (aAttribute == nsGkAtoms::framespacing_) {
+ if (valueList.Length() == 1) {
+ if (frameElement->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->IsTableRowGroupFrame()) return;
+
+ for (nsIFrame* rowFrame : rgFrame->PrincipalChildList()) {
+ DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TableRow);
+ if (rowFrame->IsTableRowFrame()) {
+ // 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 (cellFrame->IsTableCellFrame()) {
+ // 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(u"top")) {
+ len = 3; // 3 is the length of 'top'
+ aAlign = eAlign_top;
+ } else if (0 == aValue.Find(u"bottom")) {
+ len = 6; // 6 is the length of 'bottom'
+ aAlign = eAlign_bottom;
+ } else if (0 == aValue.Find(u"center")) {
+ len = 6; // 6 is the length of 'center'
+ aAlign = eAlign_center;
+ } else if (0 == aValue.Find(u"baseline")) {
+ len = 8; // 8 is the length of 'baseline'
+ aAlign = eAlign_baseline;
+ } else if (0 == aValue.Find(u"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) ||
+ // XXXbaku which kind of body tag?
+ c->NodeInfo()->NameAtom(nsGkAtoms::body))
+ 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(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmtableWrapperFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableWrapperFrame)
+
+nsMathMLmtableWrapperFrame::~nsMathMLmtableWrapperFrame() = default;
+
+nsresult nsMathMLmtableWrapperFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* 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->IsTableFrame(),
+ "should always have an inner table frame");
+ nsIFrame* rgFrame = tableFrame->PrincipalChildList().FirstChild();
+ if (!rgFrame || !rgFrame->IsTableRowGroupFrame()) return NS_OK;
+
+ // align - just need to issue a dirty (resize) reflow command
+ if (aAttribute == nsGkAtoms::align) {
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None,
+ 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.
+ PresShell()->FrameNeedsReflow(GetParent(),
+ IntrinsicDirty::FrameAncestorsAndDescendants,
+ 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
+ tableFrame->RemoveProperty(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, IntrinsicDirty::FrameAncestorsAndDescendants, 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->IsTableFrame(),
+ "should always have an inner table frame");
+ nsIFrame* rgFrame = tableFrame->PrincipalChildList().FirstChild();
+ if (!rgFrame || !rgFrame->IsTableRowGroupFrame()) return nullptr;
+ for (nsIFrame* rowFrame : rgFrame->PrincipalChildList()) {
+ if (aRowIndex == 0) {
+ DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TableRow);
+ if (!rowFrame->IsTableRowFrame()) return nullptr;
+
+ return rowFrame;
+ }
+ --aRowIndex;
+ }
+ }
+ return nullptr;
+}
+
+void nsMathMLmtableWrapperFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ 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->AsElement()->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 {
+ nsIFrame* parent = frame->GetParent();
+ dy += frame->BStart(wm, parent->GetSize());
+ frame = parent;
+ } 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;
+}
+
+nsContainerFrame* NS_NewMathMLmtableFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmtableFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableFrame)
+
+nsMathMLmtableFrame::~nsMathMLmtableFrame() = default;
+
+void nsMathMLmtableFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ nsTableFrame::SetInitialChildList(aListID, std::move(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(), RestyleHint::RestyleSubtree(),
+ 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->AsElement()->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::rowspacing_) ||
+ mContent->AsElement()->HasAttr(
+ kNameSpaceID_None, nsGkAtoms::columnspacing_) ||
+ mContent->AsElement()->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::framespacing_));
+}
+
+NS_QUERYFRAME_HEAD(nsMathMLmtableFrame)
+ NS_QUERYFRAME_ENTRY(nsMathMLmtableFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsTableFrame)
+
+// --------
+// implementation of nsMathMLmtrFrame
+
+nsContainerFrame* NS_NewMathMLmtrFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmtrFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtrFrame)
+
+nsMathMLmtrFrame::~nsMathMLmtrFrame() = default;
+
+nsresult nsMathMLmtrFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* 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;
+ }
+
+ RemoveProperty(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, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+
+ return NS_OK;
+}
+
+// --------
+// implementation of nsMathMLmtdFrame
+
+nsContainerFrame* NS_NewMathMLmtdFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle,
+ nsTableFrame* aTableFrame) {
+ return new (aPresShell) nsMathMLmtdFrame(aStyle, aTableFrame);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdFrame)
+
+nsMathMLmtdFrame::~nsMathMLmtdFrame() = default;
+
+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);
+}
+
+nsresult nsMathMLmtdFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* 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_) {
+ RemoveProperty(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;
+}
+
+StyleVerticalAlignKeyword nsMathMLmtdFrame::GetVerticalAlign() const {
+ // Set the default alignment in case no alignment was specified
+ auto alignment = nsTableCellFrame::GetVerticalAlign();
+
+ nsTArray<int8_t>* alignmentList = FindCellProperty(this, RowAlignProperty());
+
+ if (alignmentList) {
+ uint32_t rowIndex = RowIndex();
+
+ // If the row number is greater than the number of provided rowalign values,
+ // we simply repeat the last value.
+ return static_cast<StyleVerticalAlignKeyword>(
+ (rowIndex < alignmentList->Length())
+ ? alignmentList->ElementAt(rowIndex)
+ : alignmentList->LastElement());
+ }
+
+ return alignment;
+}
+
+nsresult nsMathMLmtdFrame::ProcessBorders(nsTableFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ aLists.BorderBackground()->AppendNewToTop<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(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmtdInnerFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdInnerFrame)
+
+nsMathMLmtdInnerFrame::nsMathMLmtdInnerFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsBlockFrame(aStyle, aPresContext, kClassID)
+ // Make a copy of the parent nsStyleText for later modification.
+ ,
+ mUniqueStyleText(MakeUnique<nsStyleText>(*StyleText())) {}
+
+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
+ auto alignment = uint8_t(StyleText()->mTextAlign);
+
+ nsTArray<int8_t>* alignmentList =
+ FindCellProperty(this, ColumnAlignProperty());
+
+ if (alignmentList) {
+ nsMathMLmtdFrame* cellFrame = (nsMathMLmtdFrame*)GetParent();
+ uint32_t columnIndex = cellFrame->ColIndex();
+
+ // If the column number is greater than the number of provided columalign
+ // values, we simply repeat the last value.
+ if (columnIndex < alignmentList->Length())
+ alignment = alignmentList->ElementAt(columnIndex);
+ else
+ alignment = alignmentList->ElementAt(alignmentList->Length() - 1);
+ }
+
+ mUniqueStyleText->mTextAlign = StyleTextAlign(alignment);
+ return mUniqueStyleText.get();
+}
+
+/* virtual */
+void nsMathMLmtdInnerFrame::DidSetComputedStyle(
+ ComputedStyle* aOldComputedStyle) {
+ nsBlockFrame::DidSetComputedStyle(aOldComputedStyle);
+ mUniqueStyleText = MakeUnique<nsStyleText>(*StyleText());
+}
diff --git a/layout/mathml/nsMathMLmtableFrame.h b/layout/mathml/nsMathMLmtableFrame.h
new file mode 100644
index 0000000000..60a1429cb2
--- /dev/null
+++ b/layout/mathml/nsMathMLmtableFrame.h
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/UniquePtr.h"
+#include "nsMathMLContainerFrame.h"
+#include "nsBlockFrame.h"
+#include "nsTableWrapperFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableCellFrame.h"
+
+namespace mozilla {
+class nsDisplayListBuilder;
+class nsDisplayListSet;
+class PresShell;
+} // namespace mozilla
+
+//
+// <mtable> -- table or matrix
+//
+
+class nsMathMLmtableWrapperFrame final : public nsTableWrapperFrame,
+ public nsMathMLFrame {
+ public:
+ friend nsContainerFrame* NS_NewMathMLmtableOuterFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmtableWrapperFrame)
+
+ // overloaded nsTableWrapperFrame methods
+
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override {
+ return nsTableWrapperFrame::IsFrameOfType(aFlags & ~(nsIFrame::eMathML));
+ }
+
+ protected:
+ explicit nsMathMLmtableWrapperFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsTableWrapperFrame(aStyle, aPresContext, kClassID) {}
+
+ 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 final : public nsTableFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmtableFrame)
+
+ friend nsContainerFrame* NS_NewMathMLmtableFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ // Overloaded nsTableFrame methods
+
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override {
+ nsTableFrame::AppendFrames(aListID, std::move(aFrameList));
+ RestyleTable();
+ }
+
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override {
+ nsTableFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(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.Clone();
+ }
+
+ void SetRowSpacingArray(const nsTArray<nscoord>& aRowSpacing) {
+ mRowSpacing = aRowSpacing.Clone();
+ }
+
+ 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(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsTableFrame(aStyle, aPresContext, kClassID),
+ mFrameSpacingX(0),
+ mFrameSpacingY(0),
+ mUseCSSSpacing(false) {}
+
+ virtual ~nsMathMLmtableFrame();
+
+ private:
+ nsTArray<nscoord> mColSpacing;
+ nsTArray<nscoord> mRowSpacing;
+ nscoord mFrameSpacingX;
+ nscoord mFrameSpacingY;
+ bool mUseCSSSpacing;
+}; // class nsMathMLmtableFrame
+
+// --------------
+
+class nsMathMLmtrFrame final : public nsTableRowFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmtrFrame)
+
+ friend nsContainerFrame* NS_NewMathMLmtrFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ // overloaded nsTableRowFrame methods
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override {
+ nsTableRowFrame::AppendFrames(aListID, std::move(aFrameList));
+ RestyleTable();
+ }
+
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override {
+ nsTableRowFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(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(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsTableRowFrame(aStyle, aPresContext, kClassID) {}
+
+ virtual ~nsMathMLmtrFrame();
+}; // class nsMathMLmtrFrame
+
+// --------------
+
+class nsMathMLmtdFrame final : public nsTableCellFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmtdFrame)
+
+ friend nsContainerFrame* NS_NewMathMLmtdFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle,
+ nsTableFrame* aTableFrame);
+
+ // overloaded nsTableCellFrame methods
+
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual mozilla::StyleVerticalAlignKeyword GetVerticalAlign() const override;
+ virtual nsresult ProcessBorders(
+ nsTableFrame* aFrame, mozilla::nsDisplayListBuilder* aBuilder,
+ const mozilla::nsDisplayListSet& aLists) 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(ComputedStyle* aStyle, nsTableFrame* aTableFrame)
+ : nsTableCellFrame(aStyle, aTableFrame, kClassID) {}
+
+ virtual ~nsMathMLmtdFrame();
+}; // class nsMathMLmtdFrame
+
+// --------------
+
+class nsMathMLmtdInnerFrame final : public nsBlockFrame, public nsMathMLFrame {
+ public:
+ friend nsContainerFrame* NS_NewMathMLmtdInnerFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmtdInnerFrame)
+
+ // 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);
+ }
+
+ virtual const nsStyleText* StyleTextForLineLayout() override;
+ virtual void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override;
+
+ bool IsMrowLike() override {
+ return mFrames.FirstChild() != mFrames.LastChild() || !mFrames.FirstChild();
+ }
+
+ protected:
+ explicit nsMathMLmtdInnerFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext);
+ virtual ~nsMathMLmtdInnerFrame() = default;
+
+ mozilla::UniquePtr<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..509ae29ed3
--- /dev/null
+++ b/layout/mathml/nsMathMLmunderoverFrame.cpp
@@ -0,0 +1,700 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsMathMLmmultiscriptsFrame.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MathMLElement.h"
+#include <algorithm>
+#include "gfxContext.h"
+#include "gfxMathTable.h"
+#include "gfxTextRun.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_mathml.h"
+
+using namespace mozilla;
+
+//
+// <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(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmunderoverFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmunderoverFrame)
+
+nsMathMLmunderoverFrame::~nsMathMLmunderoverFrame() = default;
+
+nsresult nsMathMLmunderoverFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* 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()->mMathStyle == StyleMathStyle::Compact) {
+ 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;
+}
+
+void nsMathMLmunderoverFrame::DestroyFrom(nsIFrame* aDestroyRoot,
+ PostDestroyData& aPostDestroyData) {
+ if (!mPostReflowIncrementScriptLevelCommands.IsEmpty()) {
+ PresShell()->CancelReflowCallback(this);
+ }
+ nsMathMLContainerFrame::DestroyFrom(aDestroyRoot, aPostDestroyData);
+}
+
+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
+}
+
+void nsMathMLmunderoverFrame::SetIncrementScriptLevel(uint32_t aChildIndex,
+ bool aIncrement) {
+ nsIFrame* child = PrincipalChildList().FrameAt(aChildIndex);
+ if (!child || !child->GetContent()->IsMathMLElement() ||
+ child->GetContent()->GetPrimaryFrame() != child) {
+ return;
+ }
+
+ auto element = dom::MathMLElement::FromNode(child->GetContent());
+ if (element->GetIncrementScriptLevel() == aIncrement) {
+ return;
+ }
+
+ if (mPostReflowIncrementScriptLevelCommands.IsEmpty()) {
+ PresShell()->PostReflowCallback(this);
+ }
+
+ mPostReflowIncrementScriptLevelCommands.AppendElement(
+ SetIncrementScriptLevelCommand{aChildIndex, aIncrement});
+}
+
+bool nsMathMLmunderoverFrame::ReflowFinished() {
+ SetPendingPostReflowIncrementScriptLevel();
+ return true;
+}
+
+void nsMathMLmunderoverFrame::ReflowCallbackCanceled() {
+ // Do nothing, at this point our work will just be useless.
+ mPostReflowIncrementScriptLevelCommands.Clear();
+}
+
+void nsMathMLmunderoverFrame::SetPendingPostReflowIncrementScriptLevel() {
+ MOZ_ASSERT(!mPostReflowIncrementScriptLevelCommands.IsEmpty());
+
+ nsTArray<SetIncrementScriptLevelCommand> commands =
+ std::move(mPostReflowIncrementScriptLevelCommands);
+
+ for (const auto& command : commands) {
+ nsIFrame* child = PrincipalChildList().FrameAt(command.mChildIndex);
+ if (!child || !child->GetContent()->IsMathMLElement()) {
+ continue;
+ }
+
+ auto element = dom::MathMLElement::FromNode(child->GetContent());
+ element->SetIncrementScriptLevel(command.mDoIncrement, true);
+ }
+}
+
+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->AsElement()->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->AsElement()->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()->mMathStyle == StyleMathStyle::Compact;
+
+ // disable the stretch-all flag if we are going to act like a superscript
+ if (subsupDisplay) {
+ mPresentationData.flags &= ~NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY;
+ }
+
+ // Now transmit any change that we want to our children so that they
+ // can update their mPresentationData structs
+ //---------------------------------------------------------------------
+
+ /* The REC says:
+ Within underscript, <munderover> always sets displaystyle to "false",
+ but increments scriptlevel by 1 only when accentunder is "false".
+
+ Within overscript, <munderover> always sets displaystyle to "false",
+ but increments scriptlevel by 1 only when accent is "false".
+
+ Within subscript and superscript it increments scriptlevel by 1, and
+ sets displaystyle to "false", but leaves both attributes unchanged within
+ base.
+
+ The TeXBook treats 'over' like a superscript, so p.141 or Rule 13a
+ say it shouldn't be compressed. However, The TeXBook says
+ that math accents and \overline change uncramped styles to their
+ cramped counterparts.
+ */
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover_,
+ nsGkAtoms::munderover_)) {
+ uint32_t compress = NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)
+ ? NS_MATHML_COMPRESSED
+ : 0;
+ mIncrementOver = !NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) ||
+ subsupDisplay;
+ SetIncrementScriptLevel(
+ mContent->IsMathMLElement(nsGkAtoms::mover_) ? 1 : 2, mIncrementOver);
+ if (mIncrementOver) {
+ PropagateFrameFlagFor(overscriptFrame, NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+ PropagatePresentationDataFor(overscriptFrame, compress, compress);
+ }
+ /*
+ The TeXBook treats 'under' like a subscript, so p.141 or Rule 13a
+ say it should be compressed
+ */
+ if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_,
+ nsGkAtoms::munderover_)) {
+ mIncrementUnder =
+ !NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags) ||
+ subsupDisplay;
+ SetIncrementScriptLevel(1, mIncrementUnder);
+ if (mIncrementUnder) {
+ PropagateFrameFlagFor(underscriptFrame,
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+ PropagatePresentationDataFor(underscriptFrame, NS_MATHML_COMPRESSED,
+ NS_MATHML_COMPRESSED);
+ }
+
+ /* Set flags for dtls font feature settings.
+
+ dtls
+ Dotless Forms
+ This feature provides dotless forms for Math Alphanumeric
+ characters, such as U+1D422 MATHEMATICAL BOLD SMALL I,
+ U+1D423 MATHEMATICAL BOLD SMALL J, U+1D456
+ U+MATHEMATICAL ITALIC SMALL I, U+1D457 MATHEMATICAL ITALIC
+ SMALL J, and so on.
+ The dotless forms are to be used as base forms for placing
+ mathematical accents over them.
+
+ To opt out of this change, add the following to the stylesheet:
+ "font-feature-settings: 'dtls' 0"
+ */
+ if (overscriptFrame &&
+ NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) &&
+ !NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags)) {
+ PropagatePresentationDataFor(baseFrame, NS_MATHML_DTLS, NS_MATHML_DTLS);
+ }
+
+ return NS_OK;
+}
+
+/*
+The REC says:
+* If the base is an operator with movablelimits="true" (or an embellished
+ operator whose <mo> element core has movablelimits="true"), and
+ displaystyle="false", then underscript and overscript are drawn in
+ a subscript and superscript position, respectively. In this case,
+ the accent and accentunder attributes are ignored. This is often
+ used for limits on symbols such as &sum;.
+
+i.e.,:
+ if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishDataflags) &&
+ StyleFont()->mMathStyle == StyleMathStyle::Compact) {
+ // 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()->mMathStyle == StyleMathStyle::Compact) {
+ // 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 PlaceForError(aDrawTarget, aPlaceOrigin, 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();
+ RefPtr<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;
+
+ //////////
+ // 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;
+ dxOver += correction;
+ } else {
+ mBoundingMetrics.width = std::max(bmBase.width, overWidth);
+ dxOver += correction / 2;
+ }
+
+ dxOver += (mBoundingMetrics.width - overWidth) / 2;
+ dxBase = (mBoundingMetrics.width - bmBase.width) / 2;
+
+ 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 (!NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags)) {
+ GetItalicCorrection(bmAnonymousBase, correction);
+ dxUnder += -correction / 2;
+ }
+ nscoord dxAnonymousBase = 0;
+ dxUnder += (maxWidth - underWidth) / 2;
+ dxAnonymousBase = (maxWidth - bmAnonymousBase.width) / 2;
+
+ // 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,
+ ReflowChildFlags::Default);
+ }
+ // place base
+ dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent();
+ FinishReflowChild(baseFrame, PresContext(), baseSize, nullptr, dxBase, dy,
+ ReflowChildFlags::Default);
+ // place underscript
+ if (underFrame) {
+ dy = aDesiredSize.BlockStartAscent() + mBoundingMetrics.descent -
+ bmUnder.descent - underSize.BlockStartAscent();
+ FinishReflowChild(underFrame, PresContext(), underSize, nullptr, dxUnder,
+ dy, ReflowChildFlags::Default);
+ }
+ }
+ return NS_OK;
+}
diff --git a/layout/mathml/nsMathMLmunderoverFrame.h b/layout/mathml/nsMathMLmunderoverFrame.h
new file mode 100644
index 0000000000..e2258979fb
--- /dev/null
+++ b/layout/mathml/nsMathMLmunderoverFrame.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsIReflowCallback.h"
+#include "nsMathMLContainerFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <munderover> -- attach an underscript-overscript pair to a base
+//
+
+class nsMathMLmunderoverFrame final : public nsMathMLContainerFrame,
+ public nsIReflowCallback {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLmunderoverFrame)
+
+ friend nsIFrame* NS_NewMathMLmunderoverFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ 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;
+
+ void DestroyFrom(nsIFrame* aRoot, PostDestroyData& aPostDestroyData) override;
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ uint8_t ScriptIncrement(nsIFrame* aFrame) override;
+
+ // nsIReflowCallback.
+ bool ReflowFinished() override;
+ void ReflowCallbackCanceled() override;
+
+ protected:
+ explicit nsMathMLmunderoverFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLContainerFrame(aStyle, aPresContext, kClassID),
+ mIncrementUnder(false),
+ mIncrementOver(false) {}
+
+ virtual ~nsMathMLmunderoverFrame();
+
+ private:
+ // 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(uint32_t aChildIndex, bool aIncrement);
+ void SetPendingPostReflowIncrementScriptLevel();
+
+ bool mIncrementUnder;
+ bool mIncrementOver;
+
+ struct SetIncrementScriptLevelCommand {
+ uint32_t mChildIndex;
+ bool mDoIncrement;
+ };
+
+ nsTArray<SetIncrementScriptLevelCommand>
+ mPostReflowIncrementScriptLevelCommands;
+};
+
+#endif /* nsMathMLmunderoverFrame_h___ */
diff --git a/layout/mathml/nsMathMLsemanticsFrame.cpp b/layout/mathml/nsMathMLsemanticsFrame.cpp
new file mode 100644
index 0000000000..9fd9d80a36
--- /dev/null
+++ b/layout/mathml/nsMathMLsemanticsFrame.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/PresShell.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+
+//
+// <semantics> -- associate annotations with a MathML expression
+//
+
+nsIFrame* NS_NewMathMLsemanticsFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLsemanticsFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLsemanticsFrame)
+
+nsMathMLsemanticsFrame::~nsMathMLsemanticsFrame() = default;
+
+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->AsElement()->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->AsElement()->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->AsElement()->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..148c0f5697
--- /dev/null
+++ b/layout/mathml/nsMathMLsemanticsFrame.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+//
+// <semantics> -- associate annotations with a MathML expression
+//
+
+class nsMathMLsemanticsFrame final : public nsMathMLSelectedFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsMathMLsemanticsFrame)
+
+ friend nsIFrame* NS_NewMathMLsemanticsFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit nsMathMLsemanticsFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsMathMLSelectedFrame(aStyle, aPresContext, kClassID) {}
+ 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..257a063330
--- /dev/null
+++ b/layout/mathml/operatorDictionary.xsl
@@ -0,0 +1,58 @@
+<!-- -*- 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="@*[.='true']">
+ <xsl:attribute name="properties">
+ <!-- largeop, movablelimits, stretchy, separator, fence,
+ symmetric -->
+ <xsl:for-each select="@*[.='true']">
+ <xsl:value-of select="name()"/>
+ <xsl:text> </xsl:text>
+ </xsl:for-each>
+ </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/file_bug706406_iframe.html b/layout/mathml/tests/file_bug706406_iframe.html
new file mode 100644
index 0000000000..e1fd3f7423
--- /dev/null
+++ b/layout/mathml/tests/file_bug706406_iframe.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+/* For some reason, test is failing on treeherder if the rules from the UA sheet
+ are not overridden. */
+ maction > :not(:first-child) { display: inline; }
+</style>
+ <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>
+window.is = window.parent.is;
+window.SimpleTest = window.parent.SimpleTest;
+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>
diff --git a/layout/mathml/tests/file_disabled_iframe.html b/layout/mathml/tests/file_disabled_iframe.html
new file mode 100644
index 0000000000..24ff4847ae
--- /dev/null
+++ b/layout/mathml/tests/file_disabled_iframe.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<script>
+window.is = window.parent.is;
+window.SimpleTest = window.parent.SimpleTest;
+</script>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<pre id="test">
+<script>
+ let t = document.getElementById('testnodes');
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math:math"));
+ t.firstChild.textContent = "<foo>";
+ is(t.innerHTML, "<math:math>&lt;foo&gt;</math:math>");
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "script"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<math><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></math>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "style"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<math><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></math>');
+
+ SimpleTest.finish();
+</script>
+</pre>
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..f5aec6be3d
--- /dev/null
+++ b/layout/mathml/tests/mochitest.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files =
+ file_disabled_iframe.html
+ file_bug706406_iframe.html
+
+[test_bug330964.html]
+[test_bug553917.html]
+[test_bug706406.html]
+disabled = bug 1789349
+[test_bug827713-2.html]
+[test_disabled.html]
+[test_opentype-axis-height.html]
+[test_opentype-limits.html]
+skip-if = os == "win" # Fails on WinXP
diff --git a/layout/mathml/tests/test_bug330964.html b/layout/mathml/tests/test_bug330964.html
new file mode 100644
index 0000000000..84da15194e
--- /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 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..0992e6708d
--- /dev/null
+++ b/layout/mathml/tests/test_bug553917.html
@@ -0,0 +1,221 @@
+<!DOCTYPE HTML>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=553917
+-->
+<html>
+ <head>
+ <title>Test for Bug 553917</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script 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");
+
+ const allow_mathspace_names = !SpecialPowers.getBoolPref('mathml.mathspace_names.disabled');
+ const allow_scriptsizemultiplier_attribute = !SpecialPowers.getBoolPref('mathml.scriptsizemultiplier_attribute.disabled');
+
+ 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><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, !allow_scriptsizemultiplier_attribute],
+ args: [["BAD!","scriptlevel"], ["BAD!","scriptsizemultiplier"]]
+ },
+ /* <math><mo rspace="2..0px">+</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>
+ <math><mfrac linethickness="thin"><mn>1</mn><mn>2</mn></mfrac></math>
+ <math><mfrac linethickness="medium"><mn>1</mn><mn>2</mn></mfrac></math>
+ <math><mfrac linethickness="thick"><mn>1</mn><mn>2</mn></mfrac></math>
+ <math><mstyle mathsize="small"></mstyle></math>
+ <math><mstyle mathsize="normal"></mstyle></math>
+ <math><mstyle mathsize="big"></mstyle></math>
+ <math><mspace width="veryverythinmathspace"/></math>
+ <math><mspace width="verythinmathspace"/></math>
+ <math><mspace width="thinmathspace"/></math>
+ <math><mspace width="mediummathspace"/></math>
+ <math><mspace width="thickmathspace"/></math>
+ <math><mspace width="verythickmathspace"/></math>
+ <math><mspace width="veryverythickmathspace"/></math>
+ <math><mspace width="12345."/></math>
+ <math><mo minsize="17">+</mo></math>
+ */
+ LengthParsingError : {
+ status: [false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ allow_mathspace_names,
+ allow_mathspace_names,
+ allow_mathspace_names,
+ allow_mathspace_names,
+ allow_mathspace_names,
+ allow_mathspace_names,
+ allow_mathspace_names,
+ false,
+ false,
+ ],
+ args: [["2..0px"],
+ ["1.5notaunit"],
+ ["2"],
+ ["BADlspace"],
+ ["BADheight"],
+ ["BADdepth"],
+ ["thin"],
+ ["medium"],
+ ["thick"],
+ ["small"],
+ ["normal"],
+ ["big"],
+ ["veryverythinmathspace"],
+ ["verythinmathspace"],
+ ["thinmathspace"],
+ ["mediummathspace"],
+ ["thickmathspace"],
+ ["verythickmathspace"],
+ ["veryverythickmathspace"],
+ ["12345."],
+ ["17"]
+ ]
+ },
+ /*<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"]
+ }};
+
+ var g_errorTypes = ["ChildCountIncorrect","AttributeParsingError",
+ "AttributeParsingErrorNoTag","LengthParsingError", "MMultiscriptsErrors"];
+
+ function getErrorMessage(name,idx)
+ {
+ if (name != "MMultiscriptsErrors") {
+ var formatParams = g_errorInfo[name].args[idx];
+ if (formatParams.length > 0) {
+ return g_bundl.formatStringFromName(name,formatParams);
+ }
+ return g_bundl.GetStringFromName(name);
+ }
+ 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>
+
+ <!-- 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..0px">+</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>
+ <math><mfrac linethickness="thin"><mn>1</mn><mn>2</mn></mfrac></math>
+ <math><mfrac linethickness="medium"><mn>1</mn><mn>2</mn></mfrac></math>
+ <math><mfrac linethickness="thick"><mn>1</mn><mn>2</mn></mfrac></math>
+ <math><mstyle mathsize="small"></mstyle></math>
+ <math><mstyle mathsize="normal"></mstyle></math>
+ <math><mstyle mathsize="big"></mstyle></math>
+ <math><mspace width="veryverythinmathspace"/></math>
+ <math><mspace width="verythinmathspace"/></math>
+ <math><mspace width="thinmathspace"/></math>
+ <math><mspace width="mediummathspace"/></math>
+ <math><mspace width="thickmathspace"/></math>
+ <math><mspace width="verythickmathspace"/></math>
+ <math><mspace width="veryverythickmathspace"/></math>
+ <math><mspace width="12345."/></math>
+ <math><mo minsize="17">+</mo></math>
+
+ <!-- MMultiscriptsErrors -->
+ <math><mmultiscripts></mmultiscripts></math>
+ <math><mmultiscripts><mprescripts/><mprescripts/></mmultiscripts></math>
+ <math><mmultiscripts><mi>x</mi><mi>y</mi></mmultiscripts></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..72e7872844
--- /dev/null
+++ b/layout/mathml/tests/test_bug706406.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=706406">Mozilla Bug 706406</a>
+<iframe></iframe>
+<pre id="test">
+<script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["mathml.legacy_maction_and_semantics_implementations.disabled", false]]}, function() {
+ document.querySelector('iframe').src = "file_bug706406_iframe.html";
+ });
+</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..8143bc0493
--- /dev/null
+++ b/layout/mathml/tests/test_bug827713-2.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=553917
+-->
+<html>
+ <head>
+ <title>Test for error handling aspect of Bug 827713</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script 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],
+ args : [["mprescripts", "msubsup"], ["mprescripts", "msubsup"],
+ ["mprescripts", "msub"]]
+ },
+
+ 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]);
+ }
+ 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>
+
+ <!-- NoBase -->
+ <math>
+ <mmultiscripts>
+ </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_disabled.html b/layout/mathml/tests/test_disabled.html
new file mode 100644
index 0000000000..f0c62ec994
--- /dev/null
+++ b/layout/mathml/tests/test_disabled.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Copied from https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a>
+<iframe></iframe>
+<pre id="test">
+<script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["mathml.disabled", true]]}, function() {
+ document.querySelector('iframe').src = "file_disabled_iframe.html";
+ });
+</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..af97f2d430
--- /dev/null
+++ b/layout/mathml/tests/test_disabled_chrome.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<!--
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+-->
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/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(async function() {
+ const initialPrefValue = SpecialPowers.getBoolPref("mathml.disabled");
+ SpecialPowers.setBoolPref("mathml.disabled", true);
+ const Cu = SpecialPowers.Components.utils;
+ const { ContentTaskUtils } = ChromeUtils.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);
+
+ await 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);
+ await loadPromise;
+
+ const contentBR = iframeEl.contentDocument.body.getBoundingClientRect();
+
+ ok(chromeBR.height > contentBR.height, "Chrome content height should be bigger than content due to layout");
+
+ 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..f65e276253
--- /dev/null
+++ b/layout/mathml/tests/test_opentype-axis-height.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - axis-height</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script 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-limits.html b/layout/mathml/tests/test_opentype-limits.html
new file mode 100644
index 0000000000..56d30fd738
--- /dev/null
+++ b/layout/mathml/tests/test_opentype-limits.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - limits</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script 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-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("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-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/updateOperatorDictionary.pl b/layout/mathml/updateOperatorDictionary.pl
new file mode 100755
index 0000000000..01fc4d0a88
--- /dev/null
+++ b/layout/mathml/updateOperatorDictionary.pl
@@ -0,0 +1,459 @@
+#!/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";
+
+# our dictionary (property file)
+$MOZ_DICTIONARY = "mathfont.properties";
+
+# dictionary provided by the W3C in "XML Entity Definitions for Characters"
+$WG_DICTIONARY_URL = "https://raw.githubusercontent.com/w3c/xml-entities/gh-pages/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 "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
+# 4 | largeop
+# 5 | movablelimits
+# 6 | stretchy
+# 7 | separator
+# 8 | accent
+# 9 | fence
+# 10 | symmetric
+# 13 | direction
+
+# 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"; }
+ $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(.*)$/);
+ if (m/^(.*)direction:([a-z]*)(.*)$/) { $value[13] = $2; }
+ else { $value[13] = ""; }
+
+ # 1.3) save the key and value
+ $moz_hash{$key} = [ @value ];
+}
+
+close($file);
+
+################################################################################
+# 2) If mode "check", verify validity of our operator dictionary and quit.
+# If mode "compare", go to step 3)
+
+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)) {
+
+ if ($key =~ /\\u.+\\u.+\\u.+/) {
+ $valid = 0;
+ $nb_errors++;
+ print $file_syntax_errors "error: \"$key\" has more than 2 characters\n";
+ }
+
+ if ($key =~ /\\u20D2\./ || $key =~ /\\u0338\./) {
+ $valid = 0;
+ $nb_errors++;
+ print $file_syntax_errors "error: \"$key\" ends with character U+20D2 or U+0338\n";
+ }
+
+ @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[13] eq "vertical")) {
+ $valid = 0;
+ $nb_errors++;
+ print $file_syntax_errors "error: operator is largeop but does not have vertical direction\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");
+
+ # Skip non-BMP Arabic characters that are handled specially.
+ if ($_ == "U1EEF0" || $_ == "U1EEF1") {
+ next;
+ }
+
+ $_ = "$_-";
+ 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"; }
+
+ $_ = $entry->getAttribute("properties");
+ $value[4] = (m/^(.*)largeop(.*)$/);
+ $value[5] = (m/^(.*)movablelimits(.*)$/);
+ $value[6] = (m/^(.*)stretchy(.*)$/);
+ $value[7] = (m/^(.*)separator(.*)$/);
+ $value[9] = (m/^(.*)fence(.*)$/);
+ $value[10] = (m/^(.*)symmetric(.*)$/);
+
+ # not stored in the WG dictionary
+ $value[8] = ""; # accent
+ $value[13] = ""; # direction
+
+ # 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 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[4]) { $entry = "$entry largeop"; }
+ if ($v[5]) { $entry = "$entry movablelimits"; }
+ if ($v[6]) { $entry = "$entry stretchy"; }
+ if ($v[7]) { $entry = "$entry separator"; }
+ if ($v[9]) { $entry = "$entry fence"; }
+ if ($v[10]) { $entry = "$entry symmetric"; }
+ 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[8]) { $entry = "$entry accent"; }
+ if ($v_moz[13]) { $entry = "$entry direction:$v_moz[13]"; }
+
+ 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;
+}