/* * Copyright (c) 2023, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::SVG { JS_DEFINE_ALLOCATOR(SVGDecodedImageData); JS_DEFINE_ALLOCATOR(SVGDecodedImageData::SVGPageClient); ErrorOr> SVGDecodedImageData::create(JS::Realm& realm, JS::NonnullGCPtr 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 navigable = page->top_level_traversable(); auto response = Fetch::Infrastructure::Response::create(navigable->vm()); response->url_list().append(url); auto navigation_params = navigable->heap().allocate_without_realm(); navigation_params->navigable = navigable; navigation_params->response = response; navigation_params->origin = HTML::Origin {}; navigation_params->policy_container = HTML::PolicyContainer {}; navigation_params->final_sandboxing_flag_set = HTML::SandboxingFlagSet {}; navigation_params->cross_origin_opener_policy = HTML::CrossOriginOpenerPolicy {}; // 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(); 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(realm, page, page_client, document, *svg_root); } SVGDecodedImageData::SVGDecodedImageData(JS::NonnullGCPtr page, JS::NonnullGCPtr page_client, JS::NonnullGCPtr document, JS::NonnullGCPtr 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 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 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 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 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 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); } }