blob: 1cad2ef567bb3883d61504fb1fcc30c818b26bf9 [file] [log] [blame]
/*
* Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "core/layout/line/InlineBox.h"
#include "core/layout/HitTestLocation.h"
#include "core/layout/LayoutBlockFlow.h"
#include "core/layout/api/LineLayoutBlockFlow.h"
#include "core/layout/line/InlineFlowBox.h"
#include "core/layout/line/RootInlineBox.h"
#include "core/paint/BlockPainter.h"
#include "core/paint/PaintInfo.h"
#include "platform/fonts/FontMetrics.h"
#include "platform/wtf/allocator/Partitions.h"
#ifndef NDEBUG
#include <stdio.h>
#endif
namespace blink {
class LayoutObject;
struct SameSizeAsInlineBox : DisplayItemClient {
~SameSizeAsInlineBox() override {}
void* a[4];
LayoutPoint b;
LayoutUnit c;
uint32_t bitfields;
#if DCHECK_IS_ON()
bool f;
#endif
};
static_assert(sizeof(InlineBox) == sizeof(SameSizeAsInlineBox),
"InlineBox should stay small");
#if DCHECK_IS_ON()
InlineBox::~InlineBox() {
if (!has_bad_parent_ && parent_)
parent_->SetHasBadChildList();
}
#endif
DISABLE_CFI_PERF
void InlineBox::Destroy() {
// We do not need to issue invalidations if the page is being destroyed
// since these objects will never be repainted.
if (!line_layout_item_.DocumentBeingDestroyed()) {
SetLineLayoutItemShouldDoFullPaintInvalidationIfNeeded();
// TODO(crbug.com/619630): Make this fast.
line_layout_item_.SlowSetPaintingLayerNeedsRepaint();
}
delete this;
}
void InlineBox::Remove(MarkLineBoxes mark_line_boxes) {
if (Parent())
Parent()->RemoveChild(this, mark_line_boxes);
}
void* InlineBox::operator new(size_t sz) {
return WTF::PartitionAlloc(WTF::Partitions::LayoutPartition(), sz,
WTF_HEAP_PROFILER_TYPE_NAME(InlineBox));
}
void InlineBox::operator delete(void* ptr) {
WTF::PartitionFree(ptr);
}
const char* InlineBox::BoxName() const {
return "InlineBox";
}
String InlineBox::DebugName() const {
return BoxName();
}
LayoutRect InlineBox::VisualRect() const {
// TODO(chrishtr): tighten these bounds.
return GetLineLayoutItem().VisualRect();
}
#ifndef NDEBUG
void InlineBox::ShowTreeForThis() const {
GetLineLayoutItem().ShowTreeForThis();
}
void InlineBox::ShowLineTreeForThis() const {
GetLineLayoutItem().ContainingBlock().ShowLineTreeAndMark(this, "*");
}
void InlineBox::ShowLineTreeAndMark(const InlineBox* marked_box1,
const char* marked_label1,
const InlineBox* marked_box2,
const char* marked_label2,
const LayoutObject* obj,
int depth) const {
int printed_characters = 0;
if (this == marked_box1)
printed_characters += fprintf(stderr, "%s", marked_label1);
if (this == marked_box2)
printed_characters += fprintf(stderr, "%s", marked_label2);
if (GetLineLayoutItem().IsEqual(obj))
printed_characters += fprintf(stderr, "*");
for (; printed_characters < depth * 2; printed_characters++)
fputc(' ', stderr);
ShowBox(printed_characters);
}
void InlineBox::ShowBox(int printed_characters) const {
printed_characters += fprintf(stderr, "%s %p", BoxName(), this);
for (; printed_characters < kShowTreeCharacterOffset; printed_characters++)
fputc(' ', stderr);
fprintf(stderr, "\t%s %p {pos=%g,%g size=%g,%g} baseline=%i/%i\n",
GetLineLayoutItem().DecoratedName().Ascii().data(),
GetLineLayoutItem().DebugPointer(), X().ToFloat(), Y().ToFloat(),
Width().ToFloat(), Height().ToFloat(),
BaselinePosition(kAlphabeticBaseline).ToInt(),
BaselinePosition(kIdeographicBaseline).ToInt());
}
#endif
LayoutUnit InlineBox::LogicalHeight() const {
if (HasVirtualLogicalHeight())
return VirtualLogicalHeight();
const SimpleFontData* font_data =
GetLineLayoutItem().Style(IsFirstLineStyle())->GetFont().PrimaryFont();
if (GetLineLayoutItem().IsText()) {
DCHECK(font_data);
return bitfields_.IsText() && font_data
? LayoutUnit(font_data->GetFontMetrics().Height())
: LayoutUnit();
}
if (GetLineLayoutItem().IsBox() && Parent()) {
return IsHorizontal() ? LineLayoutBox(GetLineLayoutItem()).Size().Height()
: LineLayoutBox(GetLineLayoutItem()).Size().Width();
}
DCHECK(IsInlineFlowBox());
LineLayoutBoxModel flow_object = BoxModelObject();
DCHECK(font_data);
LayoutUnit result(font_data ? font_data->GetFontMetrics().Height() : 0);
if (Parent())
result += flow_object.BorderAndPaddingLogicalHeight();
return result;
}
LayoutUnit InlineBox::BaselinePosition(FontBaseline baseline_type) const {
return BoxModelObject().BaselinePosition(
baseline_type, bitfields_.FirstLine(),
IsHorizontal() ? kHorizontalLine : kVerticalLine,
kPositionOnContainingLine);
}
LayoutUnit InlineBox::LineHeight() const {
return BoxModelObject().LineHeight(
bitfields_.FirstLine(), IsHorizontal() ? kHorizontalLine : kVerticalLine,
kPositionOnContainingLine);
}
int InlineBox::CaretMinOffset() const {
return GetLineLayoutItem().CaretMinOffset();
}
int InlineBox::CaretMaxOffset() const {
return GetLineLayoutItem().CaretMaxOffset();
}
void InlineBox::DirtyLineBoxes() {
MarkDirty();
for (InlineFlowBox* curr = Parent(); curr && !curr->IsDirty();
curr = curr->Parent())
curr->MarkDirty();
}
void InlineBox::DeleteLine() {
if (!bitfields_.Extracted() && GetLineLayoutItem().IsBox())
LineLayoutBox(GetLineLayoutItem()).SetInlineBoxWrapper(nullptr);
Destroy();
}
void InlineBox::ExtractLine() {
bitfields_.SetExtracted(true);
if (GetLineLayoutItem().IsBox())
LineLayoutBox(GetLineLayoutItem()).SetInlineBoxWrapper(nullptr);
}
void InlineBox::AttachLine() {
bitfields_.SetExtracted(false);
if (GetLineLayoutItem().IsBox())
LineLayoutBox(GetLineLayoutItem()).SetInlineBoxWrapper(this);
}
void InlineBox::Move(const LayoutSize& delta) {
location_.Move(delta);
if (GetLineLayoutItem().IsAtomicInlineLevel())
LineLayoutBox(GetLineLayoutItem()).Move(delta.Width(), delta.Height());
SetLineLayoutItemShouldDoFullPaintInvalidationIfNeeded();
}
void InlineBox::Paint(const PaintInfo& paint_info,
const LayoutPoint& paint_offset,
LayoutUnit /* lineTop */,
LayoutUnit /* lineBottom */) const {
BlockPainter::PaintInlineBox(*this, paint_info, paint_offset);
}
bool InlineBox::NodeAtPoint(HitTestResult& result,
const HitTestLocation& location_in_container,
const LayoutPoint& accumulated_offset,
LayoutUnit /* lineTop */,
LayoutUnit /* lineBottom */) {
// Hit test all phases of replaced elements atomically, as though the replaced
// element established its own stacking context. (See Appendix E.2, section
// 6.4 on inline block/table elements in the CSS2.1 specification.)
LayoutPoint child_point = accumulated_offset;
// Faster than calling containingBlock().
if (Parent()->GetLineLayoutItem().HasFlippedBlocksWritingMode())
child_point =
GetLineLayoutItem().ContainingBlock().FlipForWritingModeForChild(
LineLayoutBox(GetLineLayoutItem()), child_point);
return GetLineLayoutItem().HitTest(result, location_in_container,
child_point);
}
const RootInlineBox& InlineBox::Root() const {
if (parent_)
return parent_->Root();
DCHECK(IsRootInlineBox());
return static_cast<const RootInlineBox&>(*this);
}
RootInlineBox& InlineBox::Root() {
if (parent_)
return parent_->Root();
DCHECK(IsRootInlineBox());
return static_cast<RootInlineBox&>(*this);
}
InlineBox* InlineBox::NextLeafChild() const {
InlineBox* leaf = nullptr;
for (InlineBox* box = NextOnLine(); box && !leaf; box = box->NextOnLine())
leaf = box->IsLeaf() ? box : ToInlineFlowBox(box)->FirstLeafChild();
if (!leaf && Parent())
leaf = Parent()->NextLeafChild();
return leaf;
}
InlineBox* InlineBox::PrevLeafChild() const {
InlineBox* leaf = nullptr;
for (InlineBox* box = PrevOnLine(); box && !leaf; box = box->PrevOnLine())
leaf = box->IsLeaf() ? box : ToInlineFlowBox(box)->LastLeafChild();
if (!leaf && Parent())
leaf = Parent()->PrevLeafChild();
return leaf;
}
InlineBox* InlineBox::NextLeafChildIgnoringLineBreak() const {
InlineBox* leaf = NextLeafChild();
return (leaf && leaf->IsLineBreak()) ? nullptr : leaf;
}
InlineBox* InlineBox::PrevLeafChildIgnoringLineBreak() const {
InlineBox* leaf = PrevLeafChild();
return (leaf && leaf->IsLineBreak()) ? nullptr : leaf;
}
SelectionState InlineBox::GetSelectionState() const {
return GetLineLayoutItem().GetSelectionState();
}
bool InlineBox::CanAccommodateEllipsis(bool ltr,
LayoutUnit block_edge,
LayoutUnit ellipsis_width) const {
// Non-atomic inline-level elements can always accommodate an ellipsis.
// Skip list markers and try the next box.
if (!GetLineLayoutItem().IsAtomicInlineLevel() ||
GetLineLayoutItem().IsListMarker())
return true;
LayoutRect box_rect(X(), LayoutUnit(), logical_width_, LayoutUnit(10));
LayoutRect ellipsis_rect(ltr ? block_edge - ellipsis_width : block_edge,
LayoutUnit(), ellipsis_width, LayoutUnit(10));
return !(box_rect.Intersects(ellipsis_rect));
}
LayoutUnit InlineBox::PlaceEllipsisBox(bool,
LayoutUnit,
LayoutUnit,
LayoutUnit,
LayoutUnit& truncated_width,
InlineBox**,
LayoutUnit) {
// Use -1 to mean "we didn't set the position."
truncated_width += LogicalWidth();
return LayoutUnit(-1);
}
void InlineBox::ClearKnownToHaveNoOverflow() {
bitfields_.SetKnownToHaveNoOverflow(false);
if (Parent() && Parent()->KnownToHaveNoOverflow())
Parent()->ClearKnownToHaveNoOverflow();
}
LayoutPoint InlineBox::PhysicalLocation() const {
LayoutRect rect(Location(), Size());
FlipForWritingMode(rect);
return rect.Location();
}
void InlineBox::FlipForWritingMode(FloatRect& rect) const {
if (!UNLIKELY(GetLineLayoutItem().HasFlippedBlocksWritingMode()))
return;
Root().Block().FlipForWritingMode(rect);
}
FloatPoint InlineBox::FlipForWritingMode(const FloatPoint& point) const {
if (!UNLIKELY(GetLineLayoutItem().HasFlippedBlocksWritingMode()))
return point;
return Root().Block().FlipForWritingMode(point);
}
void InlineBox::FlipForWritingMode(LayoutRect& rect) const {
if (!UNLIKELY(GetLineLayoutItem().HasFlippedBlocksWritingMode()))
return;
Root().Block().FlipForWritingMode(rect);
}
LayoutPoint InlineBox::FlipForWritingMode(const LayoutPoint& point) const {
if (!UNLIKELY(GetLineLayoutItem().HasFlippedBlocksWritingMode()))
return point;
return Root().Block().FlipForWritingMode(point);
}
void InlineBox::SetShouldDoFullPaintInvalidationRecursively() {
GetLineLayoutItem().SetShouldDoFullPaintInvalidation();
if (!IsInlineFlowBox())
return;
for (InlineBox* child = ToInlineFlowBox(this)->FirstChild(); child;
child = child->NextOnLine())
child->SetShouldDoFullPaintInvalidationRecursively();
}
void InlineBox::SetLineLayoutItemShouldDoFullPaintInvalidationIfNeeded() {
// For RootInlineBox, we only need to invalidate if it's using the first line
// style. Otherwise it paints nothing so we don't need to invalidate it.
if (!IsRootInlineBox() || IsFirstLineStyle())
line_layout_item_.SetShouldDoFullPaintInvalidation();
}
bool CanUseInlineBox(const LayoutObject& node) {
DCHECK(node.IsText() || node.IsInline() || node.IsLayoutBlockFlow());
return !RuntimeEnabledFeatures::LayoutNGEnabled() ||
!RuntimeEnabledFeatures::LayoutNGPaintFragmentsEnabled() ||
!node.EnclosingNGBlockFlow();
}
} // namespace blink
#ifndef NDEBUG
void showTree(const blink::InlineBox* b) {
if (b)
b->ShowTreeForThis();
else
fprintf(stderr, "Cannot showTree for (nil) InlineBox.\n");
}
void showLineTree(const blink::InlineBox* b) {
if (b)
b->ShowLineTreeForThis();
else
fprintf(stderr, "Cannot showLineTree for (nil) InlineBox.\n");
}
#endif