1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 20:54:58 +00:00
serenity/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp
Aliaksandr Kalenik 8ba18dfd40 LibWeb: Schedule repainting from EventLoop::process()
In this change, updating layout and painting are moved to the EventLoop
processing steps. This modification allows the addition of resize
observation dispatching that needs to happen in event loop processing
steps and must occur in the following order relative to layout and
painting:

1. Update layout.
2. Gather and broadcast resize observations.
3. Paint.
2024-02-20 10:55:10 +01:00

198 lines
7.7 KiB
C++

/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Bitmap.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/DocumentState.h>
#include <LibWeb/HTML/NavigationParams.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/CommandExecutorCPU.h>
#include <LibWeb/Painting/PaintContext.h>
#include <LibWeb/Painting/ViewportPaintable.h>
#include <LibWeb/SVG/SVGDecodedImageData.h>
#include <LibWeb/SVG/SVGSVGElement.h>
namespace Web::SVG {
JS_DEFINE_ALLOCATOR(SVGDecodedImageData);
class SVGDecodedImageData::SVGPageClient final : public PageClient {
JS_CELL(SVGDecodedImageData::SVGPageClient, PageClient);
public:
static JS::NonnullGCPtr<SVGPageClient> create(JS::VM& vm, Page& page)
{
return vm.heap().allocate_without_realm<SVGPageClient>(page);
}
virtual ~SVGPageClient() override = default;
Page& m_host_page;
Page* m_svg_page { nullptr };
virtual Page& page() override { return *m_svg_page; }
virtual Page const& page() const override { return *m_svg_page; }
virtual bool is_connection_open() const override { return false; }
virtual Gfx::Palette palette() const override { return m_host_page.client().palette(); }
virtual DevicePixelRect screen_rect() const override { return {}; }
virtual double device_pixels_per_css_pixel() const override { return 1.0; }
virtual CSS::PreferredColorScheme preferred_color_scheme() const override { return m_host_page.client().preferred_color_scheme(); }
virtual void request_file(FileRequest) override { }
virtual void paint(DevicePixelRect const&, Gfx::Bitmap&, Web::PaintOptions = {}) override { }
virtual void schedule_repaint() override { }
private:
explicit SVGPageClient(Page& host_page)
: m_host_page(host_page)
{
}
};
ErrorOr<JS::NonnullGCPtr<SVGDecodedImageData>> SVGDecodedImageData::create(JS::Realm& realm, JS::NonnullGCPtr<Page> host_page, AK::URL const& url, ByteBuffer data)
{
auto page_client = SVGPageClient::create(Bindings::main_thread_vm(), host_page);
auto page = Page::create(Bindings::main_thread_vm(), *page_client);
page_client->m_svg_page = page.ptr();
page->set_top_level_traversable(MUST(Web::HTML::TraversableNavigable::create_a_new_top_level_traversable(*page, nullptr, {})));
JS::NonnullGCPtr<HTML::Navigable> navigable = page->top_level_traversable();
auto response = Fetch::Infrastructure::Response::create(navigable->vm());
response->url_list().append(url);
HTML::NavigationParams navigation_params {
.id = {},
.navigable = navigable,
.request = nullptr,
.response = response,
.fetch_controller = nullptr,
.commit_early_hints = nullptr,
.coop_enforcement_result = HTML::CrossOriginOpenerPolicyEnforcementResult {},
.reserved_environment = {},
.origin = HTML::Origin {},
.policy_container = HTML::PolicyContainer {},
.final_sandboxing_flag_set = HTML::SandboxingFlagSet {},
.cross_origin_opener_policy = HTML::CrossOriginOpenerPolicy {},
.about_base_url = {},
};
// FIXME: Use Navigable::navigate() instead of manually replacing the navigable's document.
auto document = DOM::Document::create_and_initialize(DOM::Document::Type::HTML, "text/html"_string, navigation_params).release_value_but_fixme_should_propagate_errors();
navigable->set_ongoing_navigation({});
navigable->active_document()->destroy();
navigable->active_session_history_entry()->document_state->set_document(document);
auto parser = HTML::HTMLParser::create_with_uncertain_encoding(document, data);
parser->run(document->url());
// Perform some DOM surgery to make the SVG root element be the first child of the Document.
// FIXME: This is a huge hack until we figure out how to actually parse separate SVG files.
auto* svg_root = document->body()->first_child_of_type<SVG::SVGSVGElement>();
if (!svg_root)
return Error::from_string_literal("SVGDecodedImageData: Invalid SVG input");
svg_root->remove();
document->remove_all_children();
MUST(document->append_child(*svg_root));
return realm.heap().allocate<SVGDecodedImageData>(realm, page, page_client, document, *svg_root);
}
SVGDecodedImageData::SVGDecodedImageData(JS::NonnullGCPtr<Page> page, JS::NonnullGCPtr<SVGPageClient> page_client, JS::NonnullGCPtr<DOM::Document> document, JS::NonnullGCPtr<SVG::SVGSVGElement> root_element)
: m_page(page)
, m_page_client(page_client)
, m_document(document)
, m_root_element(root_element)
{
}
SVGDecodedImageData::~SVGDecodedImageData() = default;
void SVGDecodedImageData::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_page);
visitor.visit(m_document);
visitor.visit(m_page_client);
visitor.visit(m_root_element);
}
RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
{
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors();
VERIFY(m_document->navigable());
m_document->navigable()->set_viewport_rect({ 0, 0, size.width(), size.height() });
m_document->update_layout();
Painting::CommandList painting_commands;
Painting::RecordingPainter recording_painter(painting_commands);
PaintContext context(recording_painter, m_page_client->palette(), m_page_client->device_pixels_per_css_pixel());
m_document->paintable()->paint_all_phases(context);
Painting::CommandExecutorCPU executor { *bitmap };
painting_commands.execute(executor);
return bitmap;
}
RefPtr<Gfx::ImmutableBitmap> SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const
{
if (size.is_empty())
return nullptr;
if (m_immutable_bitmap && m_immutable_bitmap->size() == size)
return m_immutable_bitmap;
m_immutable_bitmap = Gfx::ImmutableBitmap::create(*render(size));
return m_immutable_bitmap;
}
Optional<CSSPixels> SVGDecodedImageData::intrinsic_width() const
{
// https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
m_document->update_style();
auto const* root_element_style = m_root_element->computed_css_values();
VERIFY(root_element_style);
auto const& width_value = root_element_style->size_value(CSS::PropertyID::Width);
if (width_value.is_length() && width_value.length().is_absolute())
return width_value.length().absolute_length_to_px();
return {};
}
Optional<CSSPixels> SVGDecodedImageData::intrinsic_height() const
{
// https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
m_document->update_style();
auto const* root_element_style = m_root_element->computed_css_values();
VERIFY(root_element_style);
auto const& height_value = root_element_style->size_value(CSS::PropertyID::Height);
if (height_value.is_length() && height_value.length().is_absolute())
return height_value.length().absolute_length_to_px();
return {};
}
Optional<CSSPixelFraction> SVGDecodedImageData::intrinsic_aspect_ratio() const
{
// https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
auto width = intrinsic_width();
auto height = intrinsic_height();
if (height.has_value() && *height == 0)
return {};
if (width.has_value() && height.has_value())
return *width / *height;
if (auto const& viewbox = m_root_element->view_box(); viewbox.has_value())
return CSSPixels::nearest_value_for(viewbox->width) / CSSPixels::nearest_value_for(viewbox->height);
return {};
}
}