blob: 92a8543b03c204d52b284f6e9f1cb591dc2eb98c [file] [log] [blame]
// Copyright 2017 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/list/ng_unpositioned_list_marker.h"
#include "third_party/blink/renderer/core/layout/layout_list_marker.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
namespace blink {
NGUnpositionedListMarker::NGUnpositionedListMarker(LayoutNGListMarker* marker)
: marker_layout_object_(marker) {}
NGUnpositionedListMarker::NGUnpositionedListMarker(const NGBlockNode& node)
: NGUnpositionedListMarker(ToLayoutNGListMarker(node.GetLayoutBox())) {}
// Returns true if this is an image marker.
bool NGUnpositionedListMarker::IsImage() const {
DCHECK(marker_layout_object_);
return marker_layout_object_->IsContentImage();
}
// Compute the inline offset of the marker, relative to the list item.
// The marker is relative to the border box of the list item and has nothing
// to do with the content offset.
// Open issue at https://github.com/w3c/csswg-drafts/issues/2361
LayoutUnit NGUnpositionedListMarker::InlineOffset(
const LayoutUnit marker_inline_size) const {
DCHECK(marker_layout_object_);
auto margins = LayoutListMarker::InlineMarginsForOutside(
marker_layout_object_->StyleRef(), IsImage(), marker_inline_size);
return margins.first;
}
scoped_refptr<NGLayoutResult> NGUnpositionedListMarker::Layout(
const NGConstraintSpace& space,
FontBaseline baseline_type) const {
DCHECK(marker_layout_object_);
NGBlockNode marker_node(marker_layout_object_);
scoped_refptr<NGLayoutResult> marker_layout_result =
marker_node.LayoutAtomicInline(space, baseline_type,
space.UseFirstLineStyle());
DCHECK(marker_layout_result && marker_layout_result->PhysicalFragment());
return marker_layout_result;
}
bool NGUnpositionedListMarker::AddToBox(
const NGConstraintSpace& space,
FontBaseline baseline_type,
const NGPhysicalFragment& content,
NGLogicalOffset* content_offset,
NGFragmentBuilder* container_builder,
const NGBoxStrut& border_scrollbar_padding) const {
// Baselines from two different writing-mode cannot be aligned.
if (UNLIKELY(space.GetWritingMode() != content.Style().GetWritingMode()))
return false;
// Compute the baseline of the child content.
NGLineHeightMetrics content_metrics;
if (content.IsLineBox()) {
const NGPhysicalLineBoxFragment& line_box =
ToNGPhysicalLineBoxFragment(content);
// If this child is an empty line-box, the list marker should be aligned
// with the next non-empty line box produced. (This can occur with floats
// producing empty line-boxes).
if (line_box.Children().IsEmpty() && !line_box.BreakToken()->IsFinished())
return false;
content_metrics = line_box.Metrics();
} else {
NGBoxFragment content_fragment(space.GetWritingMode(), space.Direction(),
ToNGPhysicalBoxFragment(content));
content_metrics = content_fragment.BaselineMetricsWithoutSynthesize(
{NGBaselineAlgorithmType::kFirstLine, baseline_type});
// If this child content does not have any line boxes, the list marker
// should be aligned to the first line box of next child.
// https://github.com/w3c/csswg-drafts/issues/2417
if (content_metrics.IsEmpty())
return false;
}
// Layout the list marker.
scoped_refptr<NGLayoutResult> marker_layout_result =
Layout(space, baseline_type);
DCHECK(marker_layout_result && marker_layout_result->PhysicalFragment());
const NGPhysicalBoxFragment& marker_physical_fragment =
ToNGPhysicalBoxFragment(*marker_layout_result->PhysicalFragment());
// Compute the inline offset of the marker.
NGBoxFragment marker_fragment(space.GetWritingMode(), space.Direction(),
marker_physical_fragment);
NGLogicalOffset marker_offset(
InlineOffset(marker_fragment.Size().inline_size),
content_offset->block_offset);
// Adjust the block offset to align baselines of the marker and the content.
NGLineHeightMetrics marker_metrics = marker_fragment.BaselineMetrics(
{NGBaselineAlgorithmType::kAtomicInline, baseline_type}, space);
LayoutUnit baseline_adjust = content_metrics.ascent - marker_metrics.ascent;
if (baseline_adjust >= 0) {
marker_offset.block_offset += baseline_adjust;
} else {
// If the ascent of the marker is taller than the ascent of the content,
// push the content down.
content_offset->block_offset -= baseline_adjust;
}
marker_offset.inline_offset += ComputeIntrudedFloatOffset(
space, container_builder, border_scrollbar_padding,
marker_offset.block_offset);
DCHECK(container_builder);
container_builder->AddChild(std::move(marker_layout_result), marker_offset);
return true;
}
LayoutUnit NGUnpositionedListMarker::AddToBoxWithoutLineBoxes(
const NGConstraintSpace& space,
FontBaseline baseline_type,
NGFragmentBuilder* container_builder) const {
// Layout the list marker.
scoped_refptr<NGLayoutResult> marker_layout_result =
Layout(space, baseline_type);
DCHECK(marker_layout_result && marker_layout_result->PhysicalFragment());
const NGPhysicalBoxFragment& marker_physical_fragment =
ToNGPhysicalBoxFragment(*marker_layout_result->PhysicalFragment());
// When there are no line boxes, marker is top-aligned to the list item.
// https://github.com/w3c/csswg-drafts/issues/2417
NGLogicalSize marker_size =
marker_physical_fragment.Size().ConvertToLogical(space.GetWritingMode());
NGLogicalOffset offset(InlineOffset(marker_size.inline_size), LayoutUnit());
DCHECK(container_builder);
container_builder->AddChild(std::move(marker_layout_result), offset);
return marker_size.block_size;
}
// Find the opportunity for marker, and compare it to ListItem, then compute the
// diff as intruded offset.
LayoutUnit NGUnpositionedListMarker::ComputeIntrudedFloatOffset(
const NGConstraintSpace& space,
const NGFragmentBuilder* container_builder,
const NGBoxStrut& border_scrollbar_padding,
LayoutUnit marker_block_offset) const {
DCHECK(container_builder);
// Because opportunity.rect is in the content area of LI, so origin_offset
// should plus border_scrollbar_padding.inline_start, and available_size
// should minus border_scrollbar_padding.
NGBfcOffset origin_offset = {
container_builder->BfcLineOffset() +
border_scrollbar_padding.inline_start,
container_builder->BfcBlockOffset().value() + marker_block_offset};
LayoutUnit available_size = container_builder->InlineSize() -
border_scrollbar_padding.inline_start -
border_scrollbar_padding.inline_end;
NGLayoutOpportunity opportunity =
space.ExclusionSpace().FindLayoutOpportunity(
origin_offset, available_size, NGLogicalSize());
DCHECK(marker_layout_object_);
const TextDirection direction = marker_layout_object_->StyleRef().Direction();
if (direction == TextDirection::kLtr) {
// If Ltr, compare the left side.
if (opportunity.rect.LineStartOffset() > origin_offset.line_offset)
return opportunity.rect.LineStartOffset() - origin_offset.line_offset;
} else if (opportunity.rect.LineEndOffset() <
origin_offset.line_offset + available_size) {
// If Rtl, Compare the right side.
return origin_offset.line_offset + available_size -
opportunity.rect.LineEndOffset();
}
return LayoutUnit(0);
}
} // namespace blink