Commit f77b0d39 authored by kojii's avatar kojii Committed by Commit bot

[LayoutNG] Add Bidi reordering and fill in NGPhysicalTextFragment

This patch adds Bidi reordering to NGInlineNode, using ICU BiDi[1] to
implement rules defined in UAX#9 3.4 Reordering Resolved Levels[2].

The CreateLine() function produces NGTextFragment in the visual order.

This patch also adds a pointer to NGInlineNode and item index range to
NGPhysicalTextFragment.

UAX#9 L1[3] is not supported in this patch. ICU BiDi has a function for
L1, but efficient implementation needs more considerations. This will be
revisited after the line breaker. Note, L1 is not fully supported in
BidiResolver <crbug.com/316409>.

There is only one test case for the Bidi reordering in this patch. We
may run test cases from Unicode[4], or maybe it's overkill. More test
coverage is to be worked out.

[1] http://userguide.icu-project.org/transforms/bidi
[2] http://unicode.org/reports/tr9/#Reordering_Resolved_Levels
[3] http://unicode.org/reports/tr9/#L1
[4] http://www.unicode.org/Public/UNIDATA/

BUG=636993

Review-Url: https://codereview.chromium.org/2563403002
Cr-Commit-Position: refs/heads/master@{#439427}
parent 73532032
......@@ -4,6 +4,7 @@
#include "core/layout/ng/ng_bidi_paragraph.h"
#include "core/layout/ng/ng_inline_node.h"
#include "core/style/ComputedStyle.h"
#include "platform/text/ICUError.h"
......@@ -39,4 +40,23 @@ unsigned NGBidiParagraph::GetLogicalRun(unsigned start,
return end;
}
void NGBidiParagraph::IndiciesInVisualOrder(
const NGLayoutInlineItemRange& items,
Vector<int32_t, 32>* item_indicies_in_visual_order_out) {
// ICU |ubidi_getVisualMap()| works for a run of characters. Since we can
// handle the direction of each run, we use |ubidi_reorderVisual()| to reorder
// runs instead of characters.
// To do so, create a list of bidi levels by pretending a run is a character.
Vector<UBiDiLevel, 32> levels;
levels.reserveInitialCapacity(items.Size());
for (const NGLayoutInlineItem& item : items)
levels.append(item.BidiLevel());
// Check the size before passing the raw pointers to ICU.
CHECK_EQ(items.Size(), levels.size());
CHECK_EQ(items.Size(), item_indicies_in_visual_order_out->size());
ubidi_reorderVisual(levels.data(), items.Size(),
item_indicies_in_visual_order_out->data());
}
} // namespace blink
......@@ -7,12 +7,14 @@
#include "wtf/Allocator.h"
#include "wtf/Forward.h"
#include "wtf/Vector.h"
#include <unicode/ubidi.h>
namespace blink {
class ComputedStyle;
class NGLayoutInlineItemRange;
// NGBidiParagraph resolves bidirectional runs in a paragraph using ICU BiDi.
// http://userguide.icu-project.org/transforms/bidi
......@@ -41,6 +43,11 @@ class NGBidiParagraph {
// offset.
unsigned GetLogicalRun(unsigned start, UBiDiLevel*) const;
// Create a list of indicies in the visual order.
static void IndiciesInVisualOrder(
const NGLayoutInlineItemRange&,
Vector<int32_t, 32>* item_indicies_in_visual_order_out);
private:
UBiDi* ubidi_ = nullptr;
};
......
......@@ -7,6 +7,7 @@
#include "core/layout/ng/ng_block_node.h"
#include "core/layout/ng/ng_fragment_base.h"
#include "core/layout/ng/ng_physical_fragment.h"
#include "core/layout/ng/ng_physical_text_fragment.h"
namespace blink {
......@@ -148,6 +149,18 @@ NGPhysicalFragment* NGFragmentBuilder::ToFragment() {
out_of_flow_descendants_, out_of_flow_positions_, margin_strut_);
}
NGPhysicalTextFragment* NGFragmentBuilder::ToTextFragment(NGInlineNode* node,
unsigned start_index,
unsigned end_index) {
DCHECK_EQ(type_, NGPhysicalFragmentBase::kFragmentText);
DCHECK(children_.isEmpty());
DCHECK(offsets_.isEmpty());
return new NGPhysicalTextFragment(
node, start_index, end_index, size_.ConvertToPhysical(writing_mode_),
overflow_.ConvertToPhysical(writing_mode_), out_of_flow_descendants_,
out_of_flow_positions_);
}
DEFINE_TRACE(NGFragmentBuilder) {
visitor->trace(children_);
visitor->trace(out_of_flow_descendant_candidates_);
......
......@@ -11,7 +11,9 @@
namespace blink {
class NGFragmentBase;
class NGInlineNode;
class NGPhysicalFragment;
class NGPhysicalTextFragment;
class CORE_EXPORT NGFragmentBuilder final
: public GarbageCollectedFinalized<NGFragmentBuilder> {
......@@ -81,6 +83,9 @@ class CORE_EXPORT NGFragmentBuilder final
// Creates the fragment. Can only be called once.
NGPhysicalFragment* ToFragment();
NGPhysicalTextFragment* ToTextFragment(NGInlineNode*,
unsigned start_index,
unsigned end_index);
DECLARE_VIRTUAL_TRACE();
......
......@@ -39,6 +39,10 @@ NGInlineNode::NGInlineNode()
NGInlineNode::~NGInlineNode() {}
NGLayoutInlineItemRange NGInlineNode::Items(unsigned start, unsigned end) {
return NGLayoutInlineItemRange(&items_, start, end);
}
void NGInlineNode::PrepareLayout() {
// Scan list of siblings collecting all in-flow non-atomic inlines. A single
// NGInlineNode represent a collection of adjacent non-atomic inlines.
......@@ -202,32 +206,6 @@ void NGInlineNode::ShapeText() {
}
}
unsigned NGInlineNode::CreateLine(unsigned start,
NGConstraintSpace* constraint_space,
NGFragmentBuilder* builder) {
// TODO(kojii): |unsigned start| should be BreakToken.
// TODO(kojii): implement line breaker and bidi reordering.
for (unsigned i = start; i < items_.size(); i++) {
const NGLayoutInlineItem& item = items_[i];
// TODO(kojii): handle bidi controls and atomic inlines properly.
if (!item.style_)
continue;
// TODO(kojii): There should be only one oof descendants list for a
// NGInlineNode. Attach to the first NGPhysicalTextFragment and leave the
// rest empty, or attach to line box/root line box fragment?
HeapLinkedHashSet<WeakMember<NGBlockNode>> out_of_flow_descendants;
Vector<NGStaticPosition> out_of_flow_positions;
// TODO(kojii): Create NGTextFragment from |item|.
NGPhysicalTextFragment* fragment = new NGPhysicalTextFragment(
NGPhysicalSize(), NGPhysicalSize(), out_of_flow_descendants,
out_of_flow_positions);
builder->AddChild(new NGTextFragment(constraint_space->WritingMode(),
item.Direction(), fragment),
NGLogicalOffset());
}
return 0; // All items are consumed.
}
bool NGInlineNode::Layout(NGConstraintSpace* constraint_space,
NGFragmentBase** out) {
PrepareLayout();
......@@ -275,4 +253,14 @@ DEFINE_TRACE(NGInlineNode) {
NGLayoutInputNode::trace(visitor);
}
NGLayoutInlineItemRange::NGLayoutInlineItemRange(
Vector<NGLayoutInlineItem>* items,
unsigned start_index,
unsigned end_index)
: start_item_(&(*items)[start_index]),
size_(end_index - start_index),
start_index_(start_index) {
RELEASE_ASSERT(start_index <= end_index && end_index <= items->size());
}
} // namespace blink
......@@ -21,9 +21,9 @@ class ComputedStyle;
class LayoutObject;
class NGConstraintSpace;
class NGFragmentBase;
class NGFragmentBuilder;
class NGLayoutAlgorithm;
class NGLayoutInlineItem;
class NGLayoutInlineItemRange;
class NGLayoutInlineItemsBuilder;
// Represents an inline node to be laid out.
......@@ -39,12 +39,15 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
// calling the Layout method.
void PrepareLayout();
unsigned CreateLine(unsigned start, NGConstraintSpace*, NGFragmentBuilder*);
String Text(unsigned start_offset, unsigned end_offset) const {
return text_content_.substring(start_offset, end_offset);
}
Vector<NGLayoutInlineItem>& Items() { return items_; }
NGLayoutInlineItemRange Items(unsigned start_index, unsigned end_index);
bool IsBidiEnabled() const { return is_bidi_enabled_; }
DECLARE_VIRTUAL_TRACE();
protected:
......@@ -94,6 +97,7 @@ class NGLayoutInlineItem {
unsigned StartOffset() const { return start_offset_; }
unsigned EndOffset() const { return end_offset_; }
TextDirection Direction() const { return bidi_level_ & 1 ? RTL : LTR; }
UBiDiLevel BidiLevel() const { return bidi_level_; }
UScriptCode Script() const { return script_; }
const ComputedStyle* Style() const { return style_; }
......@@ -126,6 +130,44 @@ DEFINE_TYPE_CASTS(NGInlineNode,
node->Type() == NGLayoutInputNode::kLegacyInline,
node.Type() == NGLayoutInputNode::kLegacyInline);
// A vector-like object that points to a subset of an array of
// |NGLayoutInlineItem|.
// The source vector must keep alive and must not resize while this object
// is alive.
class NGLayoutInlineItemRange {
STACK_ALLOCATED();
public:
NGLayoutInlineItemRange(Vector<NGLayoutInlineItem>*,
unsigned start_index,
unsigned end_index);
unsigned StartIndex() const { return start_index_; }
unsigned EndIndex() const { return start_index_ + size_; }
unsigned Size() const { return size_; }
NGLayoutInlineItem& operator[](unsigned index) {
RELEASE_ASSERT(index < size_);
return start_item_[index];
}
const NGLayoutInlineItem& operator[](unsigned index) const {
RELEASE_ASSERT(index < size_);
return start_item_[index];
}
using iterator = NGLayoutInlineItem*;
using const_iterator = const NGLayoutInlineItem*;
iterator begin() { return start_item_; }
iterator end() { return start_item_ + size_; }
const_iterator begin() const { return start_item_; }
const_iterator end() const { return start_item_ + size_; }
private:
NGLayoutInlineItem* start_item_;
unsigned size_;
unsigned start_index_;
};
} // namespace blink
#endif // NGInlineNode_h
......@@ -4,13 +4,17 @@
#include "core/layout/ng/ng_inline_node.h"
#include "core/layout/ng/ng_constraint_space.h"
#include "core/layout/ng/ng_constraint_space_builder.h"
#include "core/layout/ng/ng_fragment_builder.h"
#include "core/layout/ng/ng_physical_text_fragment.h"
#include "core/layout/ng/ng_text_fragment.h"
#include "core/layout/ng/ng_text_layout_algorithm.h"
#include "core/style/ComputedStyle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
class NGInlineNodeForTest : public NGInlineNode {
public:
NGInlineNodeForTest(const ComputedStyle* block_style) {
......@@ -21,14 +25,17 @@ class NGInlineNodeForTest : public NGInlineNode {
String& Text() { return text_content_; }
Vector<NGLayoutInlineItem>& Items() { return items_; }
void AppendText(const String& text) {
void Append(const String& text, const ComputedStyle* style = nullptr) {
unsigned start = text_content_.length();
text_content_.append(text);
items_.emplace_back(start, start + text.length(), nullptr);
items_.append(NGLayoutInlineItem(start, start + text.length(), style));
}
void AppendText(const char16_t* text) {
AppendText(String(reinterpret_cast<const UChar*>(text)));
void Append(UChar character) {
text_content_.append(character);
unsigned end = text_content_.length();
items_.append(NGLayoutInlineItem(end - 1, end, nullptr));
is_bidi_enabled_ = true;
}
void ClearText() {
......@@ -36,77 +43,157 @@ class NGInlineNodeForTest : public NGInlineNode {
items_.clear();
}
void SegmentText() { NGInlineNode::SegmentText(); }
void SegmentText() {
is_bidi_enabled_ = true;
NGInlineNode::SegmentText();
}
};
class NGInlineNodeTest : public ::testing::Test {
protected:
void SetUp() override { style_ = ComputedStyle::create(); }
void CreateLine(NGInlineNode* node,
unsigned start_index,
unsigned end_index,
HeapVector<Member<NGPhysicalTextFragment>>* fragments_out) {
NGConstraintSpace* constraint_space =
NGConstraintSpaceBuilder(kHorizontalTopBottom).ToConstraintSpace();
HeapVector<Member<NGFragmentBase>> fragments;
Vector<NGLogicalOffset> logical_offsets;
NGTextLayoutAlgorithm* algorithm =
new NGTextLayoutAlgorithm(node, nullptr, nullptr);
algorithm->CreateLine(node->Items(start_index, end_index),
*constraint_space, &fragments, &logical_offsets);
for (const NGFragmentBase* fragment : fragments) {
fragments_out->append(
toNGPhysicalTextFragment(fragment->PhysicalFragment()));
}
}
RefPtr<ComputedStyle> style_;
};
class NGInlineNodeTest : public ::testing::Test {};
#define TEST_ITEM_OFFSET_DIR(item, start, end, direction) \
EXPECT_EQ(start, item.StartOffset()); \
EXPECT_EQ(end, item.EndOffset()); \
EXPECT_EQ(direction, item.Direction())
TEST_F(NGInlineNodeTest, SegmentASCII) {
RefPtr<ComputedStyle> style = ComputedStyle::create();
NGInlineNodeForTest* box = new NGInlineNodeForTest(style.get());
box->AppendText("Hello");
box->SegmentText();
ASSERT_EQ(1u, box->Items().size());
NGLayoutInlineItem& item = box->Items()[0];
EXPECT_EQ(0u, item.StartOffset());
EXPECT_EQ(5u, item.EndOffset());
EXPECT_EQ(LTR, item.Direction());
NGInlineNodeForTest* node = new NGInlineNodeForTest(style_.get());
node->Append("Hello");
node->SegmentText();
Vector<NGLayoutInlineItem>& items = node->Items();
ASSERT_EQ(1u, items.size());
TEST_ITEM_OFFSET_DIR(items[0], 0u, 5u, LTR);
}
TEST_F(NGInlineNodeTest, SegmentHebrew) {
RefPtr<ComputedStyle> style = ComputedStyle::create();
NGInlineNodeForTest* box = new NGInlineNodeForTest(style.get());
box->AppendText(u"\u05E2\u05D1\u05E8\u05D9\u05EA");
box->SegmentText();
ASSERT_EQ(1u, box->Items().size());
NGLayoutInlineItem& item = box->Items()[0];
EXPECT_EQ(0u, item.StartOffset());
EXPECT_EQ(5u, item.EndOffset());
EXPECT_EQ(RTL, item.Direction());
NGInlineNodeForTest* node = new NGInlineNodeForTest(style_.get());
node->Append(u"\u05E2\u05D1\u05E8\u05D9\u05EA");
node->SegmentText();
ASSERT_EQ(1u, node->Items().size());
Vector<NGLayoutInlineItem>& items = node->Items();
ASSERT_EQ(1u, items.size());
TEST_ITEM_OFFSET_DIR(items[0], 0u, 5u, RTL);
}
TEST_F(NGInlineNodeTest, SegmentSplit1To2) {
RefPtr<ComputedStyle> style = ComputedStyle::create();
NGInlineNodeForTest* box = new NGInlineNodeForTest(style.get());
box->AppendText(u"Hello \u05E2\u05D1\u05E8\u05D9\u05EA");
box->SegmentText();
ASSERT_EQ(2u, box->Items().size());
NGLayoutInlineItem& item = box->Items()[0];
EXPECT_EQ(0u, item.StartOffset());
EXPECT_EQ(6u, item.EndOffset());
EXPECT_EQ(LTR, item.Direction());
item = box->Items()[1];
EXPECT_EQ(6u, item.StartOffset());
EXPECT_EQ(11u, item.EndOffset());
EXPECT_EQ(RTL, item.Direction());
NGInlineNodeForTest* node = new NGInlineNodeForTest(style_.get());
node->Append(u"Hello \u05E2\u05D1\u05E8\u05D9\u05EA");
node->SegmentText();
ASSERT_EQ(2u, node->Items().size());
Vector<NGLayoutInlineItem>& items = node->Items();
ASSERT_EQ(2u, items.size());
TEST_ITEM_OFFSET_DIR(items[0], 0u, 6u, LTR);
TEST_ITEM_OFFSET_DIR(items[1], 6u, 11u, RTL);
}
TEST_F(NGInlineNodeTest, SegmentSplit3To4) {
RefPtr<ComputedStyle> style = ComputedStyle::create();
NGInlineNodeForTest* box = new NGInlineNodeForTest(style.get());
box->AppendText("Hel");
box->AppendText(u"lo \u05E2");
box->AppendText(u"\u05D1\u05E8\u05D9\u05EA");
box->SegmentText();
ASSERT_EQ(4u, box->Items().size());
NGLayoutInlineItem& item = box->Items()[0];
EXPECT_EQ(0u, item.StartOffset());
EXPECT_EQ(3u, item.EndOffset());
EXPECT_EQ(LTR, item.Direction());
item = box->Items()[1];
EXPECT_EQ(3u, item.StartOffset());
EXPECT_EQ(6u, item.EndOffset());
EXPECT_EQ(LTR, item.Direction());
item = box->Items()[2];
EXPECT_EQ(6u, item.StartOffset());
EXPECT_EQ(7u, item.EndOffset());
EXPECT_EQ(RTL, item.Direction());
item = box->Items()[3];
EXPECT_EQ(7u, item.StartOffset());
EXPECT_EQ(11u, item.EndOffset());
EXPECT_EQ(RTL, item.Direction());
NGInlineNodeForTest* node = new NGInlineNodeForTest(style_.get());
node->Append("Hel");
node->Append(u"lo \u05E2");
node->Append(u"\u05D1\u05E8\u05D9\u05EA");
node->SegmentText();
Vector<NGLayoutInlineItem>& items = node->Items();
ASSERT_EQ(4u, items.size());
TEST_ITEM_OFFSET_DIR(items[0], 0u, 3u, LTR);
TEST_ITEM_OFFSET_DIR(items[1], 3u, 6u, LTR);
TEST_ITEM_OFFSET_DIR(items[2], 6u, 7u, RTL);
TEST_ITEM_OFFSET_DIR(items[3], 7u, 11u, RTL);
}
TEST_F(NGInlineNodeTest, SegmentBidiOverride) {
NGInlineNodeForTest* node = new NGInlineNodeForTest(style_.get());
node->Append("Hello ");
node->Append(rightToLeftOverrideCharacter);
node->Append("ABC");
node->Append(popDirectionalFormattingCharacter);
node->SegmentText();
Vector<NGLayoutInlineItem>& items = node->Items();
ASSERT_EQ(4u, items.size());
TEST_ITEM_OFFSET_DIR(items[0], 0u, 6u, LTR);
TEST_ITEM_OFFSET_DIR(items[1], 6u, 7u, RTL);
TEST_ITEM_OFFSET_DIR(items[2], 7u, 10u, RTL);
TEST_ITEM_OFFSET_DIR(items[3], 10u, 11u, LTR);
}
} // namespace
static NGInlineNodeForTest* CreateBidiIsolateNode(const ComputedStyle* style) {
NGInlineNodeForTest* node = new NGInlineNodeForTest(style);
node->Append("Hello ", style);
node->Append(rightToLeftIsolateCharacter);
node->Append(u"\u05E2\u05D1\u05E8\u05D9\u05EA ", style);
node->Append(leftToRightIsolateCharacter);
node->Append("A", style);
node->Append(popDirectionalIsolateCharacter);
node->Append(u"\u05E2\u05D1\u05E8\u05D9\u05EA", style);
node->Append(popDirectionalIsolateCharacter);
node->Append(" World", style);
node->SegmentText();
return node;
}
TEST_F(NGInlineNodeTest, SegmentBidiIsolate) {
NGInlineNodeForTest* node = CreateBidiIsolateNode(style_.get());
Vector<NGLayoutInlineItem>& items = node->Items();
ASSERT_EQ(9u, items.size());
TEST_ITEM_OFFSET_DIR(items[0], 0u, 6u, LTR);
TEST_ITEM_OFFSET_DIR(items[1], 6u, 7u, LTR);
TEST_ITEM_OFFSET_DIR(items[2], 7u, 13u, RTL);
TEST_ITEM_OFFSET_DIR(items[3], 13u, 14u, RTL);
TEST_ITEM_OFFSET_DIR(items[4], 14u, 15u, LTR);
TEST_ITEM_OFFSET_DIR(items[5], 15u, 16u, RTL);
TEST_ITEM_OFFSET_DIR(items[6], 16u, 21u, RTL);
TEST_ITEM_OFFSET_DIR(items[7], 21u, 22u, LTR);
TEST_ITEM_OFFSET_DIR(items[8], 22u, 28u, LTR);
}
#define TEST_TEXT_FRAGMENT(fragment, node, start_index, end_index, dir) \
EXPECT_EQ(node, fragment->Node()); \
EXPECT_EQ(start_index, fragment->StartIndex()); \
EXPECT_EQ(end_index, fragment->EndIndex()); \
EXPECT_EQ(dir, node->Items()[fragment->StartIndex()].Direction())
TEST_F(NGInlineNodeTest, CreateLineBidiIsolate) {
NGInlineNodeForTest* node = CreateBidiIsolateNode(style_.get());
HeapVector<Member<NGPhysicalTextFragment>> fragments;
CreateLine(node, 0, node->Items().size(), &fragments);
ASSERT_EQ(5u, fragments.size());
TEST_TEXT_FRAGMENT(fragments[0], node, 0u, 1u, LTR);
TEST_TEXT_FRAGMENT(fragments[1], node, 6u, 7u, RTL);
TEST_TEXT_FRAGMENT(fragments[2], node, 4u, 5u, LTR);
TEST_TEXT_FRAGMENT(fragments[3], node, 2u, 3u, RTL);
TEST_TEXT_FRAGMENT(fragments[4], node, 8u, 9u, LTR);
}
TEST_F(NGInlineNodeTest, CreateLineRangeBidiIsolate) {
NGInlineNodeForTest* node = CreateBidiIsolateNode(style_.get());
HeapVector<Member<NGPhysicalTextFragment>> fragments;
CreateLine(node, 2, 7, &fragments);
ASSERT_EQ(3u, fragments.size());
TEST_TEXT_FRAGMENT(fragments[0], node, 6u, 7u, RTL);
TEST_TEXT_FRAGMENT(fragments[1], node, 4u, 5u, LTR);
TEST_TEXT_FRAGMENT(fragments[2], node, 2u, 3u, RTL);
}
} // namespace blink
......@@ -7,6 +7,7 @@
#include "core/CoreExport.h"
#include "core/layout/ng/ng_block_node.h"
#include "core/layout/ng/ng_inline_node.h"
#include "core/layout/ng/ng_physical_fragment_base.h"
#include "platform/heap/Handle.h"
......@@ -15,6 +16,9 @@ namespace blink {
class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragmentBase {
public:
NGPhysicalTextFragment(
const NGInlineNode* node,
unsigned start_index,
unsigned end_index,
NGPhysicalSize size,
NGPhysicalSize overflow,
HeapLinkedHashSet<WeakMember<NGBlockNode>>& out_of_flow_descendants,
......@@ -23,11 +27,30 @@ class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragmentBase {
overflow,
kFragmentText,
out_of_flow_descendants,
out_of_flow_positions) {}
out_of_flow_positions),
node_(node),
start_index_(start_index),
end_index_(end_index) {}
DEFINE_INLINE_TRACE_AFTER_DISPATCH() {
visitor->trace(node_);
NGPhysicalFragmentBase::traceAfterDispatch(visitor);
}
const NGInlineNode* Node() const { return node_; }
// The range of NGLayoutInlineItem.
// |StartIndex| shows the lower logical index, so the visual order iteration
// for RTL should be done from |EndIndex - 1| to |StartIndex|.
unsigned StartIndex() const { return start_index_; }
unsigned EndIndex() const { return end_index_; }
private:
// TODO(kojii): NGInlineNode is to access text content and NGLayoutInlineItem.
// Review if it's better to point them.
Member<const NGInlineNode> node_;
unsigned start_index_;
unsigned end_index_;
};
DEFINE_TYPE_CASTS(NGPhysicalTextFragment,
......
......@@ -4,11 +4,14 @@
#include "core/layout/ng/ng_text_layout_algorithm.h"
#include "core/layout/ng/ng_bidi_paragraph.h"
#include "core/layout/ng/ng_break_token.h"
#include "core/layout/ng/ng_constraint_space.h"
#include "core/layout/ng/ng_fragment.h"
#include "core/layout/ng/ng_fragment_builder.h"
#include "core/layout/ng/ng_text_fragment.h"
#include "core/layout/ng/ng_inline_node.h"
#include "core/style/ComputedStyle.h"
namespace blink {
......@@ -27,26 +30,124 @@ NGLayoutStatus NGTextLayoutAlgorithm::Layout(
NGPhysicalFragmentBase*,
NGPhysicalFragmentBase** fragment_out,
NGLayoutAlgorithm**) {
// TODO(kojii): What kind of fragment tree do we want for line boxes/root line
// boxes? Just text, box, or new type of fragment?
NGFragmentBuilder root_line_box_builder(NGPhysicalFragmentBase::kFragmentBox);
root_line_box_builder.SetWritingMode(constraint_space_->WritingMode());
root_line_box_builder.SetDirection(constraint_space_->Direction());
HeapVector<Member<NGFragmentBase>> fragments;
// TODO(kojii): Make this tickable. Each line is easy. Needs more thoughts
// for each fragment in a line. Bidi reordering is probably atomic.
Vector<NGLogicalOffset> logical_offsets;
unsigned start = 0;
do {
NGFragmentBuilder line_box_builder(NGPhysicalFragmentBase::kFragmentBox);
start =
inline_box_->CreateLine(start, constraint_space_, &line_box_builder);
root_line_box_builder.AddChild(
new NGFragment(constraint_space_->WritingMode(),
constraint_space_->Direction(),
line_box_builder.ToFragment()),
NGLogicalOffset());
start = CreateLine(start, *constraint_space_, &fragments, &logical_offsets);
DCHECK_EQ(fragments.size(), logical_offsets.size());
} while (start);
*fragment_out = root_line_box_builder.ToFragment();
// TODO(kojii): Put all TextFragments into children of a kFragmentBox since
// this function can return only one fragment. Change the signature to return
// a list of NGTextFragment, only for NGTextLayoutAlgorithm.
NGFragmentBuilder container_builder(NGPhysicalFragmentBase::kFragmentBox);
container_builder.SetWritingMode(constraint_space_->WritingMode());
container_builder.SetDirection(constraint_space_->Direction());
for (unsigned i = 0; i < fragments.size(); i++) {
container_builder.AddChild(fragments[i], logical_offsets[i]);
}
*fragment_out = container_builder.ToFragment();
return kNewFragment;
}
// Compute the line break for the line starting at |start_index|.
// @return the index after the line break, or 0 if no more lines.
unsigned NGTextLayoutAlgorithm::CreateLine(
unsigned start_index,
const NGConstraintSpace& constraint_space,
HeapVector<Member<NGFragmentBase>>* fragments_out,
Vector<NGLogicalOffset>* logical_offsets_out) {
// TODO(kojii): |unsigned start| should be BreakToken.
// TODO(kojii): implement line breaker.
CreateLine(inline_box_->Items(start_index, inline_box_->Items().size()),
constraint_space, fragments_out, logical_offsets_out);
return 0; // All items are consumed.
}
// Create text fragments for a line from the specified |items|.
// Bidi reordering is applied if necessary.
void NGTextLayoutAlgorithm::CreateLine(
const NGLayoutInlineItemRange& items,
const NGConstraintSpace& constraint_space,
HeapVector<Member<NGFragmentBase>>* fragments_out,
Vector<NGLogicalOffset>* logical_offsets_out) {
NGFragmentBuilder text_builder(NGPhysicalFragmentBase::kFragmentText);
text_builder.SetWritingMode(constraint_space.WritingMode());
// TODO(kojii): atomic inline is not well-thought yet.
// TODO(kojii): oof is not well-thought yet. The bottom static position may be
// in the next line, https://github.com/w3c/csswg-drafts/issues/609
// TODO(kojii): need to split TextFragment if:
// * oof static position is needed.
// * nested borders?
if (!inline_box_->IsBidiEnabled()) {
// If no bidi reordering, the logical order is the visual order.
DCHECK_EQ(constraint_space.Direction(), LTR);
DCHECK_EQ(items[0].Style()->direction(), LTR);
fragments_out->append(new NGTextFragment(
constraint_space.WritingMode(), constraint_space.Direction(),
text_builder.ToTextFragment(inline_box_, items.StartIndex(),
items.EndIndex())));
logical_offsets_out->append(NGLogicalOffset());
return;
}
// TODO(kojii): UAX#9 L1 is not supported yet. Supporting L1 may change
// embedding levels of parts of runs, which requires to split items.
// http://unicode.org/reports/tr9/#L1
// BidiResolver does not support L1 crbug.com/316409.
// Create a list of item indicies in the visual order.
Vector<int32_t, 32> item_indicies_in_visual_order(items.Size());
NGBidiParagraph::IndiciesInVisualOrder(items, &item_indicies_in_visual_order);
// Create a TextFragment for each bidi level.
// Bidi controls inserted in |CollectInlines()| are excluded.
for (unsigned visual_start = 0; visual_start < items.Size();) {
int32_t logical_start = item_indicies_in_visual_order[visual_start];
const NGLayoutInlineItem& start_item = items[logical_start];
if (!start_item.Style()) { // Skip bidi controls.
visual_start++;
continue;
}
UBiDiLevel level = start_item.BidiLevel();
unsigned visual_end = visual_start + 1;
for (; visual_end < items.Size(); visual_end++) {
int32_t logical_next = item_indicies_in_visual_order[visual_end];
const NGLayoutInlineItem& next_item = items[logical_next];
if (!next_item.Style()) // Stop before bidi controls.
break;
if (next_item.BidiLevel() != level)
break;
DCHECK_EQ(logical_next, item_indicies_in_visual_order[visual_end - 1] +
(level & 1 ? -1 : 1));
}
int32_t logical_end;
if (level & 1) {
// start/end are in the logical order, see NGPhysicalTextFragment.
logical_end = logical_start + 1;
logical_start = logical_end - (visual_end - visual_start);
} else {
logical_end = logical_start + (visual_end - visual_start);
}
// The direction of a fragment is the CSS direction to resolve logical
// properties, not the resolved bidi direction.
TextDirection css_direction = start_item.Style()->direction();
text_builder.SetDirection(css_direction);
fragments_out->append(new NGTextFragment(
constraint_space.WritingMode(), css_direction,
text_builder.ToTextFragment(inline_box_,
logical_start + items.StartIndex(),
logical_end + items.StartIndex())));
logical_offsets_out->append(NGLogicalOffset());
visual_start = visual_end;
}
}
DEFINE_TRACE(NGTextLayoutAlgorithm) {
NGLayoutAlgorithm::trace(visitor);
visitor->trace(inline_box_);
......
......@@ -12,7 +12,10 @@ namespace blink {
class NGBreakToken;
class NGConstraintSpace;
class NGFragmentBase;
class NGInlineNode;
class NGLayoutInlineItemRange;
struct NGLogicalOffset;
// A class for text layout. This takes a NGInlineNode which consists only
// non-atomic inlines and produces NGTextFragments.
......@@ -37,9 +40,20 @@ class CORE_EXPORT NGTextLayoutAlgorithm : public NGLayoutAlgorithm {
DECLARE_VIRTUAL_TRACE();
private:
unsigned CreateLine(unsigned start_index,
const NGConstraintSpace&,
HeapVector<Member<NGFragmentBase>>*,
Vector<NGLogicalOffset>*);
void CreateLine(const NGLayoutInlineItemRange&,
const NGConstraintSpace&,
HeapVector<Member<NGFragmentBase>>*,
Vector<NGLogicalOffset>*);
Member<NGInlineNode> inline_box_;
Member<NGConstraintSpace> constraint_space_;
Member<NGBreakToken> break_token_;
friend class NGInlineNodeTest;
};
} // namespace blink
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment