2023-05-20 11:34:52 -03:00
|
|
|
/*
|
2025-08-21 10:53:37 -03:00
|
|
|
* Copyright (c) 2023-2025, Andreas Kling <andreas@ladybird.org>
|
2023-05-20 11:34:52 -03:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <LibGfx/Bitmap.h>
|
2023-12-04 05:40:33 -03:00
|
|
|
#include <LibWeb/Bindings/MainThreadVM.h>
|
2025-02-18 05:19:56 -03:00
|
|
|
#include <LibWeb/CSS/ComputedProperties.h>
|
2023-05-20 12:19:11 -03:00
|
|
|
#include <LibWeb/DOM/Document.h>
|
|
|
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
|
|
|
|
|
#include <LibWeb/HTML/BrowsingContext.h>
|
2023-06-22 21:19:34 -03:00
|
|
|
#include <LibWeb/HTML/DocumentState.h>
|
2023-05-20 12:19:11 -03:00
|
|
|
#include <LibWeb/HTML/NavigationParams.h>
|
|
|
|
|
#include <LibWeb/HTML/Parser/HTMLParser.h>
|
2023-06-22 21:19:34 -03:00
|
|
|
#include <LibWeb/HTML/TraversableNavigable.h>
|
2025-01-18 20:35:13 -03:00
|
|
|
#include <LibWeb/HTML/Window.h>
|
|
|
|
|
#include <LibWeb/HTML/WindowProxy.h>
|
2023-09-19 14:16:50 -03:00
|
|
|
#include <LibWeb/Page/Page.h>
|
2024-06-23 13:42:39 -03:00
|
|
|
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
|
2025-07-31 18:07:26 -03:00
|
|
|
#include <LibWeb/Painting/DisplayListRecordingContext.h>
|
2023-08-19 03:38:51 -03:00
|
|
|
#include <LibWeb/Painting/ViewportPaintable.h>
|
2023-05-20 11:34:52 -03:00
|
|
|
#include <LibWeb/SVG/SVGDecodedImageData.h>
|
2023-05-20 12:19:11 -03:00
|
|
|
#include <LibWeb/SVG/SVGSVGElement.h>
|
2025-08-21 10:53:37 -03:00
|
|
|
#include <LibWeb/XML/XMLDocumentBuilder.h>
|
|
|
|
|
#include <LibXML/Parser/Parser.h>
|
2023-05-20 11:34:52 -03:00
|
|
|
|
|
|
|
|
namespace Web::SVG {
|
|
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
GC_DEFINE_ALLOCATOR(SVGDecodedImageData);
|
|
|
|
|
GC_DEFINE_ALLOCATOR(SVGDecodedImageData::SVGPageClient);
|
2023-12-12 16:07:19 -03:00
|
|
|
|
2025-08-25 13:15:31 -03:00
|
|
|
ErrorOr<GC::Ref<SVGDecodedImageData>> SVGDecodedImageData::create(JS::Realm& realm, GC::Ref<Page> host_page, URL::URL const& url, ReadonlyBytes data)
|
2023-05-20 11:34:52 -03:00
|
|
|
{
|
2023-12-04 05:40:33 -03:00
|
|
|
auto page_client = SVGPageClient::create(Bindings::main_thread_vm(), host_page);
|
2023-12-04 05:57:13 -03:00
|
|
|
auto page = Page::create(Bindings::main_thread_vm(), *page_client);
|
2023-05-20 12:19:11 -03:00
|
|
|
page_client->m_svg_page = page.ptr();
|
2024-02-07 13:03:47 -03:00
|
|
|
page->set_top_level_traversable(MUST(Web::HTML::TraversableNavigable::create_a_new_top_level_traversable(*page, nullptr, {})));
|
2024-11-14 12:01:23 -03:00
|
|
|
GC::Ref<HTML::Navigable> navigable = page->top_level_traversable();
|
2023-06-22 21:19:34 -03:00
|
|
|
auto response = Fetch::Infrastructure::Response::create(navigable->vm());
|
2023-05-20 12:19:11 -03:00
|
|
|
response->url_list().append(url);
|
2025-06-15 04:08:58 -03:00
|
|
|
auto origin = URL::Origin::create_opaque();
|
2025-06-14 23:33:29 -03:00
|
|
|
auto navigation_params = navigable->heap().allocate<HTML::NavigationParams>(OptionalNone {},
|
|
|
|
|
navigable,
|
|
|
|
|
nullptr,
|
|
|
|
|
response,
|
|
|
|
|
nullptr,
|
|
|
|
|
nullptr,
|
2025-06-15 04:08:58 -03:00
|
|
|
HTML::OpenerPolicyEnforcementResult { .url = url, .origin = origin, .opener_policy = HTML::OpenerPolicy {} },
|
2025-06-14 23:33:29 -03:00
|
|
|
nullptr,
|
2025-06-15 04:08:58 -03:00
|
|
|
origin,
|
2025-06-14 23:33:29 -03:00
|
|
|
navigable->heap().allocate<HTML::PolicyContainer>(realm.heap()),
|
|
|
|
|
HTML::SandboxingFlagSet {},
|
|
|
|
|
HTML::OpenerPolicy {},
|
|
|
|
|
OptionalNone {},
|
|
|
|
|
HTML::UserNavigationInvolvement::None);
|
2024-04-21 16:57:33 -03:00
|
|
|
|
2023-09-25 00:03:41 -03:00
|
|
|
// FIXME: Use Navigable::navigate() instead of manually replacing the navigable's document.
|
2025-08-23 09:02:31 -03:00
|
|
|
auto document = MUST(DOM::Document::create_and_initialize(DOM::Document::Type::XML, "image/svg+xml"_string, navigation_params));
|
2023-06-22 21:19:34 -03:00
|
|
|
navigable->set_ongoing_navigation({});
|
2023-09-25 00:03:41 -03:00
|
|
|
navigable->active_document()->destroy();
|
2024-03-27 11:59:12 -03:00
|
|
|
navigable->active_session_history_entry()->document_state()->set_document(document);
|
2025-01-21 11:12:05 -03:00
|
|
|
auto& window = as<HTML::Window>(HTML::relevant_global_object(document));
|
2025-01-18 20:35:13 -03:00
|
|
|
document->browsing_context()->window_proxy()->set_window(window);
|
2023-05-20 12:19:11 -03:00
|
|
|
|
2025-08-21 10:53:37 -03:00
|
|
|
XML::Parser parser(data, { .resolve_external_resource = resolve_xml_resource });
|
|
|
|
|
XMLDocumentBuilder builder { document };
|
|
|
|
|
auto result = parser.parse_with_listener(builder);
|
|
|
|
|
(void)result;
|
2023-05-20 12:19:11 -03:00
|
|
|
|
2025-08-21 10:53:37 -03:00
|
|
|
auto* svg_root = document->first_child_of_type<SVG::SVGSVGElement>();
|
2023-06-21 13:17:15 -03:00
|
|
|
if (!svg_root)
|
|
|
|
|
return Error::from_string_literal("SVGDecodedImageData: Invalid SVG input");
|
|
|
|
|
|
2024-11-13 13:50:17 -03:00
|
|
|
return realm.create<SVGDecodedImageData>(page, page_client, document, *svg_root);
|
2023-05-20 11:34:52 -03:00
|
|
|
}
|
|
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
SVGDecodedImageData::SVGDecodedImageData(GC::Ref<Page> page, GC::Ref<SVGPageClient> page_client, GC::Ref<DOM::Document> document, GC::Ref<SVG::SVGSVGElement> root_element)
|
2023-12-12 16:07:19 -03:00
|
|
|
: m_page(page)
|
|
|
|
|
, m_page_client(page_client)
|
|
|
|
|
, m_document(document)
|
|
|
|
|
, m_root_element(root_element)
|
2023-05-20 11:34:52 -03:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SVGDecodedImageData::~SVGDecodedImageData() = default;
|
|
|
|
|
|
2023-12-12 16:07:19 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 10:45:45 -03:00
|
|
|
RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
|
2023-05-20 11:34:52 -03:00
|
|
|
{
|
2024-08-02 08:15:45 -03:00
|
|
|
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size).release_value_but_fixme_should_propagate_errors();
|
2023-06-22 21:19:34 -03:00
|
|
|
VERIFY(m_document->navigable());
|
2024-06-03 11:53:55 -03:00
|
|
|
m_document->navigable()->set_viewport_size(size.to_type<CSSPixels>());
|
2025-03-05 16:50:05 -03:00
|
|
|
m_document->update_layout(DOM::UpdateLayoutReason::SVGDecodedImageDataRender);
|
2023-05-20 12:19:11 -03:00
|
|
|
|
2024-08-18 21:06:52 -03:00
|
|
|
auto display_list = m_document->record_display_list({});
|
2024-08-08 08:17:54 -03:00
|
|
|
if (!display_list)
|
|
|
|
|
return {};
|
2024-06-19 09:03:04 -03:00
|
|
|
|
2024-06-23 13:42:39 -03:00
|
|
|
auto painting_command_executor_type = m_page_client->display_list_player_type();
|
2024-06-19 09:03:04 -03:00
|
|
|
switch (painting_command_executor_type) {
|
2024-07-17 08:13:32 -03:00
|
|
|
case DisplayListPlayerType::SkiaGPUIfAvailable:
|
|
|
|
|
case DisplayListPlayerType::SkiaCPU: {
|
2025-01-29 06:24:57 -03:00
|
|
|
auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(*bitmap);
|
|
|
|
|
Painting::DisplayListPlayerSkia display_list_player;
|
2025-07-24 20:39:31 -03:00
|
|
|
display_list_player.execute(*display_list, {}, painting_surface);
|
2024-06-19 09:03:04 -03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
2023-11-24 10:45:45 -03:00
|
|
|
|
|
|
|
|
return bitmap;
|
2023-05-20 12:19:11 -03:00
|
|
|
}
|
|
|
|
|
|
2023-11-24 10:45:45 -03:00
|
|
|
RefPtr<Gfx::ImmutableBitmap> SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const
|
2023-05-20 12:19:11 -03:00
|
|
|
{
|
|
|
|
|
if (size.is_empty())
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
2024-02-24 04:34:49 -03:00
|
|
|
if (auto it = m_cached_rendered_bitmaps.find(size); it != m_cached_rendered_bitmaps.end())
|
|
|
|
|
return it->value;
|
2023-05-20 12:19:11 -03:00
|
|
|
|
2024-02-24 04:34:49 -03:00
|
|
|
// 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;
|
2023-05-20 11:34:52 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Optional<CSSPixels> SVGDecodedImageData::intrinsic_width() const
|
|
|
|
|
{
|
2023-05-20 12:33:48 -03:00
|
|
|
// https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
|
|
|
|
|
m_document->update_style();
|
2024-12-20 12:35:12 -03:00
|
|
|
auto const root_element_style = m_root_element->computed_properties();
|
|
|
|
|
VERIFY(root_element_style);
|
2023-05-20 12:33:48 -03:00
|
|
|
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 {};
|
2023-05-20 11:34:52 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Optional<CSSPixels> SVGDecodedImageData::intrinsic_height() const
|
|
|
|
|
{
|
2023-05-20 12:33:48 -03:00
|
|
|
// https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
|
|
|
|
|
m_document->update_style();
|
2024-12-20 12:35:12 -03:00
|
|
|
auto const root_element_style = m_root_element->computed_properties();
|
|
|
|
|
VERIFY(root_element_style);
|
2023-05-20 12:33:48 -03:00
|
|
|
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 {};
|
2023-05-20 11:34:52 -03:00
|
|
|
}
|
|
|
|
|
|
2023-09-03 19:33:58 -03:00
|
|
|
Optional<CSSPixelFraction> SVGDecodedImageData::intrinsic_aspect_ratio() const
|
2023-05-20 11:34:52 -03:00
|
|
|
{
|
2023-05-20 12:33:48 -03:00
|
|
|
// https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
|
|
|
|
|
auto width = intrinsic_width();
|
|
|
|
|
auto height = intrinsic_height();
|
2025-07-21 00:47:55 -03:00
|
|
|
if (width.has_value() && height.has_value() && *width > 0 && *height > 0)
|
2023-09-03 19:33:58 -03:00
|
|
|
return *width / *height;
|
2023-05-20 12:33:48 -03:00
|
|
|
|
2024-05-18 23:24:52 -03:00
|
|
|
if (auto const& viewbox = m_root_element->view_box(); viewbox.has_value()) {
|
|
|
|
|
auto viewbox_width = CSSPixels::nearest_value_for(viewbox->width);
|
2023-05-20 12:33:48 -03:00
|
|
|
|
2024-05-18 23:24:52 -03:00
|
|
|
if (viewbox_width == 0)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto viewbox_height = CSSPixels::nearest_value_for(viewbox->height);
|
2024-05-19 00:34:56 -03:00
|
|
|
if (viewbox_height == 0)
|
|
|
|
|
return {};
|
2024-05-18 23:24:52 -03:00
|
|
|
|
|
|
|
|
return viewbox_width / viewbox_height;
|
|
|
|
|
}
|
2023-05-20 12:33:48 -03:00
|
|
|
return {};
|
2023-05-20 11:34:52 -03:00
|
|
|
}
|
|
|
|
|
|
2024-04-06 14:30:13 -03:00
|
|
|
void SVGDecodedImageData::SVGPageClient::visit_edges(Visitor& visitor)
|
|
|
|
|
{
|
|
|
|
|
Base::visit_edges(visitor);
|
|
|
|
|
visitor.visit(m_host_page);
|
|
|
|
|
visitor.visit(m_svg_page);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-20 11:34:52 -03:00
|
|
|
}
|