1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 20:07:35 +00:00

LibHTML: Implement basic tiled background image support

It's now possible to set a page background image via <body background>.
Also, HtmlView now officially handles rendering the body element's
background (color, image or both.) LayoutBox is responsible for all
other background rendering.

Note that it's not yet possible to use CSS background-image properties
directly, since we can't parse them yet. :^)
This commit is contained in:
Andreas Kling 2019-10-19 11:49:46 +02:00
parent 96f10c8de2
commit 5a34225999
11 changed files with 101 additions and 7 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -6,19 +6,19 @@
<style type="text/css">
/* css comment */
body {
background-color: #fff;
color: #000; /* another css comment */
background-color: #000;
color: #fff; /* another css comment */
}
/* lol
a
css
comment */
h1 {
color: #800;
color: #a00;
}
</style>
</head>
<body>
<body link="#44f" vlink="#c4c" background="90s-bg.png">
<h1>Welcome to the Serenity Browser!</h1>
<p>This is a very simple browser built on the LibHTML engine.</p>
<p>Some small test pages:</p>

View file

@ -7,6 +7,7 @@ enum class PropertyID {
Invalid,
BackgroundColor,
BackgroundImage,
BorderBottomColor,
BorderBottomStyle,
BorderBottomWidth,

View file

@ -1,5 +1,9 @@
#include <LibDraw/GraphicsBitmap.h>
#include <LibDraw/PNGLoader.h>
#include <LibHTML/CSS/StyleValue.h>
#include <LibHTML/DOM/Document.h>
#include <LibHTML/Frame.h>
#include <LibHTML/ResourceLoader.h>
StyleValue::StyleValue(Type type)
: m_type(type)
@ -28,3 +32,20 @@ Color IdentifierStyleValue::to_color(const Document& document) const
return document.link_color();
return {};
}
ImageStyleValue::ImageStyleValue(const URL& url, Document& document)
: StyleValue(Type::Image)
, m_url(url)
, m_document(document.make_weak_ptr())
{
NonnullRefPtr<ImageStyleValue> protector(*this);
ResourceLoader::the().load(url, [this, protector](auto& data) {
if (!m_document)
return;
m_bitmap = load_png_from_memory(data.data(), data.size());
if (!m_bitmap)
return;
// FIXME: Do less than a full repaint if possible?
m_document->frame()->set_needs_display({});
});
}

View file

@ -4,7 +4,10 @@
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/URL.h>
#include <AK/WeakPtr.h>
#include <LibDraw/Color.h>
#include <LibDraw/GraphicsBitmap.h>
#include <LibHTML/CSS/Length.h>
#include <LibHTML/CSS/PropertyID.h>
@ -32,6 +35,7 @@ public:
Length,
Color,
Identifier,
Image,
};
Type type() const { return m_type; }
@ -40,6 +44,7 @@ public:
bool is_initial() const { return type() == Type::Initial; }
bool is_color() const { return type() == Type::Color; }
bool is_identifier() const { return type() == Type::Identifier; }
bool is_image() const { return type() == Type::Image; }
virtual String to_string() const = 0;
virtual Length to_length() const { return {}; }
@ -171,3 +176,20 @@ private:
CSS::ValueID m_id { CSS::ValueID::Invalid };
};
class ImageStyleValue final : public StyleValue {
public:
static NonnullRefPtr<ImageStyleValue> create(const URL& url, Document& document) { return adopt(*new ImageStyleValue(url, document)); }
virtual ~ImageStyleValue() override {}
String to_string() const override { return String::format("Image{%s}", m_url.to_string().characters()); }
const GraphicsBitmap* bitmap() const { return m_bitmap; }
private:
ImageStyleValue(const URL&, Document&);
URL m_url;
WeakPtr<Document> m_document;
RefPtr<GraphicsBitmap> m_bitmap;
};

View file

