blob: 27a17a9503ba5a4a3110d137ff52074dd520b5ba [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h"
namespace blink {
namespace {
// Describes the amount to shift the numerator/denominator of the fraction when
// a fraction bar is present. Data is populated from the OpenType MATH table.
// If the OpenType MATH table is not present fallback values are used.
// https://mathml-refresh.github.io/mathml-core/#fraction-with-nonzero-line-thickness
struct FractionParameters {
LayoutUnit numerator_gap_min;
LayoutUnit denominator_gap_min;
LayoutUnit numerator_min_shift_up;
LayoutUnit denominator_min_shift_down;
};
FractionParameters GetFractionParameters(const ComputedStyle& style) {
FractionParameters parameters;
bool has_display_style = HasDisplayStyle(style);
// We try and read constants to draw the fraction from the OpenType MATH and
// use fallback values suggested in the MathML Core specification otherwise.
parameters.numerator_gap_min = LayoutUnit(
MathConstant(
style,
has_display_style
? OpenTypeMathSupport::MathConstants::
kFractionNumDisplayStyleGapMin
: OpenTypeMathSupport::MathConstants::kFractionNumeratorGapMin)
.value_or((has_display_style ? 3 : 1) *
RuleThicknessFallback(style)));
parameters.denominator_gap_min = LayoutUnit(
MathConstant(
style,
has_display_style
? OpenTypeMathSupport::MathConstants::
kFractionDenomDisplayStyleGapMin
: OpenTypeMathSupport::MathConstants::kFractionDenominatorGapMin)
.value_or(parameters.numerator_gap_min));
parameters.numerator_min_shift_up = LayoutUnit(
MathConstant(
style,
has_display_style
? OpenTypeMathSupport::MathConstants::
kFractionNumeratorDisplayStyleShiftUp
: OpenTypeMathSupport::MathConstants::kFractionNumeratorShiftUp)
.value_or(0));
parameters.denominator_min_shift_down = LayoutUnit(
MathConstant(style, has_display_style
? OpenTypeMathSupport::MathConstants::
kFractionDenominatorDisplayStyleShiftDown
: OpenTypeMathSupport::MathConstants::
kFractionDenominatorShiftDown)
.value_or(0));
return parameters;
}
// Describes the amount to shift the numerator/denominator of the fraction when
// a fraction bar is not present. Data is populated from the OpenType MATH
// table. If the OpenType MATH table is not present fallback values are used.
// https://mathml-refresh.github.io/mathml-core/#fraction-with-zero-line-thickness
struct FractionStackParameters {
LayoutUnit gap_min;
LayoutUnit top_shift_up;
LayoutUnit bottom_shift_down;
};
FractionStackParameters GetFractionStackParameters(const ComputedStyle& style) {
FractionStackParameters parameters;
bool has_display_style = HasDisplayStyle(style);
// We try and read constants to draw the stack from the OpenType MATH and use
// fallback values otherwise.
// We use the fallback values suggested in the MATH table specification.
parameters.gap_min = LayoutUnit(
MathConstant(
style,
has_display_style
? OpenTypeMathSupport::MathConstants::kStackDisplayStyleGapMin
: OpenTypeMathSupport::MathConstants::kStackGapMin)
.value_or((has_display_style ? 7 : 3) *
RuleThicknessFallback(style)));
// The MATH table specification does not suggest any values for shifts, so
// we leave them at zero.
parameters.top_shift_up = LayoutUnit(
MathConstant(
style,
has_display_style
? OpenTypeMathSupport::MathConstants::kStackTopDisplayStyleShiftUp
: OpenTypeMathSupport::MathConstants::kStackTopShiftUp)
.value_or(0));
parameters.bottom_shift_down = LayoutUnit(
MathConstant(
style,
has_display_style
? OpenTypeMathSupport::MathConstants::
kStackBottomDisplayStyleShiftDown
: OpenTypeMathSupport::MathConstants::kStackBottomShiftDown)
.value_or(0));
return parameters;
}
} // namespace
NGMathFractionLayoutAlgorithm::NGMathFractionLayoutAlgorithm(
const NGLayoutAlgorithmParams& params)
: NGLayoutAlgorithm(params) {
DCHECK(params.space.IsNewFormattingContext());
container_builder_.SetIsMathMLFraction();
}
void NGMathFractionLayoutAlgorithm::GatherChildren(NGBlockNode* numerator,
NGBlockNode* denominator) {
for (NGLayoutInputNode child = Node().FirstChild(); child;
child = child.NextSibling()) {
NGBlockNode block_child = To<NGBlockNode>(child);
if (child.IsOutOfFlowPositioned()) {
container_builder_.AddOutOfFlowChildCandidate(
block_child, BorderScrollbarPadding().StartOffset());
continue;
}
if (!*numerator) {
*numerator = block_child;
continue;
}
if (!*denominator) {
*denominator = block_child;
continue;
}
NOTREACHED();
}
DCHECK(*numerator);
DCHECK(*denominator);
}
scoped_refptr<const NGLayoutResult> NGMathFractionLayoutAlgorithm::Layout() {
DCHECK(!BreakToken());
NGBlockNode numerator = nullptr;
NGBlockNode denominator = nullptr;
GatherChildren(&numerator, &denominator);
auto numerator_space = CreateConstraintSpaceForMathChild(
Node(), ChildAvailableSize(), ConstraintSpace(), numerator);
scoped_refptr<const NGLayoutResult> numerator_layout_result =
numerator.Layout(numerator_space);
auto numerator_margins =
ComputeMarginsFor(numerator_space, numerator.Style(), ConstraintSpace());
auto denominator_space = CreateConstraintSpaceForMathChild(
Node(), ChildAvailableSize(), ConstraintSpace(), denominator);
scoped_refptr<const NGLayoutResult> denominator_layout_result =
denominator.Layout(denominator_space);
auto denominator_margins = ComputeMarginsFor(
denominator_space, denominator.Style(), ConstraintSpace());
NGBoxFragment numerator_fragment(
ConstraintSpace().GetWritingDirection(),
To<NGPhysicalBoxFragment>(numerator_layout_result->PhysicalFragment()));
NGBoxFragment denominator_fragment(
ConstraintSpace().GetWritingDirection(),
To<NGPhysicalBoxFragment>(denominator_layout_result->PhysicalFragment()));
LayoutUnit numerator_ascent =
numerator_margins.block_start + numerator_fragment.BaselineOrSynthesize();
LayoutUnit numerator_descent = numerator_fragment.BlockSize() +
numerator_margins.BlockSum() -
numerator_ascent;
LayoutUnit denominator_ascent = denominator_margins.block_start +
denominator_fragment.BaselineOrSynthesize();
LayoutUnit denominator_descent = denominator_fragment.BlockSize() +
denominator_margins.BlockSum() -
denominator_ascent;
LayoutUnit numerator_shift, denominator_shift;
LayoutUnit thickness = FractionLineThickness(Style());
if (thickness) {
LayoutUnit axis_height = MathAxisHeight(Style());
FractionParameters parameters = GetFractionParameters(Style());
numerator_shift =
std::max(parameters.numerator_min_shift_up,
axis_height + thickness / 2 + parameters.numerator_gap_min +
numerator_descent);
denominator_shift =
std::max(parameters.denominator_min_shift_down,
thickness / 2 + parameters.denominator_gap_min +
denominator_ascent - axis_height);
} else {
FractionStackParameters parameters = GetFractionStackParameters(Style());
numerator_shift = parameters.top_shift_up;
denominator_shift = parameters.bottom_shift_down;
LayoutUnit gap = denominator_shift - denominator_ascent + numerator_shift -
numerator_descent;
if (gap < parameters.gap_min) {
LayoutUnit diff = parameters.gap_min - gap;
LayoutUnit delta = diff / 2;
numerator_shift += delta;
denominator_shift += diff - delta;
}
}
LayoutUnit fraction_ascent =
std::max(numerator_shift + numerator_ascent,
-denominator_shift + denominator_ascent);
LayoutUnit fraction_descent =
std::max(-numerator_shift + numerator_descent,
denominator_shift + denominator_descent);
fraction_ascent += BorderScrollbarPadding().block_start;
fraction_descent += BorderScrollbarPadding().block_end;
LayoutUnit total_block_size = fraction_ascent + fraction_descent;
container_builder_.SetBaseline(fraction_ascent);
LogicalOffset numerator_offset;
LogicalOffset denominator_offset;
numerator_offset.inline_offset =
BorderScrollbarPadding().inline_start + numerator_margins.inline_start +
(ChildAvailableSize().inline_size -
(numerator_fragment.InlineSize() + numerator_margins.InlineSum())) /
2;
denominator_offset.inline_offset =
BorderScrollbarPadding().inline_start + denominator_margins.inline_start +
(ChildAvailableSize().inline_size -
(denominator_fragment.InlineSize() + denominator_margins.InlineSum())) /
2;
numerator_offset.block_offset = numerator_margins.block_start +
fraction_ascent - numerator_shift -
numerator_ascent;
denominator_offset.block_offset = denominator_margins.block_start +
fraction_ascent + denominator_shift -
denominator_ascent;
container_builder_.AddChild(numerator_layout_result->PhysicalFragment(),
numerator_offset);
container_builder_.AddChild(denominator_layout_result->PhysicalFragment(),
denominator_offset);
numerator.StoreMargins(ConstraintSpace(), numerator_margins);
denominator.StoreMargins(ConstraintSpace(), denominator_margins);
LayoutUnit block_size = ComputeBlockSizeForFragment(
ConstraintSpace(), Style(), BorderPadding(), total_block_size,
container_builder_.InitialBorderBoxSize().inline_size,
Node().ShouldBeConsideredAsReplaced());
container_builder_.SetIntrinsicBlockSize(total_block_size);
container_builder_.SetFragmentsTotalBlockSize(block_size);
NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), &container_builder_).Run();
return container_builder_.ToBoxFragment();
}
MinMaxSizesResult NGMathFractionLayoutAlgorithm::ComputeMinMaxSizes(
const MinMaxSizesInput& child_input) const {
if (auto result = CalculateMinMaxSizesIgnoringChildren(
Node(), BorderScrollbarPadding()))
return *result;
MinMaxSizes sizes;
bool depends_on_percentage_block_size = false;
for (NGLayoutInputNode child = Node().FirstChild(); child;
child = child.NextSibling()) {
if (child.IsOutOfFlowPositioned())
continue;
auto child_result = ComputeMinAndMaxContentContribution(
Style(), To<NGBlockNode>(child), child_input);
NGBoxStrut margins = ComputeMinMaxMargins(Style(), child);
child_result.sizes += margins.InlineSum();
sizes.Encompass(child_result.sizes);
depends_on_percentage_block_size |=
child_result.depends_on_percentage_block_size;
}
sizes += BorderScrollbarPadding().InlineSum();
return MinMaxSizesResult(sizes, depends_on_percentage_block_size);
}
} // namespace blink