mirror of
https://github.com/RGBCube/serenity
synced 2025-05-25 21:25:07 +00:00
LibHTML: Refactor to go from DOM -> styled tree -> layout tree.
Frame::layout() drives everything now, it takes the DOM contained in the frame and puts it through the tree transformations.
This commit is contained in:
parent
6e95b11395
commit
7eef69ad4b
20 changed files with 132 additions and 131 deletions
|
@ -1,5 +1,5 @@
|
||||||
html {
|
html {
|
||||||
display: inline;
|
display: block;
|
||||||
font-family: Katica;
|
font-family: Katica;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ static bool matches(const Selector& selector, const Element& element)
|
||||||
NonnullRefPtrVector<StyleRule> StyleResolver::collect_matching_rules(const Element& element) const
|
NonnullRefPtrVector<StyleRule> StyleResolver::collect_matching_rules(const Element& element) const
|
||||||
{
|
{
|
||||||
NonnullRefPtrVector<StyleRule> matching_rules;
|
NonnullRefPtrVector<StyleRule> matching_rules;
|
||||||
for (auto& sheet : m_sheets) {
|
for (auto& sheet : document().stylesheets()) {
|
||||||
for (auto& rule : sheet.rules()) {
|
for (auto& rule : sheet.rules()) {
|
||||||
for (auto& selector : rule.selectors()) {
|
for (auto& selector : rule.selectors()) {
|
||||||
if (matches(selector, element)) {
|
if (matches(selector, element)) {
|
||||||
|
|
|
@ -18,8 +18,6 @@ public:
|
||||||
Document& document() { return m_document; }
|
Document& document() { return m_document; }
|
||||||
const Document& document() const { return m_document; }
|
const Document& document() const { return m_document; }
|
||||||
|
|
||||||
void add_sheet(const StyleSheet& sheet) { m_sheets.append(sheet); }
|
|
||||||
|
|
||||||
NonnullRefPtr<StyledNode> create_styled_node(const Element&);
|
NonnullRefPtr<StyledNode> create_styled_node(const Element&);
|
||||||
NonnullRefPtr<StyledNode> create_styled_node(const Document&);
|
NonnullRefPtr<StyledNode> create_styled_node(const Document&);
|
||||||
|
|
||||||
|
@ -28,6 +26,4 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Document& m_document;
|
Document& m_document;
|
||||||
|
|
||||||
NonnullRefPtrVector<StyleSheet> m_sheets;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,3 +8,18 @@ StyledNode::StyledNode(const Node* node)
|
||||||
StyledNode::~StyledNode()
|
StyledNode::~StyledNode()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Display StyledNode::display() const
|
||||||
|
{
|
||||||
|
auto it = m_property_values.find("display");
|
||||||
|
if (it == m_property_values.end())
|
||||||
|
return Display::Inline;
|
||||||
|
auto value = it->value->to_string();
|
||||||
|
if (value == "none")
|
||||||
|
return Display::None;
|
||||||
|
if (value == "block")
|
||||||
|
return Display::Block;
|
||||||
|
if (value == "inline")
|
||||||
|
return Display::Inline;
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
|
|
||||||
class Node;
|
class Node;
|
||||||
|
|
||||||
|
enum class Display {
|
||||||
|
None,
|
||||||
|
Block,
|
||||||
|
Inline,
|
||||||
|
};
|
||||||
|
|
||||||
class StyledNode : public TreeNode<StyledNode> {
|
class StyledNode : public TreeNode<StyledNode> {
|
||||||
public:
|
public:
|
||||||
static NonnullRefPtr<StyledNode> create(const Node& node)
|
static NonnullRefPtr<StyledNode> create(const Node& node)
|
||||||
|
@ -44,6 +50,8 @@ public:
|
||||||
m_property_values.set(name, move(value));
|
m_property_values.set(name, move(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Display display() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit StyledNode(const Node*);
|
explicit StyledNode(const Node*);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <LibHTML/CSS/StyleResolver.h>
|
||||||
#include <LibHTML/DOM/Document.h>
|
#include <LibHTML/DOM/Document.h>
|
||||||
#include <LibHTML/DOM/Element.h>
|
#include <LibHTML/DOM/Element.h>
|
||||||
#include <LibHTML/Layout/LayoutDocument.h>
|
#include <LibHTML/Layout/LayoutDocument.h>
|
||||||
|
@ -12,31 +13,9 @@ Document::~Document()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
static void create_layout_tree_for_node(Node& node)
|
StyleResolver& Document::style_resolver()
|
||||||
{
|
{
|
||||||
if (auto layout_node = node.create_layout_node()) {
|
if (!m_style_resolver)
|
||||||
node.set_layout_node(*layout_node);
|
m_style_resolver = make<StyleResolver>(*this);
|
||||||
#ifdef DEBUG_LAYOUT_TREE_BUILD
|
return *m_style_resolver;
|
||||||
if (node.is_element()) {
|
|
||||||
printf("created layout node for <%s>, parent is %p, parent ln is %p\n", static_cast<const Element&>(node).tag_name().characters(), node.parent_node(), node.parent_node()->layout_node());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (node.parent() && node.parent()->layout_node())
|
|
||||||
node.parent()->layout_node()->append_child(*layout_node);
|
|
||||||
}
|
|
||||||
if (node.is_parent_node()) {
|
|
||||||
static_cast<ParentNode&>(node).for_each_child([&](auto& child) {
|
|
||||||
create_layout_tree_for_node(child);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Document::build_layout_tree()
|
|
||||||
{
|
|
||||||
create_layout_tree_for_node(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
RefPtr<LayoutNode> Document::create_layout_node()
|
|
||||||
{
|
|
||||||
return adopt(*new LayoutDocument(*this));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,27 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/AKString.h>
|
#include <AK/AKString.h>
|
||||||
|
#include <AK/NonnullRefPtrVector.h>
|
||||||
|
#include <AK/OwnPtr.h>
|
||||||
|
#include <LibHTML/CSS/StyleResolver.h>
|
||||||
|
#include <LibHTML/CSS/StyleSheet.h>
|
||||||
#include <LibHTML/DOM/ParentNode.h>
|
#include <LibHTML/DOM/ParentNode.h>
|
||||||
|
|
||||||
class LayoutNode;
|
class LayoutNode;
|
||||||
|
class StyleResolver;
|
||||||
|
class StyleSheet;
|
||||||
|
|
||||||
class Document : public ParentNode {
|
class Document : public ParentNode {
|
||||||
public:
|
public:
|
||||||
Document();
|
Document();
|
||||||
virtual ~Document() override;
|
virtual ~Document() override;
|
||||||
|
|
||||||
virtual RefPtr<LayoutNode> create_layout_node() override;
|
StyleResolver& style_resolver();
|
||||||
|
|
||||||
void build_layout_tree();
|
void add_sheet(const StyleSheet& sheet) { m_sheets.append(sheet); }
|
||||||
|
const NonnullRefPtrVector<StyleSheet>& stylesheets() const { return m_sheets; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
OwnPtr<StyleResolver> m_style_resolver;
|
||||||
|
NonnullRefPtrVector<StyleSheet> m_sheets;
|
||||||
};
|
};
|
||||||
|
|
|
@ -62,18 +62,3 @@ bool Element::has_class(const StringView& class_name) const
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<LayoutNode> Element::create_layout_node()
|
|
||||||
{
|
|
||||||
if (m_tag_name == "html")
|
|
||||||
return adopt(*new LayoutBlock(*this));
|
|
||||||
if (m_tag_name == "body")
|
|
||||||
return adopt(*new LayoutBlock(*this));
|
|
||||||
if (m_tag_name == "h1")
|
|
||||||
return adopt(*new LayoutBlock(*this));
|
|
||||||
if (m_tag_name == "p")
|
|
||||||
return adopt(*new LayoutBlock(*this));
|
|
||||||
if (m_tag_name == "b")
|
|
||||||
return adopt(*new LayoutInline(*this));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
|
@ -42,8 +42,6 @@ public:
|
||||||
|
|
||||||
bool has_class(const StringView&) const;
|
bool has_class(const StringView&) const;
|
||||||
|
|
||||||
virtual RefPtr<LayoutNode> create_layout_node() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Attribute* find_attribute(const String& name);
|
Attribute* find_attribute(const String& name);
|
||||||
const Attribute* find_attribute(const String& name) const;
|
const Attribute* find_attribute(const String& name) const;
|
||||||
|
|
|
@ -9,13 +9,3 @@ Node::Node(NodeType type)
|
||||||
Node::~Node()
|
Node::~Node()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<LayoutNode> Node::create_layout_node()
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::set_layout_node(NonnullRefPtr<LayoutNode> layout_node)
|
|
||||||
{
|
|
||||||
m_layout_node = move(layout_node);
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ enum class NodeType : unsigned {
|
||||||
DOCUMENT_NODE = 9,
|
DOCUMENT_NODE = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
class LayoutNode;
|
|
||||||
class ParentNode;
|
class ParentNode;
|
||||||
|
|
||||||
class Node : public TreeNode<Node> {
|
class Node : public TreeNode<Node> {
|
||||||
|
@ -25,16 +24,8 @@ public:
|
||||||
bool is_document() const { return type() == NodeType::DOCUMENT_NODE; }
|
bool is_document() const { return type() == NodeType::DOCUMENT_NODE; }
|
||||||
bool is_parent_node() const { return is_element() || is_document(); }
|
bool is_parent_node() const { return is_element() || is_document(); }
|
||||||
|
|
||||||
virtual RefPtr<LayoutNode> create_layout_node();
|
|
||||||
|
|
||||||
const LayoutNode* layout_node() const { return m_layout_node; }
|
|
||||||
LayoutNode* layout_node() { return m_layout_node; }
|
|
||||||
|
|
||||||
void set_layout_node(NonnullRefPtr<LayoutNode>);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit Node(NodeType);
|
explicit Node(NodeType);
|
||||||
|
|
||||||
NodeType m_type { NodeType::INVALID };
|
NodeType m_type { NodeType::INVALID };
|
||||||
RefPtr<LayoutNode> m_layout_node;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,8 +10,3 @@ Text::Text(const String& data)
|
||||||
Text::~Text()
|
Text::~Text()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<LayoutNode> Text::create_layout_node()
|
|
||||||
{
|
|
||||||
return adopt(*new LayoutText(*this));
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,8 +10,6 @@ public:
|
||||||
|
|
||||||
const String& data() const { return m_data; }
|
const String& data() const { return m_data; }
|
||||||
|
|
||||||
virtual RefPtr<LayoutNode> create_layout_node() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String m_data;
|
String m_data;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
|
#include <AK/Function.h>
|
||||||
|
#include <LibHTML/CSS/StyleResolver.h>
|
||||||
|
#include <LibHTML/CSS/StyledNode.h>
|
||||||
|
#include <LibHTML/DOM/Element.h>
|
||||||
|
#include <LibHTML/Dump.h>
|
||||||
#include <LibHTML/Frame.h>
|
#include <LibHTML/Frame.h>
|
||||||
#include <LibHTML/Layout/LayoutNode.h>
|
#include <LibHTML/Layout/LayoutBlock.h>
|
||||||
|
#include <LibHTML/Layout/LayoutInline.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
Frame::Frame()
|
Frame::Frame()
|
||||||
: m_size(800, 600)
|
: m_size(800, 600)
|
||||||
|
@ -15,17 +22,78 @@ void Frame::set_document(Document* document)
|
||||||
m_document = document;
|
m_document = document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefPtr<StyledNode> Frame::generate_style_tree()
|
||||||
|
{
|
||||||
|
if (!m_document)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto& resolver = m_document->style_resolver();
|
||||||
|
Function<RefPtr<StyledNode>(const Node&, StyledNode*)> resolve_style = [&](const Node& node, StyledNode* parent_styled_node) -> RefPtr<StyledNode> {
|
||||||
|
RefPtr<StyledNode> styled_node;
|
||||||
|
if (node.is_element())
|
||||||
|
styled_node = resolver.create_styled_node(static_cast<const Element&>(node));
|
||||||
|
else if (node.is_document())
|
||||||
|
styled_node = resolver.create_styled_node(static_cast<const Document&>(node));
|
||||||
|
if (!styled_node)
|
||||||
|
return nullptr;
|
||||||
|
if (parent_styled_node)
|
||||||
|
parent_styled_node->append_child(*styled_node);
|
||||||
|
static_cast<const ParentNode&>(node).for_each_child([&](const Node& child) {
|
||||||
|
if (!child.is_element())
|
||||||
|
return;
|
||||||
|
auto styled_child_node = resolve_style(static_cast<const Element&>(child), styled_node.ptr());
|
||||||
|
printf("Created StyledNode{%p} for Element{%p}\n", styled_child_node.ptr(), &node);
|
||||||
|
});
|
||||||
|
return styled_node;
|
||||||
|
};
|
||||||
|
auto styled_root = resolve_style(*m_document, nullptr);
|
||||||
|
dump_tree(*styled_root);
|
||||||
|
return styled_root;
|
||||||
|
}
|
||||||
|
|
||||||
void Frame::layout()
|
void Frame::layout()
|
||||||
{
|
{
|
||||||
if (!m_document)
|
if (!m_document)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!m_document->layout_node())
|
auto styled_root = generate_style_tree();
|
||||||
m_document->create_layout_node();
|
|
||||||
|
|
||||||
ASSERT(m_document->layout_node());
|
auto create_layout_node = [](const StyledNode& styled_node) -> RefPtr<LayoutNode> {
|
||||||
|
switch (styled_node.display()) {
|
||||||
|
case Display::None:
|
||||||
|
return nullptr;
|
||||||
|
case Display::Block:
|
||||||
|
return adopt(*new LayoutBlock(*styled_node.node()));
|
||||||
|
case Display::Inline:
|
||||||
|
return adopt(*new LayoutInline(*styled_node.node()));
|
||||||
|
default:
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
m_document->layout_node()->style().size().set_width(m_size.width());
|
Function<RefPtr<LayoutNode>(const StyledNode&, LayoutNode*)> resolve_layout = [&](const StyledNode& styled_node, LayoutNode* parent_layout_node) -> RefPtr<LayoutNode> {
|
||||||
|
auto layout_node = create_layout_node(styled_node);
|
||||||
|
if (!layout_node)
|
||||||
|
return nullptr;
|
||||||
|
if (parent_layout_node)
|
||||||
|
parent_layout_node->append_child(*layout_node);
|
||||||
|
if (styled_node.has_children()) {
|
||||||
|
for (auto* child = styled_node.first_child(); child; child = child->next_sibling()) {
|
||||||
|
resolve_layout(*child, layout_node.ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return layout_node;
|
||||||
|
};
|
||||||
|
|
||||||
m_document->layout_node()->layout();
|
auto layout_root = resolve_layout(*styled_root, nullptr);
|
||||||
|
|
||||||
|
layout_root->style().size().set_width(m_size.width());
|
||||||
|
|
||||||
|
printf("\033[33;1mLayout tree before layout:\033[0m\n");
|
||||||
|
dump_tree(*layout_root);
|
||||||
|
|
||||||
|
layout_root->layout();
|
||||||
|
|
||||||
|
printf("\033[33;1mLayout tree after layout:\033[0m\n");
|
||||||
|
dump_tree(*layout_root);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ public:
|
||||||
void layout();
|
void layout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
RefPtr<StyledNode> generate_style_tree();
|
||||||
|
|
||||||
RefPtr<Document> m_document;
|
RefPtr<Document> m_document;
|
||||||
Size m_size;
|
Size m_size;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include <LibHTML/DOM/Element.h>
|
#include <LibHTML/DOM/Element.h>
|
||||||
#include <LibHTML/Layout/LayoutBlock.h>
|
#include <LibHTML/Layout/LayoutBlock.h>
|
||||||
|
|
||||||
LayoutBlock::LayoutBlock(Element& element)
|
LayoutBlock::LayoutBlock(const Node& node)
|
||||||
: LayoutNode(&element)
|
: LayoutNode(&node)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Element;
|
||||||
|
|
||||||
class LayoutBlock : public LayoutNode {
|
class LayoutBlock : public LayoutNode {
|
||||||
public:
|
public:
|
||||||
explicit LayoutBlock(Element&);
|
explicit LayoutBlock(const Node&);
|
||||||
virtual ~LayoutBlock() override;
|
virtual ~LayoutBlock() override;
|
||||||
|
|
||||||
virtual const char* class_name() const override { return "LayoutBlock"; }
|
virtual const char* class_name() const override { return "LayoutBlock"; }
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include <LibHTML/DOM/Element.h>
|
#include <LibHTML/DOM/Element.h>
|
||||||
#include <LibHTML/Layout/LayoutInline.h>
|
#include <LibHTML/Layout/LayoutInline.h>
|
||||||
|
|
||||||
LayoutInline::LayoutInline(Element& element)
|
LayoutInline::LayoutInline(const Node& node)
|
||||||
: LayoutNode(&element)
|
: LayoutNode(&node)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Element;
|
||||||
|
|
||||||
class LayoutInline : public LayoutNode {
|
class LayoutInline : public LayoutNode {
|
||||||
public:
|
public:
|
||||||
explicit LayoutInline(Element&);
|
explicit LayoutInline(const Node&);
|
||||||
virtual ~LayoutInline() override;
|
virtual ~LayoutInline() override;
|
||||||
|
|
||||||
virtual const char* class_name() const override { return "LayoutInline"; }
|
virtual const char* class_name() const override { return "LayoutInline"; }
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
#include <LibCore/CFile.h>
|
#include <LibCore/CFile.h>
|
||||||
#include <LibHTML/Dump.h>
|
|
||||||
#include <LibHTML/Frame.h>
|
|
||||||
#include <LibHTML/Parser/CSSParser.h>
|
|
||||||
#include <LibHTML/CSS/StyleResolver.h>
|
#include <LibHTML/CSS/StyleResolver.h>
|
||||||
#include <LibHTML/CSS/StyledNode.h>
|
#include <LibHTML/CSS/StyledNode.h>
|
||||||
#include <LibHTML/DOM/Element.h>
|
#include <LibHTML/DOM/Element.h>
|
||||||
|
#include <LibHTML/Dump.h>
|
||||||
|
#include <LibHTML/Frame.h>
|
||||||
|
#include <LibHTML/Layout/LayoutBlock.h>
|
||||||
|
#include <LibHTML/Layout/LayoutInline.h>
|
||||||
|
#include <LibHTML/Parser/CSSParser.h>
|
||||||
#include <LibHTML/Parser/HTMLParser.h>
|
#include <LibHTML/Parser/HTMLParser.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
@ -23,47 +25,12 @@ int main(int argc, char** argv)
|
||||||
dump_sheet(sheet);
|
dump_sheet(sheet);
|
||||||
|
|
||||||
String html = String::copy(f.read_all());
|
String html = String::copy(f.read_all());
|
||||||
auto doc = parse_html(html);
|
auto document = parse_html(html);
|
||||||
dump_tree(doc);
|
dump_tree(document);
|
||||||
|
document->add_sheet(*sheet);
|
||||||
StyleResolver resolver(*doc);
|
|
||||||
resolver.add_sheet(*sheet);
|
|
||||||
|
|
||||||
Function<RefPtr<StyledNode>(const Node&, StyledNode*)> resolve_style = [&](const Node& node, StyledNode* parent_styled_node) -> RefPtr<StyledNode> {
|
|
||||||
auto styled_node = [&]() -> RefPtr<StyledNode> {
|
|
||||||
if (node.is_element())
|
|
||||||
return resolver.create_styled_node(static_cast<const Element&>(node));
|
|
||||||
if (node.is_document())
|
|
||||||
return resolver.create_styled_node(static_cast<const Document&>(node));
|
|
||||||
return nullptr;
|
|
||||||
}();
|
|
||||||
if (!styled_node)
|
|
||||||
return nullptr;
|
|
||||||
if (parent_styled_node)
|
|
||||||
parent_styled_node->append_child(*styled_node);
|
|
||||||
static_cast<const ParentNode&>(node).for_each_child([&](const Node& child) {
|
|
||||||
if (!child.is_element())
|
|
||||||
return;
|
|
||||||
auto styled_child_node = resolve_style(static_cast<const Element&>(child), styled_node.ptr());
|
|
||||||
printf("Created StyledNode{%p} for Element{%p}\n", styled_child_node.ptr(), &node);
|
|
||||||
});
|
|
||||||
return styled_node;
|
|
||||||
};
|
|
||||||
auto styled_root = resolve_style(*doc, nullptr);
|
|
||||||
|
|
||||||
dump_tree(*styled_root);
|
|
||||||
|
|
||||||
doc->build_layout_tree();
|
|
||||||
ASSERT(doc->layout_node());
|
|
||||||
|
|
||||||
printf("\033[33;1mLayout tree before layout:\033[0m\n");
|
|
||||||
dump_tree(*doc->layout_node());
|
|
||||||
|
|
||||||
auto frame = make<Frame>();
|
auto frame = make<Frame>();
|
||||||
frame->set_document(doc);
|
frame->set_document(document);
|
||||||
frame->layout();
|
frame->layout();
|
||||||
|
|
||||||
printf("\033[33;1mLayout tree after layout:\033[0m\n");
|
|
||||||
dump_tree(*doc->layout_node());
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue