
Since we drive painting for SVG-as-image manually anyway, there's no need for them to say they are "ready to paint", since that just causes unnecessary extra processing in the HTML event loop.
205 lines
8.1 KiB
C++
205 lines
8.1 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 { }
|
|
virtual bool is_ready_to_paint() const override { return false; }
|
|
|
|
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, URL::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 (auto it = m_cached_rendered_bitmaps.find(size); it != m_cached_rendered_bitmaps.end())
|
|
return it->value;
|
|
|
|
// Prevent the cache from growing too big.
|
|
// FIXME: Evict least used entries.
|
|
if (m_cached_rendered_bitmaps.size() > 10)
|
|
m_cached_rendered_bitmaps.remove(m_cached_rendered_bitmaps.begin());
|
|
|
|
auto immutable_bitmap = Gfx::ImmutableBitmap::create(*render(size));
|
|
m_cached_rendered_bitmaps.set(size, immutable_bitmap);
|
|
return 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 {};
|
|
}
|
|
|
|
}
|