Skip to content

Commit

Permalink
Bug 1916988 - Support CSS width/height properties on MathML elements.…
Browse files Browse the repository at this point in the history
… r=emilio

This patch implements support for the width/height properties on
MathML elements [1]. The general algorithm from the spec is as
follows:

(1) The outcome of the math layout is a "math content box".
(2) The content box sets its size from computed width/height values. If
  auto, it's the one of the "math content box". This patch ignores
  percentage values for now [2] [3].
(3) math content box is shifted so that its inline-start and top edges
  aligns with the ones of the content box. There are exceptions
  elements like mfrac and munder/mover/munderover which instead
  horizontally center the math content box within the content box.
  For baseline adjustment, we follow what Chromium does, see [4].
(4) Padding+border are added around the content box. Note that we
  ignore the box-sizing property for now [5].

The patch essentially tweaks the various MathML layout algorithms to
perform steps (3) and (4) before the calls to
GetBorderPaddingForPlace and InflateReflowAndBoundingMetrics.

[1] https://w3c.github.io/mathml-core/#layout-algorithms
[2] w3c/mathml-core#76
[3] w3c/mathml-core#77
[4] w3c/mathml-core#259
[5] w3c/mathml-core#257

Below is more information about test coverage:

- width-height-001: Verify that width, height, inline-size and block-size
  properties sets the size of the content box. This test used to verify
  they are ignored, this patch fixes the `<meta name="assert">` tag.
  It also adds a test for the case the specified size is smaller than the
  content (we force non empty descendants to make sure this content is
  large enough) and to verify the width is used for the preferred width.

- width-height-002, width-height-003: These are reftests visually checking
  offsets of the math content box within a larger content box (specified
  by width/height) for the mtext, mrow, mpadded, mfrac, msqrt, mroot,
  in LTR/RTL modes. In particular they allow to verify some painted
  elements like fraction bar and radical symbols.

- width-height-004: This test more directly checks that the math content
  box is horizontally centered within a larger content box for munder,
  mover, munderover and mfrac. This patch extends the test to cover the
  case when the math content box is wider (i.e. overflowing outside the
  content box) and removes unnecessary specified height.

- width-height-005: New test for other layout algorithm that don't
  center the math content box, checking inline-start edges of children
  when a width is specified. We check both LTR/RTL modes and
  wider/narrower content boxes.

- width-height-006: Same but checking the top edges for larger/smaller
  height and verifying that baseline is perserved.

Differential Revision: https://phabricator.services.mozilla.com/D221436
  • Loading branch information
fred-wang committed Sep 30, 2024
1 parent a66618b commit 3d16d95
Show file tree
Hide file tree
Showing 24 changed files with 1,208 additions and 176 deletions.
61 changes: 60 additions & 1 deletion layout/mathml/nsMathMLContainerFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,59 @@ void nsMathMLContainerFrame::InflateReflowAndBoundingMetrics(
aReflowOutput.Height() += aBorderPadding.TopBottom();
}

nsMathMLContainerFrame::WidthAndHeightForPlaceAdjustment
nsMathMLContainerFrame::GetWidthAndHeightForPlaceAdjustment(
const PlaceFlags& aFlags) {
WidthAndHeightForPlaceAdjustment sizes;
if (aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight)) {
return sizes;
}
const nsStylePosition* stylePos = StylePosition();
const auto& width = stylePos->mWidth;
// TODO: Resolve percentages.
// https://github.com/w3c/mathml-core/issues/76
if (width.ConvertsToLength()) {
sizes.width = Some(width.ToLength());
}
if (!aFlags.contains(PlaceFlag::IntrinsicSize)) {
// TODO: Resolve percentages.
// https://github.com/w3c/mathml-core/issues/77
const auto& height = stylePos->mHeight;
if (height.ConvertsToLength()) {
sizes.height = Some(height.ToLength());
}
}
return sizes;
}

nscoord nsMathMLContainerFrame::ApplyAdjustmentForWidthAndHeight(
const PlaceFlags& aFlags, const WidthAndHeightForPlaceAdjustment& aSizes,
ReflowOutput& aReflowOutput, nsBoundingMetrics& aBoundingMetrics) {
nscoord shiftX = 0;
if (aSizes.width) {
MOZ_ASSERT(!aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight));
auto width = *aSizes.width;
auto oldWidth = aReflowOutput.Width();
if (IsMathContentBoxHorizontallyCentered()) {
shiftX = (width - oldWidth) / 2;
} else if (StyleVisibility()->mDirection == StyleDirection::Rtl) {
shiftX = width - oldWidth;
}
aBoundingMetrics.leftBearing = 0;
aBoundingMetrics.rightBearing = width;
aBoundingMetrics.width = width;
aReflowOutput.mBoundingMetrics = aBoundingMetrics;
aReflowOutput.Width() = width;
}
if (aSizes.height) {
MOZ_ASSERT(!aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight));
MOZ_ASSERT(!aFlags.contains(PlaceFlag::IntrinsicSize));
auto height = *aSizes.height;
aReflowOutput.Height() = height;
}
return shiftX;
}