@ -119,6 +119,27 @@ Color Document::background_color() const
return background_color.value()->to_color(*this);
}
RefPtr<GraphicsBitmap> Document::background_image() const
{
auto* body_element = body();
if (!body_element)
return {};
auto* body_layout_node = body_element->layout_node();
if (!body_layout_node)
return {};
auto background_image = body_layout_node->style().property(CSS::PropertyID::BackgroundImage);
if (!background_image.has_value() || !background_image.value()->is_image())
return {};
auto& image_value = static_cast<const ImageStyleValue&>(*background_image.value());
if (!image_value.bitmap())
return {};
return *image_value.bitmap();
}
URL Document::complete_url(const String& string) const
{
URL url(string);

View file

@ -56,6 +56,7 @@ public:
const Frame* frame() const { return m_frame.ptr(); }
Color background_color() const;
RefPtr<GraphicsBitmap> background_image() const;
Color link_color() const { return m_link_color; }
void set_link_color(Color);

View file

@ -23,6 +23,8 @@ void HTMLBodyElement::apply_presentational_hints(StyleProperties& style) const
auto color = Color::from_string(value);
if (color.has_value())
style.set_property(CSS::PropertyID::Color, ColorStyleValue::create(color.value()));
} else if (name == "background") {
style.set_property(CSS::PropertyID::BackgroundImage, ImageStyleValue::create(document().complete_url(value), const_cast<Document&>(document())));
}
});
}

View file

@ -25,6 +25,10 @@ HtmlView::HtmlView(GWidget* parent)
, m_main_frame(Frame::create())
{
main_frame().on_set_needs_display = [this](auto& content_rect) {
if (content_rect.is_empty()) {
update();
return;
}
Rect adjusted_rect = content_rect;
adjusted_rect.set_location(to_widget_position(content_rect.location()));
update(adjusted_rect);
@ -118,6 +122,10 @@ void HtmlView::paint_event(GPaintEvent& event)
painter.fill_rect(event.rect(), m_document->background_color());
if (auto background_bitmap = m_document->background_image()) {
painter.draw_tiled_bitmap(event.rect(), *background_bitmap);
}
painter.translate(frame_thickness(), frame_thickness());
painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());

View file

@ -1,5 +1,6 @@
#include <LibGUI/GPainter.h>
#include <LibHTML/DOM/Document.h>
#include <LibHTML/DOM/HTMLBodyElement.h>
#include <LibHTML/Frame.h>
#include <LibHTML/Layout/LayoutBlock.h>
#include <LibHTML/Layout/LayoutBox.h>
@ -26,9 +27,19 @@ void LayoutBox::render(RenderingContext& context)
padded_rect.set_y(y() - box_model().padding().top.to_px());
padded_rect.set_height(height() + box_model().padding().top.to_px() + box_model().padding().bottom.to_px());
auto bgcolor = style().property(CSS::PropertyID::BackgroundColor);
if (bgcolor.has_value() && bgcolor.value()->is_color()) {
context.painter().fill_rect(padded_rect, bgcolor.value()->to_color(document()));
if (!is_body()) {
auto bgcolor = style().property(CSS::PropertyID::BackgroundColor);
if (bgcolor.has_value() && bgcolor.value()->is_color()) {
context.painter().fill_rect(padded_rect, bgcolor.value()->to_color(document()));
}
auto bgimage = style().property(CSS::PropertyID::BackgroundImage);
if (bgimage.has_value() && bgimage.value()->is_image()) {
auto& image_value = static_cast<const ImageStyleValue&>(*bgimage.value());
if (image_value.bitmap()) {
context.painter().draw_tiled_bitmap(padded_rect, *image_value.bitmap());
}
}
}
// FIXME: Respect all individual border sides
@ -93,3 +104,8 @@ void LayoutBox::set_needs_display()
LayoutNode::set_needs_display();
}
bool LayoutBox::is_body() const
{
return node() && node() == document().body();
}

View file

@ -18,6 +18,8 @@ public:
virtual HitTestResult hit_test(const Point& position) const override;
virtual void set_needs_display() override;
bool is_body() const;
protected:
LayoutBox(const Node* node, NonnullRefPtr<StyleProperties> style)
: LayoutNodeWithStyleAndBoxModelMetrics(node, move(style))