/* -*- 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(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);
}