// helper to get the preferred size that a container frame should use to fire
// the stretch on its stretchy child frames.
void nsMathMLContainerFrame::GetPreferredStretchSize(
Expand Down Expand Up @@ -1169,18 +1222,24 @@ nsresult nsMathMLContainerFrame::Place(DrawTarget* aDrawTarget,
aDesiredSize.SetBlockStartAscent(ascent);
aDesiredSize.mBoundingMetrics = mBoundingMetrics;

// Apply inline/block sizes to math content box.
auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags);
nscoord shiftX = ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
mBoundingMetrics);

// Add padding+border.
auto borderPadding = GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
mBoundingMetrics);
shiftX += borderPadding.left;

mReference.x = 0;
mReference.y = aDesiredSize.BlockStartAscent();

//////////////////
// Place Children
if (!aFlags.contains(PlaceFlag::MeasureOnly)) {
PositionRowChildFrames(borderPadding.left, aDesiredSize.BlockStartAscent());
PositionRowChildFrames(shiftX, aDesiredSize.BlockStartAscent());
}

return NS_OK;
Expand Down
17 changes: 17 additions & 0 deletions layout/mathml/nsMathMLContainerFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ class nsMathMLContainerFrame : public nsContainerFrame, public nsMathMLFrame {
// place some radical symbol on top of them and finally add its
// padding/border around that radical symbol.
IgnoreBorderPadding,

// If DoNotAdjustForWidthAndHeight is set, the function will complete
// without setting the computed width and height after the math layout. This
// can be used similarly to IgnoreBorderPadding above.
DoNotAdjustForWidthAndHeight,
};
using PlaceFlags = mozilla::EnumSet<PlaceFlag>;

Expand Down Expand Up @@ -247,6 +252,18 @@ class nsMathMLContainerFrame : public nsContainerFrame, public nsMathMLFrame {

nsMargin GetBorderPaddingForPlace(const PlaceFlags& aFlags);

struct WidthAndHeightForPlaceAdjustment {
mozilla::Maybe<nscoord> width;
mozilla::Maybe<nscoord> height;
};
WidthAndHeightForPlaceAdjustment GetWidthAndHeightForPlaceAdjustment(
const PlaceFlags& aFlags);

virtual bool IsMathContentBoxHorizontallyCentered() const { return false; }
nscoord ApplyAdjustmentForWidthAndHeight(
const PlaceFlags& aFlags, const WidthAndHeightForPlaceAdjustment& aSizes,
ReflowOutput& aReflowOutput, nsBoundingMetrics& aBoundingMetrics);

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
Expand Down
6 changes: 6 additions & 0 deletions layout/mathml/nsMathMLTokenFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,19 @@ nsresult nsMathMLTokenFrame::Place(DrawTarget* aDrawTarget,
aDesiredSize.Height() = aDesiredSize.BlockStartAscent() +
std::max(mBoundingMetrics.descent, descent);

// Apply width/height to math content box.
auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags);
auto shiftX = ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
mBoundingMetrics);

// Add padding+border.
auto borderPadding = GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
mBoundingMetrics);

