Commit 858aea82 authored by Rob Buis's avatar Rob Buis Committed by Commit Bot

[mathml] Implement movablelimits

If the <munder>, <mover> or <munderover> elements have a computed
math-style property equal to compact and their base is an embellished
operator with the movablelimits property, then their layout algorithms
are respectively the same as the ones described for <msub>, <msup> and
<msubsup>. In this CL we delegate to the scripts algorithm which is
changed to be able to handle script types over, under and underover.

This CL does not handle embellished operators as defined [2] but just
the first case, i.e. <mo> elements but not the two other cases, to
simplify this CL.

To handle dynamic changes to the movablelimits attribute on <mo>
UpdateFromElement is implemented on LayoutNGMathMLBlockFlow.

To handle dynamic changes to attributes (like displaystyle) on math
elements UpdateFromElement is implemented on LayoutNGMathMLBlock.

[1] https://mathml-refresh.github.io/mathml-core/#children-of-munder-mover-munderover
[2] https://mathml-refresh.github.io/mathml-core/#embellished-operators

Bug: 6606
Change-Id: Ib4af58ed88b7d41944f4d4676bba812c3b7a3a2e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2383023Reviewed-by: default avatarIan Kilpatrick <ikilpatrick@chromium.org>
Reviewed-by: default avatarFrédéric Wang <fwang@igalia.com>
Commit-Queue: Rob Buis <rbuis@igalia.com>
Cr-Commit-Position: refs/heads/master@{#804331}
parent aaf48338
......@@ -7,6 +7,7 @@
#include "third_party/blink/renderer/core/layout/layout_analyzer.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/mathml/mathml_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_under_over_element.h"
namespace blink {
......@@ -43,4 +44,16 @@ bool LayoutNGMathMLBlock::CanHaveChildren() const {
return LayoutNGMixin<LayoutBlock>::CanHaveChildren();
}
void LayoutNGMathMLBlock::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
LayoutNGMixin<LayoutBlock>::StyleDidChange(diff, old_style);
if (!old_style)
return;
if (IsA<MathMLUnderOverElement>(GetNode()) &&
old_style->MathStyle() != StyleRef().MathStyle()) {
SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
layout_invalidation_reason::kAttributeChanged);
}
}
} // namespace blink
......@@ -21,6 +21,7 @@ class LayoutNGMathMLBlock : public LayoutNGMixin<LayoutBlock> {
bool IsOfType(LayoutObjectType) const final;
bool IsChildAllowed(LayoutObject*, const ComputedStyle&) const final;
bool CanHaveChildren() const final;
void StyleDidChange(StyleDifference, const ComputedStyle*) final;
};
} // namespace blink
......
......@@ -10,6 +10,7 @@
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
#include "third_party/blink/renderer/core/mathml/mathml_fraction_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_operator_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_radical_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_scripts_element.h"
......@@ -198,6 +199,22 @@ MinMaxSizes GetMinMaxSizesForVerticalStretchyOperator(
return sizes;
}
bool IsUnderOverLaidOutAsSubSup(const NGBlockNode& node) {
DCHECK(IsValidMathMLScript(node));
if (HasDisplayStyle(node.Style()))
return false;
if (!node.IsBlock() || !node.GetLayoutBox()->IsMathML())
return false;
auto base = To<NGBlockNode>(FirstChildInFlow(node));
// TODO(crbug.com/1124298)):
// https://mathml-refresh.github.io/mathml-core/#embellished-operators
if (auto* element =
DynamicTo<MathMLOperatorElement>(base.GetLayoutBox()->GetNode())) {
return element->HasBooleanProperty(MathMLOperatorElement::kMovableLimits);
}
return false;
}
namespace {
inline LayoutUnit DefaultFractionLineThickness(const ComputedStyle& style) {
......
......@@ -80,6 +80,7 @@ RadicalVerticalParameters GetRadicalVerticalParameters(const ComputedStyle&,
MinMaxSizes GetMinMaxSizesForVerticalStretchyOperator(const ComputedStyle&,
UChar character);
bool IsUnderOverLaidOutAsSubSup(const NGBlockNode& node);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_MATHML_NG_MATH_LAYOUT_UTILS_H_
......@@ -121,6 +121,7 @@ void NGMathScriptsLayoutAlgorithm::GatherChildren(
}
switch (script_type) {
case MathScriptType::kSub:
case MathScriptType::kUnder:
// These elements must have exactly two children.
// The second child is a postscript and there are no prescripts.
// <msub> base subscript </msub>
......@@ -129,9 +130,11 @@ void NGMathScriptsLayoutAlgorithm::GatherChildren(
sub_sup_pairs->at(0).sub = block_child;
continue;
case MathScriptType::kSuper:
case MathScriptType::kOver:
DCHECK(!sub_sup_pairs->at(0).sup);
sub_sup_pairs->at(0).sup = block_child;
continue;
case MathScriptType::kUnderOver:
case MathScriptType::kSubSup:
// These elements must have exactly three children.
// The second and third children are postscripts and there are no
......@@ -184,6 +187,7 @@ NGMathScriptsLayoutAlgorithm::GetVerticalMetrics(
MathScriptType type = Node().ScriptType();
if (type == MathScriptType::kSub || type == MathScriptType::kSubSup ||
type == MathScriptType::kMultiscripts || type == MathScriptType::kUnder ||
type == MathScriptType::kMultiscripts) {
metrics.sub_shift =
std::max(parameters.subscript_shift_down,
......@@ -191,6 +195,7 @@ NGMathScriptsLayoutAlgorithm::GetVerticalMetrics(
}
LayoutUnit shift_up = parameters.superscript_shift_up;
if (type == MathScriptType::kSuper || type == MathScriptType::kSubSup ||
type == MathScriptType::kMultiscripts || type == MathScriptType::kOver ||
type == MathScriptType::kMultiscripts) {
if (Style().MathSuperscriptShiftStyle() ==
EMathSuperscriptShiftStyle::kInline)
......@@ -201,19 +206,22 @@ NGMathScriptsLayoutAlgorithm::GetVerticalMetrics(
}
switch (type) {
case MathScriptType::kSub: {
case MathScriptType::kSub:
case MathScriptType::kUnder: {
metrics.descent = sub_metrics[0].descent;
metrics.sub_shift =
std::max(metrics.sub_shift,
sub_metrics[0].ascent - parameters.subscript_top_max);
} break;
case MathScriptType::kSuper: {
case MathScriptType::kSuper:
case MathScriptType::kOver: {
metrics.ascent = sup_metrics[0].ascent;
metrics.sup_shift =
std::max(metrics.sup_shift,
parameters.superscript_bottom_min + sup_metrics[0].descent);
} break;
case MathScriptType::kMultiscripts:
case MathScriptType::kUnderOver:
case MathScriptType::kSubSup: {
for (wtf_size_t idx = 0; idx < sub_metrics.size(); ++idx) {
metrics.ascent = std::max(metrics.ascent, sup_metrics[idx].ascent);
......@@ -255,12 +263,6 @@ NGMathScriptsLayoutAlgorithm::GetVerticalMetrics(
metrics.sup_shift = std::max(metrics.sup_shift, sup_script_shift);
}
} break;
case MathScriptType::kOver:
case MathScriptType::kUnder:
case MathScriptType::kUnderOver:
// TODO(rbuis): implement movablelimits.
NOTREACHED();
break;
}
return metrics;
......@@ -431,6 +433,8 @@ MinMaxSizesResult NGMathScriptsLayoutAlgorithm::ComputeMinMaxSizes(
LayoutUnit space = GetSpaceAfterScript(Style());
switch (Node().ScriptType()) {
case MathScriptType::kSub:
case MathScriptType::kUnder:
case MathScriptType::kOver:
case MathScriptType::kSuper: {
// TODO(fwang): Take italic correction into account.
NGBlockNode sub = sub_sup_pairs[0].sub;
......@@ -448,6 +452,7 @@ MinMaxSizesResult NGMathScriptsLayoutAlgorithm::ComputeMinMaxSizes(
break;
}
case MathScriptType::kSubSup:
case MathScriptType::kUnderOver:
case MathScriptType::kMultiscripts: {
// TODO(fwang): Take italic correction into account.
MinMaxSizes sub_sup_pair_size;
......@@ -478,12 +483,6 @@ MinMaxSizesResult NGMathScriptsLayoutAlgorithm::ComputeMinMaxSizes(
} while (++index < sub_sup_pairs.size());
break;
}
case MathScriptType::kUnder:
case MathScriptType::kOver:
case MathScriptType::kUnderOver:
// TODO(rbuis): implement movablelimits.
NOTREACHED();
break;
}
sizes += BorderScrollbarPadding().InlineSum();
......
......@@ -136,8 +136,8 @@ NOINLINE void DetermineMathMLAlgorithmAndRun(
return;
} else if (IsA<MathMLScriptsElement>(element) &&
IsValidMathMLScript(params.node)) {
// TODO(rbuis): take into account movablelimits.
if (IsA<MathMLUnderOverElement>(element)) {
if (IsA<MathMLUnderOverElement>(element) &&
!IsUnderOverLaidOutAsSubSup(params.node)) {
CreateAlgorithmAndRun<NGMathUnderOverLayoutAlgorithm>(params, callback);
} else {
CreateAlgorithmAndRun<NGMathScriptsLayoutAlgorithm>(params, callback);
......
......@@ -128,8 +128,11 @@ void MathMLOperatorElement::ParseAttribute(
SetOperatorPropertyDirtyFlagIfNeeded(
param, MathMLOperatorElement::kMovableLimits, needs_layout);
}
if (needs_layout && GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
if (needs_layout && GetLayoutObject() && GetLayoutObject()->IsMathML()) {
GetLayoutObject()
->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
layout_invalidation_reason::kAttributeChanged);
}
MathMLElement::ParseAttribute(param);
}
......
......@@ -1249,10 +1249,7 @@ crbug.com/6606 external/wpt/mathml/presentation-markup/operators/mo-axis-height-
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/mo-form-dynamic.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/mo-form-minus-plus.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/mo-form.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/mo-movablelimits.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/mo-paint-lspace-rspace.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/operator-dictionary-001.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/operator-dictionary-002.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/scripts/underover-parameters-4.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/spaces/space-like-001.html [ Failure ]
crbug.com/6606 external/wpt/mathml/presentation-markup/spaces/space-like-002.html [ Failure ]
......@@ -1262,9 +1259,6 @@ crbug.com/6606 external/wpt/mathml/presentation-markup/tables/table-002.html [ F
crbug.com/6606 external/wpt/mathml/presentation-markup/tables/table-axis-height.html [ Failure ]
crbug.com/6606 external/wpt/mathml/relations/css-styling/attribute-mapping-002.html [ Failure ]
crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-011.html [ Failure ]
crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-012.html [ Failure ]
crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-013.html [ Failure ]
crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-015.html [ Failure ]
crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-1.html [ Failure ]
crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-2.html [ Failure ]
crbug.com/6606 external/wpt/mathml/relations/css-styling/ignored-properties-001.html [ Failure Timeout ]
......@@ -1295,7 +1289,6 @@ crbug.com/6606 external/wpt/mathml/relations/html5-tree/integration-point-4.html
# Need to handle OOF for these.
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/embellished-operator-001.html [ Skip ]
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/embellished-operator-002.html [ Skip ]
crbug.com/6606 external/wpt/mathml/presentation-markup/operators/mo-movablelimits-from-in-flow.html [ Skip ]
crbug.com/6606 external/wpt/mathml/presentation-markup/scripts/cramped-001.html [ Skip ]
crbug.com/6606 external/wpt/mathml/presentation-markup/spaces/space-like-004.html [ Skip ]
......
This is a testharness.js-based test.
FAIL Operator dictionary chunk 1 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 2 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 3 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 4 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 5 - movablelimits assert_true: expected true got false
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL Operator dictionary chunk 1 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 2 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 3 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 4 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 5 - movablelimits assert_true: expected true got false
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL Operator dictionary chunk 1 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 2 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 3 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 4 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 5 - movablelimits assert_true: expected true got false
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL Operator dictionary chunk 1 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 2 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 3 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 4 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 5 - movablelimits assert_true: expected true got false
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL Operator dictionary chunk 1 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 2 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 3 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 4 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 5 - movablelimits assert_true: expected true got false
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL Operator dictionary chunk 1 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 2 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 3 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 4 - movablelimits assert_true: expected true got false
FAIL Operator dictionary chunk 5 - movablelimits assert_true: expected true got false
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL movablelimits for munder element (displaystyle=false) assert_true: expected true got false
FAIL movablelimits for munder element (displaystyle=true) assert_true: expected true got false
FAIL movablelimits for mover element (displaystyle=false) assert_true: expected true got false
FAIL movablelimits for mover element (displaystyle=true) assert_true: expected true got false
FAIL movablelimits for munderover element (displaystyle=false) assert_true: expected true got false
FAIL movablelimits for munderover element (displaystyle=true) assert_true: expected true got false
Harness: the test ran to completion.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment