/src/serenity/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibGfx/Bitmap.h> |
8 | | #include <LibWeb/Bindings/MainThreadVM.h> |
9 | | #include <LibWeb/DOM/Document.h> |
10 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h> |
11 | | #include <LibWeb/HTML/BrowsingContext.h> |
12 | | #include <LibWeb/HTML/DocumentState.h> |
13 | | #include <LibWeb/HTML/NavigationParams.h> |
14 | | #include <LibWeb/HTML/Parser/HTMLParser.h> |
15 | | #include <LibWeb/HTML/TraversableNavigable.h> |
16 | | #include <LibWeb/Layout/Viewport.h> |
17 | | #include <LibWeb/Page/Page.h> |
18 | | #include <LibWeb/Painting/DisplayListPlayerCPU.h> |
19 | | #include <LibWeb/Painting/PaintContext.h> |
20 | | #include <LibWeb/Painting/ViewportPaintable.h> |
21 | | #include <LibWeb/SVG/SVGDecodedImageData.h> |
22 | | #include <LibWeb/SVG/SVGSVGElement.h> |
23 | | |
24 | | namespace Web::SVG { |
25 | | |
26 | | JS_DEFINE_ALLOCATOR(SVGDecodedImageData); |
27 | | JS_DEFINE_ALLOCATOR(SVGDecodedImageData::SVGPageClient); |
28 | | |
29 | | ErrorOr<JS::NonnullGCPtr<SVGDecodedImageData>> SVGDecodedImageData::create(JS::Realm& realm, JS::NonnullGCPtr<Page> host_page, URL::URL const& url, ByteBuffer data) |
30 | 0 | { |
31 | 0 | auto page_client = SVGPageClient::create(Bindings::main_thread_vm(), host_page); |
32 | 0 | auto page = Page::create(Bindings::main_thread_vm(), *page_client); |
33 | 0 | page_client->m_svg_page = page.ptr(); |
34 | 0 | page->set_top_level_traversable(MUST(Web::HTML::TraversableNavigable::create_a_new_top_level_traversable(*page, nullptr, {}))); |
35 | 0 | JS::NonnullGCPtr<HTML::Navigable> navigable = page->top_level_traversable(); |
36 | 0 | auto response = Fetch::Infrastructure::Response::create(navigable->vm()); |
37 | 0 | response->url_list().append(url); |
38 | 0 | auto navigation_params = navigable->heap().allocate_without_realm<HTML::NavigationParams>(); |
39 | 0 | navigation_params->navigable = navigable; |
40 | 0 | navigation_params->response = response; |
41 | 0 | navigation_params->origin = URL::Origin {}; |
42 | 0 | navigation_params->policy_container = HTML::PolicyContainer {}; |
43 | 0 | navigation_params->final_sandboxing_flag_set = HTML::SandboxingFlagSet {}; |
44 | 0 | navigation_params->opener_policy = HTML::OpenerPolicy {}; |
45 | | |
46 | | // FIXME: Use Navigable::navigate() instead of manually replacing the navigable's document. |
47 | 0 | auto document = DOM::Document::create_and_initialize(DOM::Document::Type::HTML, "text/html"_string, navigation_params).release_value_but_fixme_should_propagate_errors(); |
48 | 0 | navigable->set_ongoing_navigation({}); |
49 | 0 | navigable->active_document()->destroy(); |
50 | 0 | navigable->active_session_history_entry()->document_state()->set_document(document); |
51 | |
|
52 | 0 | auto parser = HTML::HTMLParser::create_with_uncertain_encoding(document, data); |
53 | 0 | parser->run(document->url()); |
54 | | |
55 | | // Perform some DOM surgery to make the SVG root element be the first child of the Document. |
56 | | // FIXME: This is a huge hack until we figure out how to actually parse separate SVG files. |
57 | 0 | auto* svg_root = document->body()->first_child_of_type<SVG::SVGSVGElement>(); |
58 | 0 | if (!svg_root) |
59 | 0 | return Error::from_string_literal("SVGDecodedImageData: Invalid SVG input"); |
60 | | |
61 | 0 | svg_root->remove(); |
62 | 0 | document->remove_all_children(); |
63 | |
|
64 | 0 | MUST(document->append_child(*svg_root)); |
65 | |
|
66 | 0 | return realm.heap().allocate<SVGDecodedImageData>(realm, page, page_client, document, *svg_root); |
67 | 0 | } |
68 | | |
69 | | SVGDecodedImageData::SVGDecodedImageData(JS::NonnullGCPtr<Page> page, JS::NonnullGCPtr<SVGPageClient> page_client, JS::NonnullGCPtr<DOM::Document> document, JS::NonnullGCPtr<SVG::SVGSVGElement> root_element) |
70 | 0 | : m_page(page) |
71 | 0 | , m_page_client(page_client) |
72 | 0 | , m_document(document) |
73 | 0 | , m_root_element(root_element) |
74 | 0 | { |
75 | 0 | } |
76 | | |
77 | 0 | SVGDecodedImageData::~SVGDecodedImageData() = default; |
78 | | |
79 | | void SVGDecodedImageData::visit_edges(Cell::Visitor& visitor) |
80 | 0 | { |
81 | 0 | Base::visit_edges(visitor); |
82 | 0 | visitor.visit(m_page); |
83 | 0 | visitor.visit(m_document); |
84 | 0 | visitor.visit(m_page_client); |
85 | 0 | visitor.visit(m_root_element); |
86 | 0 | } |
87 | | |
88 | | RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const |
89 | 0 | { |
90 | 0 | auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors(); |
91 | 0 | VERIFY(m_document->navigable()); |
92 | 0 | m_document->navigable()->set_viewport_size(size.to_type<CSSPixels>()); |
93 | 0 | m_document->update_layout(); |
94 | |
|
95 | 0 | auto display_list = Painting::DisplayList::create(); |
96 | 0 | Painting::DisplayListRecorder display_list_recorder(display_list); |
97 | |
|
98 | 0 | m_document->navigable()->record_display_list(display_list_recorder, {}); |
99 | |
|
100 | 0 | auto painting_command_executor_type = m_page_client->display_list_player_type(); |
101 | 0 | switch (painting_command_executor_type) { |
102 | 0 | case DisplayListPlayerType::CPU: |
103 | 0 | case DisplayListPlayerType::CPUWithExperimentalTransformSupport: |
104 | 0 | case DisplayListPlayerType::GPU: { // GPU painter does not have any path rasterization support so we always fall back to CPU painter |
105 | 0 | Painting::DisplayListPlayerCPU display_list_player { *bitmap }; |
106 | 0 | display_list_player.execute(display_list); |
107 | 0 | break; |
108 | 0 | } |
109 | 0 | default: |
110 | 0 | VERIFY_NOT_REACHED(); |
111 | 0 | } |
112 | | |
113 | 0 | return bitmap; |
114 | 0 | } |
115 | | |
116 | | RefPtr<Gfx::ImmutableBitmap> SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const |
117 | 0 | { |
118 | 0 | if (size.is_empty()) |
119 | 0 | return nullptr; |
120 | | |
121 | 0 | if (auto it = m_cached_rendered_bitmaps.find(size); it != m_cached_rendered_bitmaps.end()) |
122 | 0 | return it->value; |
123 | | |
124 | | // Prevent the cache from growing too big. |
125 | | // FIXME: Evict least used entries. |
126 | 0 | if (m_cached_rendered_bitmaps.size() > 10) |
127 | 0 | m_cached_rendered_bitmaps.remove(m_cached_rendered_bitmaps.begin()); |
128 | |
|
129 | 0 | auto immutable_bitmap = Gfx::ImmutableBitmap::create(*render(size)); |
130 | 0 | m_cached_rendered_bitmaps.set(size, immutable_bitmap); |
131 | 0 | return immutable_bitmap; |
132 | 0 | } |
133 | | |
134 | | Optional<CSSPixels> SVGDecodedImageData::intrinsic_width() const |
135 | 0 | { |
136 | | // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS |
137 | 0 | m_document->update_style(); |
138 | 0 | auto const* root_element_style = m_root_element->computed_css_values(); |
139 | 0 | VERIFY(root_element_style); |
140 | 0 | auto const& width_value = root_element_style->size_value(CSS::PropertyID::Width); |
141 | 0 | if (width_value.is_length() && width_value.length().is_absolute()) |
142 | 0 | return width_value.length().absolute_length_to_px(); |
143 | 0 | return {}; |
144 | 0 | } |
145 | | |
146 | | Optional<CSSPixels> SVGDecodedImageData::intrinsic_height() const |
147 | 0 | { |
148 | | // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS |
149 | 0 | m_document->update_style(); |
150 | 0 | auto const* root_element_style = m_root_element->computed_css_values(); |
151 | 0 | VERIFY(root_element_style); |
152 | 0 | auto const& height_value = root_element_style->size_value(CSS::PropertyID::Height); |
153 | 0 | if (height_value.is_length() && height_value.length().is_absolute()) |
154 | 0 | return height_value.length().absolute_length_to_px(); |
155 | 0 | return {}; |
156 | 0 | } |
157 | | |
158 | | Optional<CSSPixelFraction> SVGDecodedImageData::intrinsic_aspect_ratio() const |
159 | 0 | { |
160 | | // https://www.w3.org/TR/SVG2/coords.html#SizingSVGInCSS |
161 | 0 | auto width = intrinsic_width(); |
162 | 0 | auto height = intrinsic_height(); |
163 | 0 | if (height.has_value() && *height == 0) |
164 | 0 | return {}; |
165 | | |
166 | 0 | if (width.has_value() && height.has_value()) |
167 | 0 | return *width / *height; |
168 | | |
169 | 0 | if (auto const& viewbox = m_root_element->view_box(); viewbox.has_value()) { |
170 | 0 | auto viewbox_width = CSSPixels::nearest_value_for(viewbox->width); |
171 | |
|
172 | 0 | if (viewbox_width == 0) |
173 | 0 | return {}; |
174 | | |
175 | 0 | auto viewbox_height = CSSPixels::nearest_value_for(viewbox->height); |
176 | 0 | if (viewbox_height == 0) |
177 | 0 | return {}; |
178 | | |
179 | 0 | return viewbox_width / viewbox_height; |
180 | 0 | } |
181 | 0 | return {}; |
182 | 0 | } |
183 | | |
184 | | void SVGDecodedImageData::SVGPageClient::visit_edges(Visitor& visitor) |
185 | 0 | { |
186 | 0 | Base::visit_edges(visitor); |
187 | 0 | visitor.visit(m_host_page); |
188 | 0 | visitor.visit(m_svg_page); |
189 | 0 | } |
190 | | |
191 | | } |