if (!aFlags.contains(PlaceFlag::MeasureOnly)) {
nscoord dx = borderPadding.left;
dx += shiftX;
for (nsIFrame* childFrame : PrincipalChildList()) {
ReflowOutput childSize(aDesiredSize.GetWritingMode());
GetReflowAndBoundingMetricsFor(childFrame, childSize,
Expand Down
10 changes: 8 additions & 2 deletions layout/mathml/nsMathMLmencloseFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,9 @@ nsresult nsMathMLmencloseFrame::Place(DrawTarget* aDrawTarget,
// Measure the size of our content using the base class to format like an
// inferred mrow, without border/padding.
ReflowOutput baseSize(aDesiredSize.GetWritingMode());
PlaceFlags flags =
aFlags + PlaceFlag::MeasureOnly + PlaceFlag::IgnoreBorderPadding;
PlaceFlags flags = aFlags + PlaceFlag::MeasureOnly +
PlaceFlag::IgnoreBorderPadding +
PlaceFlag::DoNotAdjustForWidthAndHeight;
nsresult rv = nsMathMLContainerFrame::Place(aDrawTarget, flags, baseSize);

if (NS_FAILED(rv)) {
Expand Down Expand Up @@ -527,6 +528,11 @@ nsresult nsMathMLmencloseFrame::Place(DrawTarget* aDrawTarget,

aDesiredSize.mBoundingMetrics = mBoundingMetrics;

// Apply width/height to math content box.
auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags);
dx_left += ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
mBoundingMetrics);

// Add padding+border.
auto borderPadding = GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
Expand Down
12 changes: 12 additions & 0 deletions layout/mathml/nsMathMLmfracFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,18 @@ nsresult nsMathMLmfracFrame::Place(DrawTarget* aDrawTarget,
aDesiredSize.Width() = mBoundingMetrics.width;
aDesiredSize.mBoundingMetrics = mBoundingMetrics;

// Apply width/height to math content box.
auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags);
auto shiftX = ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
mBoundingMetrics);
if (sizes.width) {
// MathML Core says the math content box is horizontally centered
// but the fraction bar still takes the full width of the content box.
dxNum += shiftX;
dxDen += shiftX;
width = *sizes.width;
}

// Add padding+border.
auto borderPadding = GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
Expand Down
2 changes: 2 additions & 0 deletions layout/mathml/nsMathMLmfracFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class nsMathMLmfracFrame final : public nsMathMLContainerFrame {
mLineThickness(0) {}
virtual ~nsMathMLmfracFrame();

bool IsMathContentBoxHorizontallyCentered() const final { return true; }

// Display a slash
void DisplaySlash(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
nscoord aThickness, const nsDisplayListSet& aLists);
Expand Down
5 changes: 5 additions & 0 deletions layout/mathml/nsMathMLmmultiscriptsFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,11 @@ nsresult nsMathMLmmultiscriptsFrame::PlaceMultiScript(
aDesiredSize.Width() = boundingMetrics.width;
aDesiredSize.mBoundingMetrics = boundingMetrics;

// Apply width/height to math content box.
auto sizes = aFrame->GetWidthAndHeightForPlaceAdjustment(aFlags);
aFrame->ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
boundingMetrics);

// Add padding+border.
auto borderPadding = aFrame->GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize, boundingMetrics);
Expand Down
11 changes: 9 additions & 2 deletions layout/mathml/nsMathMLmoFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,8 @@ nsMathMLmoFrame::Stretch(DrawTarget* aDrawTarget,

// Place our children using the default method and no border/padding.
// This will allow our child text frame to get its DidReflow()
PlaceFlags flags = PlaceFlag::IgnoreBorderPadding;
PlaceFlags flags(PlaceFlag::IgnoreBorderPadding,
PlaceFlag::DoNotAdjustForWidthAndHeight);
nsresult rv = Place(aDrawTarget, flags, aDesiredStretchSize);
if (NS_FAILED(rv)) {
// Make sure the child frames get their DidReflow() calls.
Expand Down Expand Up @@ -852,8 +853,10 @@ nsMathMLmoFrame::Stretch(DrawTarget* aDrawTarget,
}

flags = PlaceFlags();
auto sizes = GetWidthAndHeightForPlaceAdjustment(flags);
auto borderPadding = GetBorderPaddingForPlace(flags);
if (leadingSpace || trailingSpace || !borderPadding.IsAllZero()) {
if (leadingSpace || trailingSpace || !borderPadding.IsAllZero() ||
sizes.width || sizes.height) {
mBoundingMetrics.width += leadingSpace + trailingSpace;
aDesiredStretchSize.Width() = mBoundingMetrics.width;
aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width;
Expand All @@ -866,6 +869,10 @@ nsMathMLmoFrame::Stretch(DrawTarget* aDrawTarget,
aDesiredStretchSize.mBoundingMetrics.leftBearing += dx;
aDesiredStretchSize.mBoundingMetrics.rightBearing += dx;

// Apply inline/block sizes to math content box.
dx += ApplyAdjustmentForWidthAndHeight(flags, sizes, aDesiredStretchSize,
mBoundingMetrics);

// Add border/padding.
InflateReflowAndBoundingMetrics(borderPadding, aDesiredStretchSize,
mBoundingMetrics);
Expand Down
10 changes: 8 additions & 2 deletions layout/mathml/nsMathMLmpaddedFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,9 @@ nsresult nsMathMLmpaddedFrame::Place(DrawTarget* aDrawTarget,
const PlaceFlags& aFlags,
ReflowOutput& aDesiredSize) {
// First perform normal row layout without border/padding.
PlaceFlags flags =
aFlags + PlaceFlag::MeasureOnly + PlaceFlag::IgnoreBorderPadding;
PlaceFlags flags = aFlags + PlaceFlag::MeasureOnly +
PlaceFlag::IgnoreBorderPadding +
PlaceFlag::DoNotAdjustForWidthAndHeight;
nsresult rv = nsMathMLContainerFrame::Place(aDrawTarget, flags, aDesiredSize);
if (NS_FAILED(rv)) {
DidReflowChildren(PrincipalChildList().FirstChild());
Expand Down Expand Up @@ -388,6 +389,11 @@ nsresult nsMathMLmpaddedFrame::Place(DrawTarget* aDrawTarget,
mBoundingMetrics.descent = depth;
aDesiredSize.mBoundingMetrics = mBoundingMetrics;

// Apply width/height to math content box.
auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags);
dx += ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
mBoundingMetrics);

// Add padding+border.
auto borderPadding = GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
Expand Down
13 changes: 10 additions & 3 deletions layout/mathml/nsMathMLmrootFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,9 @@ nsresult nsMathMLmrootFrame::Place(DrawTarget* aDrawTarget,
} else {
// Format our content as an mrow without border/padding to obtain the
// square root base. The metrics/frame for the index are ignored.
PlaceFlags flags =
aFlags + PlaceFlag::MeasureOnly + PlaceFlag::IgnoreBorderPadding;
PlaceFlags flags = aFlags + PlaceFlag::MeasureOnly +
PlaceFlag::IgnoreBorderPadding +
PlaceFlag::DoNotAdjustForWidthAndHeight;
nsresult rv = nsMathMLContainerFrame::Place(aDrawTarget, flags, baseSize);
if (NS_FAILED(rv)) {
DidReflowChildren(PrincipalChildList().FirstChild());
Expand Down Expand Up @@ -317,6 +318,12 @@ nsresult nsMathMLmrootFrame::Place(DrawTarget* aDrawTarget,

aDesiredSize.mBoundingMetrics = mBoundingMetrics;

// Apply width/height to math content box.
const PlaceFlags flags;
auto sizes = GetWidthAndHeightForPlaceAdjustment(flags);
nscoord shiftX = ApplyAdjustmentForWidthAndHeight(flags, sizes, aDesiredSize,
mBoundingMetrics);

// Add padding+border around the final layout.
auto borderPadding = GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
Expand Down Expand Up @@ -360,7 +367,7 @@ nsresult nsMathMLmrootFrame::Place(DrawTarget* aDrawTarget,
MirrorIfRTL(aDesiredSize.Width(), baseSize.Width(), dx),
dy, ReflowChildFlags::Default);
} else {
nscoord dx_left = borderPadding.left;
nscoord dx_left = borderPadding.left + shiftX;
if (!isRTL) {
dx_left += bmSqr.width;
}
Expand Down
6 changes: 6 additions & 0 deletions layout/mathml/nsMathMLmspaceFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ nsresult nsMathMLmspaceFrame::Place(DrawTarget* aDrawTarget,
// Also return our bounding metrics
aDesiredSize.mBoundingMetrics = mBoundingMetrics;

// Apply width/height to math content box.
const PlaceFlags flags;
auto sizes = GetWidthAndHeightForPlaceAdjustment(flags);
ApplyAdjustmentForWidthAndHeight(flags, sizes, aDesiredSize,
mBoundingMetrics);

// Add padding+border.
auto borderPadding = GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
Expand Down
15 changes: 15 additions & 0 deletions layout/mathml/nsMathMLmunderoverFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,14 @@ nsresult nsMathMLmunderoverFrame::Place(DrawTarget* aDrawTarget,
aDesiredSize.Width() = mBoundingMetrics.width;
aDesiredSize.mBoundingMetrics = mBoundingMetrics;

// Apply width/height to math content box.
auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags);
auto shiftX = ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
mBoundingMetrics);
dxOver += shiftX;
dxBase += shiftX;
dxUnder += shiftX;

// Add padding+border.
auto borderPadding = GetBorderPaddingForPlace(aFlags);
InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
Expand Down Expand Up @@ -719,3 +727,10 @@ nsresult nsMathMLmunderoverFrame::Place(DrawTarget* aDrawTarget,
}
return NS_OK;
}

bool nsMathMLmunderoverFrame::IsMathContentBoxHorizontallyCentered() const {
bool subsupDisplay =
NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) &&
StyleFont()->mMathStyle == StyleMathStyle::Compact;
return !subsupDisplay;
}
2 changes: 2 additions & 0 deletions layout/mathml/nsMathMLmunderoverFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class nsMathMLmunderoverFrame final : public nsMathMLContainerFrame,
virtual ~nsMathMLmunderoverFrame();

private:
bool IsMathContentBoxHorizontallyCentered() const final;

// Helper to set the "increment script level" flag on the element belonging
// to a child frame given by aChildIndex.
//
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
expected: FAIL

[list display inside display block]
expected: FAIL
expected: [PASS, FAIL]

[flexbox display (math)]
expected: FAIL
expected: [PASS, FAIL]

[block display with column width (math)]
expected: FAIL
Expand Down
Loading

0 comments on commit 3d16d95

Please sign in to comment.