blob: d6bd5480120910a84dd82b86377c5e1e27f94df6 [file] [log] [blame]
// Copyright 2019 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/ng_ink_overflow.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_rect.h"
#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h"
#include "third_party/blink/renderer/core/layout/line/line_orientation_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.h"
#include "third_party/blink/renderer/core/paint/text_decoration_info.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/wtf/size_assertions.h"
namespace blink {
namespace {
struct SameSizeAsNGInkOverflow {
void* pointer;
#if DCHECK_IS_ON()
NGInkOverflow::Type type;
#endif
};
ASSERT_SIZE(NGInkOverflow, SameSizeAsNGInkOverflow);
inline bool HasOverflow(const PhysicalRect& rect, const PhysicalSize& size) {
if (rect.IsEmpty())
return false;
return rect.X() < 0 || rect.Y() < 0 || rect.Right() > size.width ||
rect.Bottom() > size.height;
}
} // namespace
#if DCHECK_IS_ON()
unsigned NGInkOverflow::read_unset_as_none_ = 0;
NGInkOverflow::~NGInkOverflow() {
// Because |Type| is kept outside of the instance, callers must call |Reset|
// before destructing.
DCHECK(type_ == kNotSet || type_ == kNone) << type_;
}
#endif
NGInkOverflow::NGInkOverflow(Type source_type, const NGInkOverflow& source) {
source.CheckType(source_type);
new (this) NGInkOverflow();
switch (source_type) {
case kNotSet:
case kNone:
break;
case kSmallSelf:
case kSmallContents:
static_assert(sizeof(outsets_) == sizeof(single_),
"outsets should be the size of a pointer");
single_ = source.single_;
#if DCHECK_IS_ON()
for (wtf_size_t i = 0; i < base::size(outsets_); ++i)
DCHECK_EQ(outsets_[i], source.outsets_[i]);
#endif
break;
case kSelf:
case kContents:
single_ = new NGSingleInkOverflow(*source.single_);
break;
case kSelfAndContents:
container_ = new NGContainerInkOverflow(*source.container_);
break;
}
SetType(source_type);
}
NGInkOverflow::NGInkOverflow(Type source_type, NGInkOverflow&& source) {
source.CheckType(source_type);
new (this) NGInkOverflow();
switch (source_type) {
case kNotSet:
case kNone:
break;
case kSmallSelf:
case kSmallContents:
static_assert(sizeof(outsets_) == sizeof(single_),
"outsets should be the size of a pointer");
single_ = source.single_;
#if DCHECK_IS_ON()
for (wtf_size_t i = 0; i < base::size(outsets_); ++i)
DCHECK_EQ(outsets_[i], source.outsets_[i]);
#endif
break;
case kSelf:
case kContents:
single_ = source.single_;
source.single_ = nullptr;
break;
case kSelfAndContents:
container_ = source.container_;
source.container_ = nullptr;
break;
}
SetType(source_type);
}
NGInkOverflow::Type NGInkOverflow::Reset(Type type, Type new_type) {
CheckType(type);
DCHECK(new_type == kNotSet || new_type == kNone);
switch (type) {
case kNotSet:
case kNone:
case kSmallSelf:
case kSmallContents:
break;
case kSelf:
case kContents:
delete single_;
break;
case kSelfAndContents:
delete container_;
break;
}
return SetType(new_type);
}
PhysicalRect NGInkOverflow::FromOutsets(const PhysicalSize& size) const {
const LayoutUnit left_outset(LayoutUnit::FromRawValue(outsets_[0]));
const LayoutUnit top_outset(LayoutUnit::FromRawValue(outsets_[1]));
return {-left_outset, -top_outset,
left_outset + size.width + LayoutUnit::FromRawValue(outsets_[2]),
top_outset + size.height + LayoutUnit::FromRawValue(outsets_[3])};
}
PhysicalRect NGInkOverflow::Self(Type type, const PhysicalSize& size) const {
CheckType(type);
DCHECK_NE(type, kNotSet);
switch (type) {
case kNotSet:
case kNone:
case kSmallContents:
case kContents:
return {PhysicalOffset(), size};
case kSmallSelf:
return FromOutsets(size);
case kSelf:
case kSelfAndContents:
DCHECK(single_);
return single_->ink_overflow;
}
NOTREACHED();
return {PhysicalOffset(), size};
}
PhysicalRect NGInkOverflow::SelfAndContents(Type type,
const PhysicalSize& size) const {
CheckType(type);
switch (type) {
case kNotSet:
#if DCHECK_IS_ON()
if (!read_unset_as_none_)
NOTREACHED();
FALLTHROUGH;
#endif
case kNone:
return {PhysicalOffset(), size};
case kSmallSelf:
case kSmallContents:
return FromOutsets(size);
case kSelf:
case kContents:
DCHECK(single_);
return single_->ink_overflow;
case kSelfAndContents:
DCHECK(container_);
return container_->SelfAndContentsInkOverflow();
}
NOTREACHED();
return {PhysicalOffset(), size};
}
// Store |ink_overflow| as |SmallRawValue| if possible and returns |true|.
// Returns |false| if |ink_overflow| is too large for |SmallRawValue|.
bool NGInkOverflow::TrySetOutsets(Type type,
LayoutUnit left_outset,
LayoutUnit top_outset,
LayoutUnit right_outset,
LayoutUnit bottom_outset) {
CheckType(type);
const LayoutUnit max_small_value(
LayoutUnit::FromRawValue(std::numeric_limits<SmallRawValue>::max()));
if (left_outset > max_small_value)
return false;
if (top_outset > max_small_value)
return false;
if (right_outset > max_small_value)
return false;
if (bottom_outset > max_small_value)
return false;
Reset(type);
outsets_[0] = left_outset.RawValue();
outsets_[1] = top_outset.RawValue();
outsets_[2] = right_outset.RawValue();
outsets_[3] = bottom_outset.RawValue();
return true;
}
NGInkOverflow::Type NGInkOverflow::SetSingle(Type type,
const PhysicalRect& ink_overflow,
const PhysicalSize& size,
Type new_type,
Type new_small_type) {
CheckType(type);
DCHECK(HasOverflow(ink_overflow, size));
const LayoutUnit left_outset = (-ink_overflow.X()).ClampNegativeToZero();
const LayoutUnit top_outset = (-ink_overflow.Y()).ClampNegativeToZero();
const LayoutUnit right_outset =
(ink_overflow.Right() - size.width).ClampNegativeToZero();
const LayoutUnit bottom_outset =
(ink_overflow.Bottom() - size.height).ClampNegativeToZero();
if (TrySetOutsets(type, left_outset, top_outset, right_outset, bottom_outset))
return SetType(new_small_type);
const PhysicalRect adjusted_ink_overflow(
-left_outset, -top_outset, left_outset + size.width + right_outset,
top_outset + size.height + bottom_outset);
switch (type) {
case kSelfAndContents:
Reset(type);
FALLTHROUGH;
case kNotSet:
case kNone:
case kSmallSelf:
case kSmallContents:
single_ = new NGSingleInkOverflow(adjusted_ink_overflow);
return SetType(new_type);
case kSelf:
case kContents:
DCHECK(single_);
single_->ink_overflow = adjusted_ink_overflow;
return SetType(new_type);
}
NOTREACHED();
}
NGInkOverflow::Type NGInkOverflow::SetSelf(Type type,
const PhysicalRect& ink_overflow,
const PhysicalSize& size) {
CheckType(type);
if (!HasOverflow(ink_overflow, size))
return Reset(type);
return SetSingle(type, ink_overflow, size, kSelf, kSmallSelf);
}
NGInkOverflow::Type NGInkOverflow::SetContents(Type type,
const PhysicalRect& ink_overflow,
const PhysicalSize& size) {
CheckType(type);
if (!HasOverflow(ink_overflow, size))
return Reset(type);
return SetSingle(type, ink_overflow, size, kContents, kSmallContents);
}
NGInkOverflow::Type NGInkOverflow::Set(Type type,
const PhysicalRect& self,
const PhysicalRect& contents,
const PhysicalSize& size) {
CheckType(type);
if (!HasOverflow(self, size)) {
if (!HasOverflow(contents, size))
return Reset(type);
return SetSingle(type, contents, size, kContents, kSmallContents);
}
if (!HasOverflow(contents, size))
return SetSingle(type, self, size, kSelf, kSmallSelf);
switch (type) {
case kSelf:
case kContents:
Reset(type);
FALLTHROUGH;
case kNotSet:
case kNone:
case kSmallSelf:
case kSmallContents:
container_ = new NGContainerInkOverflow(self, contents);
return SetType(kSelfAndContents);
case kSelfAndContents:
DCHECK(container_);
container_->ink_overflow = self;
container_->contents_ink_overflow = contents;
return kSelfAndContents;
}
NOTREACHED();
}
NGInkOverflow::Type NGInkOverflow::SetTextInkOverflow(
Type type,
const NGTextFragmentPaintInfo& text_info,
const ComputedStyle& style,
const PhysicalSize& size,
PhysicalRect* ink_overflow_out) {
CheckType(type);
DCHECK_EQ(type, kNotSet);
base::Optional<PhysicalRect> ink_overflow =
ComputeTextInkOverflow(text_info, style, size);
if (!ink_overflow) {
*ink_overflow_out = {PhysicalOffset(), size};
return Reset(type);
}
*ink_overflow_out = *ink_overflow;
return SetSelf(type, *ink_overflow, size);
}
// static
base::Optional<PhysicalRect> NGInkOverflow::ComputeTextInkOverflow(
const NGTextFragmentPaintInfo& text_info,
const ComputedStyle& style,
const PhysicalSize& size) {
// Glyph bounds is in logical coordinate, origin at the alphabetic baseline.
const Font& font = style.GetFont();
const FloatRect text_ink_bounds = font.TextInkBounds(text_info);
LayoutRect ink_overflow = EnclosingLayoutRect(text_ink_bounds);
// Make the origin at the logical top of this fragment.
if (const SimpleFontData* font_data = font.PrimaryFont()) {
ink_overflow.SetY(
ink_overflow.Y() +
font_data->GetFontMetrics().FixedAscent(kAlphabeticBaseline));
}
if (float stroke_width = style.TextStrokeWidth()) {
ink_overflow.Inflate(LayoutUnit::FromFloatCeil(stroke_width / 2.0f));
}
// Following effects, such as shadows, operate on the text decorations,
// so compute text decoration overflow first.
if (!style.AppliedTextDecorations().IsEmpty() && font.PrimaryFont()) {
LayoutRect decoration_rect =
ComputeTextDecorationOverflow(text_info, style, ink_overflow);
ink_overflow.Unite(decoration_rect);
}
const WritingMode writing_mode = style.GetWritingMode();
if (style.GetTextEmphasisMark() != TextEmphasisMark::kNone) {
LayoutUnit emphasis_mark_height =
LayoutUnit(font.EmphasisMarkHeight(style.TextEmphasisMarkString()));
DCHECK_GE(emphasis_mark_height, LayoutUnit());
if (style.GetTextEmphasisLineLogicalSide() == LineLogicalSide::kOver) {
ink_overflow.ShiftYEdgeTo(
std::min(ink_overflow.Y(), -emphasis_mark_height));
} else {
LayoutUnit logical_height =
IsHorizontalWritingMode(writing_mode) ? size.height : size.width;
ink_overflow.ShiftMaxYEdgeTo(
std::max(ink_overflow.MaxY(), logical_height + emphasis_mark_height));
}
}
if (ShadowList* text_shadow = style.TextShadow()) {
LayoutRectOutsets text_shadow_logical_outsets =
LineOrientationLayoutRectOutsets(
LayoutRectOutsets(text_shadow->RectOutsetsIncludingOriginal()),
writing_mode);
text_shadow_logical_outsets.ClampNegativeToZero();
ink_overflow.Expand(text_shadow_logical_outsets);
}
PhysicalRect local_ink_overflow =
WritingModeConverter({writing_mode, TextDirection::kLtr}, size)
.ToPhysical(LogicalRect(ink_overflow));
// Uniting the frame rect ensures that non-ink spaces such side bearings, or
// even space characters, are included in the visual rect for decorations.
if (!HasOverflow(local_ink_overflow, size))
return base::nullopt;
local_ink_overflow.Unite({{}, size});
local_ink_overflow.ExpandEdgesToPixelBoundaries();
return local_ink_overflow;
}
LayoutRect NGInkOverflow::ComputeTextDecorationOverflow(
const NGTextFragmentPaintInfo& text_info,
const ComputedStyle& style,
const LayoutRect& ink_overflow) {
// TODO(https://crbug.com/1145160): Reduce code duplication between here and
// TextPainterBase::PaintDecorations*.
// Use a zero offset because all offsets
// are applied to the ink overflow after it has been computed.
PhysicalOffset offset;
TextDecorationInfo decoration_info(offset, offset, ink_overflow.Width(),
style.GetFontBaseline(), style,
base::nullopt, nullptr);
NGTextDecorationOffset decoration_offset(decoration_info.Style(), style,
nullptr);
const Vector<AppliedTextDecoration>& decorations =
style.AppliedTextDecorations();
// text-underline-position may flip underline and overline.
ResolvedUnderlinePosition underline_position =
decoration_info.UnderlinePosition();
bool flip_underline_and_overline = false;
if (underline_position == ResolvedUnderlinePosition::kOver) {
flip_underline_and_overline = true;
underline_position = ResolvedUnderlinePosition::kUnder;
}
FloatRect accumulated_bound;
for (size_t applied_decoration_index = 0;
applied_decoration_index < decorations.size();
++applied_decoration_index) {
const AppliedTextDecoration& decoration =
decorations[applied_decoration_index];
TextDecoration lines = decoration.Lines();
bool has_underline = EnumHasFlags(lines, TextDecoration::kUnderline);
bool has_overline = EnumHasFlags(lines, TextDecoration::kOverline);
if (flip_underline_and_overline)
std::swap(has_underline, has_overline);
decoration_info.SetDecorationIndex(applied_decoration_index);
float resolved_thickness = decoration_info.ResolvedThickness();
if (has_underline) {
// Don't apply text-underline-offset to overline.
Length line_offset =
flip_underline_and_overline ? Length() : decoration.UnderlineOffset();
const int paint_underline_offset =
decoration_offset.ComputeUnderlineOffset(
underline_position, decoration_info.Style().ComputedFontSize(),
decoration_info.FontData()->GetFontMetrics(), line_offset,
resolved_thickness);
decoration_info.SetPerLineData(
TextDecoration::kUnderline, paint_underline_offset,
TextDecorationInfo::DoubleOffsetFromThickness(resolved_thickness), 1);
accumulated_bound.Unite(
decoration_info.BoundsForLine(TextDecoration::kUnderline));
}
if (has_overline) {
// Don't apply text-underline-offset to overline.
Length line_offset =
flip_underline_and_overline ? decoration.UnderlineOffset() : Length();
FontVerticalPositionType position =
flip_underline_and_overline ? FontVerticalPositionType::TopOfEmHeight
: FontVerticalPositionType::TextTop;
const int paint_overline_offset =
decoration_offset.ComputeUnderlineOffsetForUnder(
line_offset, decoration_info.Style().ComputedFontSize(),
resolved_thickness, position);
decoration_info.SetPerLineData(
TextDecoration::kOverline, paint_overline_offset,
-TextDecorationInfo::DoubleOffsetFromThickness(resolved_thickness),
1);
accumulated_bound.Unite(
decoration_info.BoundsForLine(TextDecoration::kOverline));
}
if (EnumHasFlags(lines, TextDecoration::kLineThrough)) {
// For increased line thickness, the line-through decoration needs to grow
// in both directions from its origin, subtract half the thickness to keep
// it centered at the same origin.
const float line_through_offset =
2 * decoration_info.Baseline() / 3 - resolved_thickness / 2;
// Floor double_offset in order to avoid double-line gap to appear
// of different size depending on position where the double line
// is drawn because of rounding downstream in
// GraphicsContext::DrawLineForText.
decoration_info.SetPerLineData(
TextDecoration::kLineThrough, line_through_offset,
floorf(TextDecorationInfo::DoubleOffsetFromThickness(
resolved_thickness)),
0);
accumulated_bound.Unite(
decoration_info.BoundsForLine(TextDecoration::kLineThrough));
}
}
return EnclosingLayoutRect(accumulated_bound);
}
} // namespace blink