| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2023, Andreas Kling <kling@serenityos.org> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <LibGfx/Bitmap.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/DOM/Document.h>
 | 
					
						
							|  |  |  | #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/BrowsingContext.h>
 | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  | #include <LibWeb/HTML/DocumentState.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/HTML/NavigationParams.h>
 | 
					
						
							|  |  |  | #include <LibWeb/HTML/Parser/HTMLParser.h>
 | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  | #include <LibWeb/HTML/TraversableNavigable.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/Layout/Viewport.h>
 | 
					
						
							| 
									
										
										
										
											2023-09-19 19:16:50 +02:00
										 |  |  | #include <LibWeb/Page/Page.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/Painting/PaintContext.h>
 | 
					
						
							| 
									
										
										
										
											2023-08-19 08:38:51 +02:00
										 |  |  | #include <LibWeb/Painting/ViewportPaintable.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | #include <LibWeb/SVG/SVGDecodedImageData.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | #include <LibWeb/SVG/SVGSVGElement.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace Web::SVG { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | class SVGDecodedImageData::SVGPageClient final : public PageClient { | 
					
						
							|  |  |  | public: | 
					
						
							|  |  |  |     explicit SVGPageClient(Page& host_page) | 
					
						
							|  |  |  |         : m_host_page(host_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 {}; } | 
					
						
							| 
									
										
										
										
											2023-05-30 20:52:44 +02:00
										 |  |  |     virtual double device_pixels_per_css_pixel() const override { return 1.0; } | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     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&) override { } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ErrorOr<NonnullRefPtr<SVGDecodedImageData>> SVGDecodedImageData::create(Page& host_page, AK::URL const& url, ByteBuffer data) | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     auto page_client = make<SVGPageClient>(host_page); | 
					
						
							|  |  |  |     auto page = make<Page>(*page_client); | 
					
						
							|  |  |  |     page_client->m_svg_page = page.ptr(); | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  |     JS::NonnullGCPtr<HTML::Navigable> navigable = page->top_level_traversable(); | 
					
						
							|  |  |  |     auto response = Fetch::Infrastructure::Response::create(navigable->vm()); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     response->url_list().append(url); | 
					
						
							|  |  |  |     HTML::NavigationParams navigation_params { | 
					
						
							|  |  |  |         .id = {}, | 
					
						
							| 
									
										
										
										
											2023-09-21 13:47:19 -06:00
										 |  |  |         .navigable = navigable, | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |         .request = nullptr, | 
					
						
							|  |  |  |         .response = response, | 
					
						
							| 
									
										
										
										
											2023-09-21 13:47:19 -06:00
										 |  |  |         .fetch_controller = nullptr, | 
					
						
							|  |  |  |         .commit_early_hints = nullptr, | 
					
						
							|  |  |  |         .coop_enforcement_result = HTML::CrossOriginOpenerPolicyEnforcementResult {}, | 
					
						
							|  |  |  |         .reserved_environment = {}, | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |         .origin = HTML::Origin {}, | 
					
						
							|  |  |  |         .policy_container = HTML::PolicyContainer {}, | 
					
						
							|  |  |  |         .final_sandboxing_flag_set = HTML::SandboxingFlagSet {}, | 
					
						
							|  |  |  |         .cross_origin_opener_policy = HTML::CrossOriginOpenerPolicy {}, | 
					
						
							| 
									
										
										
										
											2023-09-21 13:47:19 -06:00
										 |  |  |         .about_base_url = {}, | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2023-09-25 05:03:41 +02:00
										 |  |  |     // FIXME: Use Navigable::navigate() instead of manually replacing the navigable's document.
 | 
					
						
							| 
									
										
										
										
											2023-04-07 02:32:56 +03:00
										 |  |  |     auto document = DOM::Document::create_and_initialize(DOM::Document::Type::HTML, "text/html", navigation_params).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  |     navigable->set_ongoing_navigation({}); | 
					
						
							| 
									
										
										
										
											2023-09-25 05:03:41 +02:00
										 |  |  |     navigable->active_document()->destroy(); | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  |     navigable->active_session_history_entry()->document_state->set_document(document); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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>(); | 
					
						
							| 
									
										
										
										
											2023-06-21 19:17:15 +03:00
										 |  |  |     if (!svg_root) | 
					
						
							|  |  |  |         return Error::from_string_literal("SVGDecodedImageData: Invalid SVG input"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     svg_root->remove(); | 
					
						
							|  |  |  |     document->remove_all_children(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     MUST(document->append_child(*svg_root)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return adopt_nonnull_ref_or_enomem(new (nothrow) SVGDecodedImageData(move(page), move(page_client), move(document), move(svg_root))); | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | SVGDecodedImageData::SVGDecodedImageData(NonnullOwnPtr<Page> page, NonnullOwnPtr<SVGPageClient> page_client, JS::Handle<DOM::Document> document, JS::Handle<SVG::SVGSVGElement> root_element) | 
					
						
							|  |  |  |     : m_page(move(page)) | 
					
						
							|  |  |  |     , m_page_client(move(page_client)) | 
					
						
							|  |  |  |     , m_document(move(document)) | 
					
						
							|  |  |  |     , m_root_element(move(root_element)) | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SVGDecodedImageData::~SVGDecodedImageData() = default; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | void SVGDecodedImageData::render(Gfx::IntSize size) const | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-06-23 03:19:34 +03:00
										 |  |  |     VERIFY(m_document->navigable()); | 
					
						
							|  |  |  |     m_document->navigable()->set_viewport_rect({ 0, 0, size.width(), size.height() }); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  |     m_document->update_layout(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-15 04:27:48 +02:00
										 |  |  |     Painting::RecordingPainter recording_painter; | 
					
						
							|  |  |  |     PaintContext context(recording_painter, m_page_client->palette(), m_page_client->device_pixels_per_css_pixel()); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-19 09:29:04 +02:00
										 |  |  |     m_document->paintable()->paint_all_phases(context); | 
					
						
							| 
									
										
										
										
											2023-10-15 04:27:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     recording_painter.execute(*m_bitmap); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:19:11 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RefPtr<Gfx::Bitmap const> SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (size.is_empty()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_bitmap && m_bitmap->size() == size) | 
					
						
							|  |  |  |         return m_bitmap; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors(); | 
					
						
							|  |  |  |     render(size); | 
					
						
							|  |  |  |     return m_bitmap; | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Optional<CSSPixels> SVGDecodedImageData::intrinsic_width() const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  |     // 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 {}; | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Optional<CSSPixels> SVGDecodedImageData::intrinsic_height() const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  |     // 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 {}; | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 17:33:58 -05:00
										 |  |  | Optional<CSSPixelFraction> SVGDecodedImageData::intrinsic_aspect_ratio() const | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  |     // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS
 | 
					
						
							|  |  |  |     auto width = intrinsic_width(); | 
					
						
							|  |  |  |     auto height = intrinsic_height(); | 
					
						
							|  |  |  |     if (width.has_value() && height.has_value()) | 
					
						
							| 
									
										
										
										
											2023-09-03 17:33:58 -05:00
										 |  |  |         return *width / *height; | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (auto const& viewbox = m_root_element->view_box(); viewbox.has_value()) | 
					
						
							| 
									
										
										
										
											2023-09-03 17:33:58 -05:00
										 |  |  |         return CSSPixels::nearest_value_for(viewbox->width) / CSSPixels::nearest_value_for(viewbox->height); | 
					
						
							| 
									
										
										
										
											2023-05-20 17:33:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return {}; | 
					
						
							| 
									
										
										
										
											2023-05-20 16:34:52 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |