blob: 76080b123d5425a30ac2f432ed2f2d4b638005dc [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_radical_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_block_break_token.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/shaping/stretchy_operator_shaper.h"
namespace blink {
namespace {
bool HasBaseGlyphForRadical(const ComputedStyle& style) {
const SimpleFontData* font_data = style.GetFont().PrimaryFont();
return font_data && font_data->GlyphForCharacter(kSquareRootCharacter);
}
} // namespace
NGMathRadicalLayoutAlgorithm::NGMathRadicalLayoutAlgorithm(
const NGLayoutAlgorithmParams& params)
: NGLayoutAlgorithm(params) {
DCHECK(params.space.IsNewFormattingContext());
}
void NGMathRadicalLayoutAlgorithm::GatherChildren(
NGBlockNode* base,
NGBlockNode* index,
NGBoxFragmentBuilder* container_builder) const {
for (NGLayoutInputNode child = Node().FirstChild(); child;
child = child.NextSibling()) {
NGBlockNode block_child = To<NGBlockNode>(child);
if (child.IsOutOfFlowPositioned()) {
if (container_builder) {
container_builder->AddOutOfFlowChildCandidate(
block_child, BorderScrollbarPadding().StartOffset());
}
continue;
}
if (!*base) {
*base = block_child;
continue;
}
if (!*index) {
*index = block_child;
continue;
}
NOTREACHED();
}
if (Node().HasIndex()) {
DCHECK(*base);
DCHECK(*index);
}
}
scoped_refptr<const NGLayoutResult> NGMathRadicalLayoutAlgorithm::Layout() {
DCHECK(!BreakToken());
DCHECK(IsValidMathMLRadical(Node()));
auto vertical = GetRadicalVerticalParameters(Style(), Node().HasIndex());
scoped_refptr<const NGPhysicalBoxFragment> index_fragment, base_fragment;
LayoutUnit index_inline_size, index_ascent, index_descent, base_ascent,
base_descent;
RadicalHorizontalParameters horizontal;
NGBoxStrut index_margins, base_margins;
NGBlockNode base = nullptr;
NGBlockNode index = nullptr;
GatherChildren(&base, &index, &container_builder_);
if (base) {
// Handle layout of base child. For <msqrt> the base is anonymous and uses
// the row layout algorithm.
NGConstraintSpace constraint_space = CreateConstraintSpaceForMathChild(
Node(), ChildAvailableSize(), ConstraintSpace(), base);
scoped_refptr<const NGLayoutResult> base_layout_result =
base.Layout(constraint_space);
base_fragment =
&To<NGPhysicalBoxFragment>(base_layout_result->PhysicalFragment());
base_margins =
ComputeMarginsFor(constraint_space, base.Style(), ConstraintSpace());
NGBoxFragment fragment(ConstraintSpace().GetWritingDirection(),
*base_fragment);
base_ascent = base_margins.block_start + fragment.BaselineOrSynthesize();
base_descent = fragment.BlockSize() + base_margins.BlockSum() - base_ascent;
}
if (index) {
// Handle layout of index child.
// (https://mathml-refresh.github.io/mathml-core/#root-with-index).
NGConstraintSpace constraint_space = CreateConstraintSpaceForMathChild(
Node(), ChildAvailableSize(), ConstraintSpace(), index);
scoped_refptr<const NGLayoutResult> index_layout_result =
index.Layout(constraint_space);
index_fragment =
&To<NGPhysicalBoxFragment>(index_layout_result->PhysicalFragment());
index_margins =
ComputeMarginsFor(constraint_space, index.Style(), ConstraintSpace());
NGBoxFragment fragment(ConstraintSpace().GetWritingDirection(),
*index_fragment);
index_inline_size = fragment.InlineSize() + index_margins.InlineSum();
index_ascent = index_margins.block_start + fragment.BaselineOrSynthesize();
index_descent =
fragment.BlockSize() + index_margins.BlockSum() - index_ascent;
horizontal = GetRadicalHorizontalParameters(Style());
horizontal.kern_before_degree =
std::max(horizontal.kern_before_degree, LayoutUnit());
horizontal.kern_after_degree =
std::max(horizontal.kern_after_degree, -index_inline_size);
}
StretchyOperatorShaper::Metrics surd_metrics;
if (HasBaseGlyphForRadical(Style())) {
// Stretch the radical operator to cover the base height.
StretchyOperatorShaper shaper(kSquareRootCharacter,
OpenTypeMathStretchData::Vertical);
float target_size = base_ascent + base_descent + vertical.vertical_gap +
vertical.rule_thickness;
scoped_refptr<ShapeResult> shape_result =
shaper.Shape(&Style().GetFont(), target_size, &surd_metrics);
scoped_refptr<ShapeResultView> shape_result_view =
ShapeResultView::Create(shape_result.get());
LayoutUnit operator_inline_offset = index_inline_size +
horizontal.kern_before_degree +
horizontal.kern_after_degree;
container_builder_.SetMathMLPaintInfo(
std::move(shape_result_view), LayoutUnit(surd_metrics.advance),
LayoutUnit(surd_metrics.ascent), LayoutUnit(surd_metrics.descent),
operator_inline_offset, base_margins);
}
// Determine the metrics of the radical operator + the base.
LayoutUnit radical_operator_block_size =
LayoutUnit(surd_metrics.ascent + surd_metrics.descent);
LayoutUnit index_bottom_raise =
LayoutUnit(vertical.degree_bottom_raise_percent) *
radical_operator_block_size;
LayoutUnit radical_ascent = base_ascent + vertical.vertical_gap +
vertical.rule_thickness + vertical.extra_ascender;
LayoutUnit ascent = radical_ascent;
LayoutUnit descent =
std::max(base_descent,
radical_operator_block_size + vertical.extra_ascender - ascent);
if (index) {
ascent = std::max(
ascent, -descent + index_bottom_raise + index_descent + index_ascent);
descent = std::max(
descent, descent - index_bottom_raise + index_descent + index_ascent);
}
ascent += BorderScrollbarPadding().block_start;
if (base) {
LogicalOffset base_offset = {
BorderScrollbarPadding().inline_start +
LayoutUnit(surd_metrics.advance) + index_inline_size +
horizontal.kern_before_degree + horizontal.kern_after_degree +
base_margins.inline_start,
base_margins.block_start - base_ascent + ascent};
container_builder_.AddChild(To<NGPhysicalContainerFragment>(*base_fragment),
base_offset);
base.StoreMargins(ConstraintSpace(), base_margins);
}
if (index) {
LogicalOffset index_offset = {
BorderScrollbarPadding().inline_start + index_margins.inline_start +
horizontal.kern_before_degree,
index_margins.block_start + ascent + descent - index_bottom_raise -
index_descent - index_ascent};
container_builder_.AddChild(
To<NGPhysicalContainerFragment>(*index_fragment), index_offset);
index.StoreMargins(ConstraintSpace(), index_margins);
}
container_builder_.SetBaseline(ascent);
auto total_block_size = ascent + descent + BorderScrollbarPadding().block_end;
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 NGMathRadicalLayoutAlgorithm::ComputeMinMaxSizes(
const MinMaxSizesInput& input) const {
DCHECK(IsValidMathMLRadical(Node()));
NGBlockNode base = nullptr;
NGBlockNode index = nullptr;
GatherChildren(&base, &index);
MinMaxSizes sizes;
bool depends_on_percentage_block_size = false;
if (index) {
auto horizontal = GetRadicalHorizontalParameters(Style());
sizes += horizontal.kern_before_degree.ClampNegativeToZero();
MinMaxSizesResult index_result =
ComputeMinAndMaxContentContribution(Style(), index, input);
NGBoxStrut index_margins = ComputeMinMaxMargins(Style(), index);
index_result.sizes += index_margins.InlineSum();
depends_on_percentage_block_size |=
index_result.depends_on_percentage_block_size;
sizes += index_result.sizes;
// kern_after_degree decreases the inline size, but is capped by the index
// content inline size.
sizes.min_size +=
std::max(-index_result.sizes.min_size, horizontal.kern_after_degree);
sizes.max_size +=
std::max(index_result.sizes.max_size, horizontal.kern_after_degree);
}
if (base) {
if (HasBaseGlyphForRadical(Style())) {
sizes += GetMinMaxSizesForVerticalStretchyOperator(Style(),
kSquareRootCharacter);
}
MinMaxSizesResult base_result =
ComputeMinAndMaxContentContribution(Style(), base, input);
NGBoxStrut base_margins = ComputeMinMaxMargins(Style(), base);
base_result.sizes += base_margins.InlineSum();
depends_on_percentage_block_size |=
base_result.depends_on_percentage_block_size;
sizes += base_result.sizes;
}
sizes += BorderScrollbarPadding().InlineSum();
return MinMaxSizesResult(sizes, depends_on_percentage_block_size);
}
} // namespace blink