123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- /*
- * 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);
- 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);
- Painting::CommandExecutorCPU executor { *bitmap };
- m_document->navigable()->paint(recording_painter, {});
- 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 {};
- }
- void SVGDecodedImageData::SVGPageClient::visit_edges(Visitor& visitor)
- {
- Base::visit_edges(visitor);
- visitor.visit(m_host_page);
- visitor.visit(m_svg_page);
- }
- }
|