diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /layout/mathml | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/mathml')
62 files changed, 17627 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 # ∳ +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..03eb412a15 --- /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..8ea4bd34ba --- /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(¶ms, 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..884f5e8b6b --- /dev/null +++ b/layout/mathml/nsMathMLContainerFrame.cpp @@ -0,0 +1,1504 @@ +/* -*- 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 ∑ might appear big in the following situation + // <math xmlns='http://www.w3.org/1998/Math/MathML'> + // <mstyle> + // <msub> + // <msub><mo>∑</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub> + // <msub><mo>∑</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub> + // </msub> + // </mstyle> + // </math> + nsBoundingMetrics containerSize = aContainerSize; + if (aStretchDirection != mEmbellishData.direction && + mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED) { + NS_ASSERTION( + mEmbellishData.direction != NS_STRETCH_DIRECTION_DEFAULT, + "Stretches may have a default direction, operators can not."); + if (mEmbellishData.direction == NS_STRETCH_DIRECTION_VERTICAL + ? NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY( + mPresentationData.flags) + : NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY( + mPresentationData.flags)) { + GetPreferredStretchSize(aDrawTarget, 0, mEmbellishData.direction, + containerSize); + // Stop further recalculations + aStretchDirection = mEmbellishData.direction; + } else { + // We aren't going to stretch the child, so just use the child + // metrics. + containerSize = childSize.mBoundingMetrics; + } + } + + // do the stretching... + mathMLFrame->Stretch(aDrawTarget, aStretchDirection, containerSize, + childSize); + // store the updated metrics + SaveReflowAndBoundingMetricsFor(baseFrame, childSize, + childSize.mBoundingMetrics); + + // Remember the siblings which were _deferred_. + // Now that this embellished child may have changed, we need to + // fire the stretch on its siblings using our updated size + + if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY( + mPresentationData.flags) || + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY( + mPresentationData.flags)) { + nsStretchDirection stretchDir = + NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY( + mPresentationData.flags) + ? NS_STRETCH_DIRECTION_VERTICAL + : NS_STRETCH_DIRECTION_HORIZONTAL; + + GetPreferredStretchSize(aDrawTarget, STRETCH_CONSIDER_EMBELLISHMENTS, + stretchDir, containerSize); + + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + if (childFrame != mPresentationData.baseFrame) { + mathMLFrame = do_QueryFrame(childFrame); + if (mathMLFrame) { + // retrieve the metrics that was stored at the previous pass + GetReflowAndBoundingMetricsFor(childFrame, childSize, + childSize.mBoundingMetrics); + // do the stretching... + mathMLFrame->Stretch(aDrawTarget, stretchDir, containerSize, + childSize); + // store the updated metrics + SaveReflowAndBoundingMetricsFor(childFrame, childSize, + childSize.mBoundingMetrics); + } + } + childFrame = childFrame->GetNextSibling(); + } + } + + // re-position all our children + nsresult rv = Place(aDrawTarget, true, aDesiredStretchSize); + if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) { + // Make sure the child frames get their DidReflow() calls. + DidReflowChildren(mFrames.FirstChild()); + } + + // If our parent is not embellished, it means we are the outermost + // embellished container and so we put the spacing, otherwise we don't + // include the spacing, the outermost embellished container will take + // care of it. + + nsEmbellishData parentData; + GetEmbellishDataFrom(GetParent(), parentData); + // ensure that we are the embellished child, not just a sibling + // (need to test coreFrame since <mfrac> resets other things) + if (parentData.coreFrame != mEmbellishData.coreFrame) { + // (we fetch values from the core since they may use units that depend + // on style data, and style changes could have occurred in the core + // since our last visit there) + nsEmbellishData coreData; + GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData); + + mBoundingMetrics.width += + coreData.leadingSpace + coreData.trailingSpace; + aDesiredStretchSize.Width() = mBoundingMetrics.width; + aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width; + + nscoord dx = StyleVisibility()->mDirection == 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(DestroyContext& aContext, + ChildListID aListID, + nsIFrame* aOldFrame) { + MOZ_ASSERT(aListID == FrameChildListID::Principal); + mFrames.DestroyFrame(aContext, 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) { + if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { + return; + } + + 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>⁡</mo> <mi>x</mi> + // 2. <mi>x</mi> <mo>&InvisibileTime;</mo> <mi>sin</mi> + // + // the trick to get the inter-space in either situation + // is to promote "<mi>sin</mi><mo>⁡</mo>" and + // "<mo>&InvisibileTime;</mo><mi>sin</mi>" to user-defined operators... + if (firstType == eMathMLFrameType_UprightIdentifier) { + firstType = eMathMLFrameType_OperatorUserDefined; + } else if (secondType == eMathMLFrameType_UprightIdentifier) { + secondType = eMathMLFrameType_OperatorUserDefined; + } + + GET_INTERSPACE(aScriptLevel, firstType, secondType, space); + + // Now, we have two values: the computed space and the space that + // has been carried forward until now. Which value do we pick? + // If the second type is an operator (e.g., fence), it already has + // built-in lspace & rspace, so we let them win. Otherwise we pick + // the max between the two values that we have. + if (secondType != eMathMLFrameType_OperatorOrdinary && space < *aCarrySpace) + space = *aCarrySpace; + + // reset everything now that the carry-forward is done + *aFromFrameType = eMathMLFrameType_UNKNOWN; + *aCarrySpace = 0; + } + + return space; +} + +static nscoord GetThinSpace(const nsStyleFont* aStyleFont) { + return 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..29818c9494 --- /dev/null +++ b/layout/mathml/nsMathMLContainerFrame.h @@ -0,0 +1,525 @@ +/* -*- 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 + + 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; + + void RemoveFrame(DestroyContext&, ChildListID aListID, + nsIFrame* aOldFrame) override; + + /** + * Both GetMinISize and GetPrefISize use the intrinsic width metrics + * returned by GetIntrinsicMetrics, including ink overflow. + */ + nscoord GetMinISize(gfxContext* aRenderingContext) override; + nscoord GetPrefISize(gfxContext* aRenderingContext) override; + + /** + * Return the intrinsic horizontal metrics of the frame's content area. + */ + virtual void GetIntrinsicISizeMetrics(gfxContext* aRenderingContext, + ReflowOutput& aDesiredSize); + + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + void DidReflow(nsPresContext* aPresContext, + const ReflowInput* aReflowInput) override + + { + mPresentationData.flags &= ~NS_MATHML_STRETCH_DONE; + return nsContainerFrame::DidReflow(aPresContext, aReflowInput); + } + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas) override; + + 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>). + 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); + } + } + + void RemoveFrame(DestroyContext& aContext, ChildListID aListID, + nsIFrame* aOldFrame) override { + NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal || + aListID == mozilla::FrameChildListID::NoReflowPrincipal, + "unexpected frame list"); + nsBlockFrame::RemoveFrame(aContext, 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); + } + } + + void RemoveFrame(DestroyContext& aContext, ChildListID aListID, + nsIFrame* aOldFrame) override { + NS_ASSERTION(aListID == mozilla::FrameChildListID::Principal || + aListID == mozilla::FrameChildListID::NoReflowPrincipal, + "unexpected frame list"); + nsInlineFrame::RemoveFrame(aContext, aListID, aOldFrame); + if (MOZ_LIKELY(aListID == mozilla::FrameChildListID::Principal)) { + nsMathMLContainerFrame::ReLayoutChildren(this); + } + } + + 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) || // − + (ch == 0x2264) || // ≤ + (ch == 0x2265) || // ≥ + (ch == 0x00D7)) { // × + mFlags |= NS_MATHML_OPERATOR_CENTERED; + } + } + + // cache the operator + mMathMLChar.SetData(data); + + // cache the native direction -- beware of bug 133429... + // mEmbellishData.direction must always retain our native direction, whereas + // mMathMLChar.GetStretchDirection() may change later, when Stretch() is + // called + mEmbellishData.direction = mMathMLChar.GetStretchDirection(); + + bool isMutable = + NS_MATHML_OPERATOR_IS_LARGEOP(allFlags) || + (mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED); + if (isMutable) mFlags |= NS_MATHML_OPERATOR_MUTABLE; + + 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..42af4e21cf --- /dev/null +++ b/layout/mathml/nsMathMLmtableFrame.cpp @@ -0,0 +1,1221 @@ +/* -*- 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()); + + const auto a2d = aFrame->PresContext()->AppUnitsPerDevPixel(); + + // 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, a2d); + } + + // 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, a2d); + } +} + +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..3d108cb5ef --- /dev/null +++ b/layout/mathml/nsMathMLmtableFrame.h @@ -0,0 +1,297 @@ +/* -*- 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 + + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + 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(); + } + + void RemoveFrame(DestroyContext& aContext, ChildListID aListID, + nsIFrame* aOldFrame) override { + nsTableFrame::RemoveFrame(aContext, aListID, aOldFrame); + RestyleTable(); + } + + 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(); + } + + void RemoveFrame(DestroyContext& aContext, ChildListID aListID, + nsIFrame* aOldFrame) override { + nsTableRowFrame::RemoveFrame(aContext, aListID, aOldFrame); + RestyleTable(); + } + + 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 + + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + mozilla::StyleVerticalAlignKeyword GetVerticalAlign() const override; + nsresult ProcessBorders(nsTableFrame* aFrame, + mozilla::nsDisplayListBuilder* aBuilder, + const mozilla::nsDisplayListSet& aLists) override; + + bool IsFrameOfType(uint32_t aFlags) const override { + return nsTableCellFrame::IsFrameOfType(aFlags & ~(nsIFrame::eMathML)); + } + + LogicalMargin GetBorderWidth(WritingMode aWM) const override; + + 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; + } + + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + bool IsFrameOfType(uint32_t aFlags) const override { + return nsBlockFrame::IsFrameOfType(aFlags & ~nsIFrame::eMathML); + } + + const nsStyleText* StyleTextForLineLayout() override; + 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..21ac68c77d --- /dev/null +++ b/layout/mathml/nsMathMLmunderoverFrame.cpp @@ -0,0 +1,699 @@ +/* -*- 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::Destroy(DestroyContext& aContext) { + if (!mPostReflowIncrementScriptLevelCommands.IsEmpty()) { + PresShell()->CancelReflowCallback(this); + } + nsMathMLContainerFrame::Destroy(aContext); +} + +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 ∑. + +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..1282464ed3 --- /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 Destroy(DestroyContext&) 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><foo></math:math>"); + + t.innerHTML = null; + t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math")); + is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML"); + t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "script")); + is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML"); + t.firstChild.firstChild.textContent = "1&2<3>4\xA0"; + is(t.innerHTML, '<math><script>1&2<3>4 \u003C/script></math>'); + + t.innerHTML = null; + t.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math")); + is(t.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML"); + t.firstChild.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "style")); + is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML"); + t.firstChild.firstChild.textContent = "1&2<3>4\xA0"; + is(t.innerHTML, '<math><style>1&2<3>4 \u003C/style></math>'); + + SimpleTest.finish(); +</script> +</pre> 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; +} |