From a88e524fa75272bd41689d79978104356065e24b Mon Sep 17 00:00:00 2001 From: Robin Linden Date: Fri, 3 Jan 2025 14:55:41 +0100 Subject: [PATCH 1/5] gfx/sfml: Remove bonus draw call --- gfx/sfml_canvas.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gfx/sfml_canvas.cpp b/gfx/sfml_canvas.cpp index 345d6d39..6d31e6e3 100644 --- a/gfx/sfml_canvas.cpp +++ b/gfx/sfml_canvas.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022-2024 Robin Lindén +// SPDX-FileCopyrightText: 2022-2025 Robin Lindén // SPDX-FileCopyrightText: 2022 Mikael Larsson // // SPDX-License-Identifier: BSD-2-Clause @@ -226,9 +226,6 @@ void SfmlCanvas::draw_pixels(geom::Rect const &rect, std::span(rect.x), static_cast(rect.y)}); target_.draw(sprite); - sf::RectangleShape shape{{static_cast(rect.width), static_cast(rect.height)}}; - shape.setTexture(&texture); - target_.draw(shape); } } // namespace gfx From 7dc16ff37c730c6fd5a5601a4ac33cc54b159de7 Mon Sep 17 00:00:00 2001 From: Robin Linden Date: Fri, 3 Jan 2025 14:56:59 +0100 Subject: [PATCH 2/5] gfx/sfml: Fix pixel-drawing not being translated or scaled --- gfx/sfml_canvas.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gfx/sfml_canvas.cpp b/gfx/sfml_canvas.cpp index 6d31e6e3..1c5c82b6 100644 --- a/gfx/sfml_canvas.cpp +++ b/gfx/sfml_canvas.cpp @@ -213,6 +213,10 @@ void SfmlCanvas::draw_text( void SfmlCanvas::draw_pixels(geom::Rect const &rect, std::span rgba_data) { assert(rgba_data.size() == static_cast(rect.width * rect.height * 4)); + + auto translated = rect.translated(tx_, ty_); + auto scaled = translated.scaled(scale_); + sf::Image img; // Textures need to be kept around while they're displayed. This will be // cleared when the canvas is cleared. @@ -224,7 +228,8 @@ void SfmlCanvas::draw_pixels(geom::Rect const &rect, std::span(rect.x), static_cast(rect.y)}); + sprite.setPosition({static_cast(scaled.x), static_cast(scaled.y)}); + sprite.setScale({static_cast(scale_), static_cast(scale_)}); target_.draw(sprite); } From 0c373fb802c6cc8e41183415b10e018a4869654c Mon Sep 17 00:00:00 2001 From: Robin Linden Date: Fri, 3 Jan 2025 14:58:01 +0100 Subject: [PATCH 3/5] gfx/sfml: Clean up some unused code --- gfx/sfml_canvas.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gfx/sfml_canvas.cpp b/gfx/sfml_canvas.cpp index 1c5c82b6..5cad4304 100644 --- a/gfx/sfml_canvas.cpp +++ b/gfx/sfml_canvas.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -37,8 +36,6 @@ #include #include -using namespace std::literals; - namespace gfx { namespace { @@ -217,7 +214,6 @@ void SfmlCanvas::draw_pixels(geom::Rect const &rect, std::span Date: Fri, 3 Jan 2025 15:41:06 +0100 Subject: [PATCH 4/5] render: Implement image rendering --- render/render.cpp | 46 +++++++++++++++++++++++++++++++++++------- render/render.h | 20 ++++++++++++++++-- render/render_test.cpp | 42 +++++++++++++++++++++++++++++++++++++- 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/render/render.cpp b/render/render.cpp index 8736bf1b..97447ef7 100644 --- a/render/render.cpp +++ b/render/render.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2024 Robin Lindén +// SPDX-FileCopyrightText: 2021-2025 Robin Lindén // SPDX-FileCopyrightText: 2022 Mikael Larsson // // SPDX-License-Identifier: BSD-2-Clause @@ -6,6 +6,7 @@ #include "render/render.h" #include "css/property_id.h" +#include "dom/dom.h" #include "dom/xpath.h" #include "geom/geom.h" #include "gfx/color.h" @@ -19,6 +20,7 @@ #include #include #include +#include #include namespace render { @@ -109,9 +111,33 @@ void render_element(gfx::ICanvas &painter, layout::LayoutBox const &layout) { } } -void do_render(gfx::ICanvas &painter, layout::LayoutBox const &layout) { +void render_image(gfx::ICanvas &painter, layout::LayoutBox const &layout, ImageView const &image) { + // TODO(robinlinden): Handle image scaling. image.{width,height} are unused + // right now, but should be used to scale the image to work with the content + // size. + painter.draw_pixels(layout.dimensions.content, image.rgba_data); +} + +std::optional get_image_id(layout::LayoutBox const &layout) { + assert(!layout.is_anonymous_block()); + auto const *img = std::get_if(&layout.node->node); + if (img == nullptr || img->name != "img") { + return {}; + } + + auto src = img->attributes.find("src"); + if (src == img->attributes.end()) { + return {}; + } + + return src->second; +} + +void do_render(gfx::ICanvas &painter, layout::LayoutBox const &layout, ImageLookupFn const &image_lookup) { if (auto text = layout.text()) { render_text(painter, layout, *text); + } else if (auto img = get_image_id(layout).and_then(image_lookup); img.has_value()) { + render_image(painter, layout, *img); } else { render_element(painter, layout); } @@ -122,7 +148,10 @@ bool should_render(layout::LayoutBox const &layout) { } // NOLINTNEXTLINE(misc-no-recursion) -void render_layout_impl(gfx::ICanvas &painter, layout::LayoutBox const &layout, std::optional const &clip) { +void render_layout_impl(gfx::ICanvas &painter, + layout::LayoutBox const &layout, + std::optional const &clip, + ImageLookupFn const &image_lookup) { if (clip && clip->intersected(layout.dimensions.border_box()).empty()) { return; } @@ -130,17 +159,20 @@ void render_layout_impl(gfx::ICanvas &painter, layout::LayoutBox const &layout, if (should_render(layout)) { // display: none'd elements aren't part of the layout tree, so they won't appear here. assert(layout.get_property().has_value()); - do_render(painter, layout); + do_render(painter, layout, image_lookup); } for (auto const &child : layout.children) { - render_layout_impl(painter, child, clip); + render_layout_impl(painter, child, clip, image_lookup); } } } // namespace -void render_layout(gfx::ICanvas &painter, layout::LayoutBox const &layout, std::optional const &clip) { +void render_layout(gfx::ICanvas &painter, + layout::LayoutBox const &layout, + std::optional const &clip, + ImageLookupFn const &image_lookup) { static constexpr auto kGetBg = [](std::string_view xpath, layout::LayoutBox const &l) -> std::optional { auto d = dom::nodes_by_xpath(l, xpath); if (d.empty()) { @@ -161,7 +193,7 @@ void render_layout(gfx::ICanvas &painter, layout::LayoutBox const &layout, std:: painter.clear(gfx::Color{255, 255, 255}); } - render_layout_impl(painter, layout, clip); + render_layout_impl(painter, layout, clip, image_lookup); } namespace debug { diff --git a/render/render.h b/render/render.h index ee946d60..d7994f28 100644 --- a/render/render.h +++ b/render/render.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2024 Robin Lindén +// SPDX-FileCopyrightText: 2021-2025 Robin Lindén // // SPDX-License-Identifier: BSD-2-Clause @@ -9,11 +9,27 @@ #include "gfx/icanvas.h" #include "layout/layout_box.h" +#include +#include #include +#include +#include namespace render { -void render_layout(gfx::ICanvas &, layout::LayoutBox const &, std::optional const &clip = std::nullopt); +struct ImageView { + std::uint32_t width{}; + std::uint32_t height{}; + std::span rgba_data; +}; + +using ImageLookupFn = std::function(std::string_view id)>; + +void render_layout( + gfx::ICanvas &, + layout::LayoutBox const &, + std::optional const &clip = std::nullopt, + ImageLookupFn const & = [](auto) { return std::nullopt; }); namespace debug { void render_layout_depth(gfx::ICanvas &, layout::LayoutBox const &); diff --git a/render/render_test.cpp b/render/render_test.cpp index 39db78c5..2fc02c07 100644 --- a/render/render_test.cpp +++ b/render/render_test.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022-2024 Robin Lindén +// SPDX-FileCopyrightText: 2022-2025 Robin Lindén // // SPDX-License-Identifier: BSD-2-Clause @@ -14,6 +14,8 @@ #include "layout/layout_box.h" #include "style/styled_node.h" +#include +#include #include #include #include @@ -175,6 +177,44 @@ int main() { gfx::DrawRectCmd{expected_rect, expected_color, expected_borders}}); }); + s.add_test("render img", [](etest::IActions &a) { + dom::Node dom = dom::Element{"img", {{"src", "meep.png"}}}; + auto styled = style::StyledNode{.node = dom}; + auto layout = layout::LayoutBox{ + .node = &styled, + .dimensions = {{0, 0, 1, 3}}, + }; + + std::vector img{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + auto get_img_success = [&](auto) -> render::ImageView { + return {1, 3, img}; + }; + auto get_img_failure = [&](auto) -> std::optional { + return std::nullopt; + }; + + gfx::CanvasCommandSaver saver; + render::render_layout(saver, layout, {}, get_img_success); + + // Success! + a.expect_eq(saver.take_commands(), + CanvasCommands{gfx::ClearCmd{{0xFF, 0xFF, 0xFF}}, gfx::DrawPixelsCmd{{0, 0, 1, 3}, img}}); + + // Failure: image not found + render::render_layout(saver, layout, {}, get_img_failure); + a.expect_eq(saver.take_commands(), CanvasCommands{gfx::ClearCmd{{0xFF, 0xFF, 0xFF}}}); + + // // Failure: missing src + dom = dom::Element{"img"}; + render::render_layout(saver, layout, {}, get_img_success); + a.expect_eq(saver.take_commands(), CanvasCommands{gfx::ClearCmd{{0xFF, 0xFF, 0xFF}}}); + + // // Failure: not an img + dom = dom::Element{"div", {{"src", "meep.png"}}}; + render::render_layout(saver, layout, {}, get_img_success); + a.expect_eq(saver.take_commands(), CanvasCommands{gfx::ClearCmd{{0xFF, 0xFF, 0xFF}}}); + }); + s.add_test("currentcolor", [](etest::IActions &a) { dom::Node dom = dom::Element{"span", {}, {dom::Element{"span"}}}; auto const &children = std::get(dom).children; From 63cf259313ae41f91204230738573e098a3c47e9 Mon Sep 17 00:00:00 2001 From: Robin Linden Date: Fri, 3 Jan 2025 15:42:20 +0100 Subject: [PATCH 5/5] browser/gui: Hook up image lookup to rendering --- browser/gui/app.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/browser/gui/app.cpp b/browser/gui/app.cpp index 2755b976..66bb1d18 100644 --- a/browser/gui/app.cpp +++ b/browser/gui/app.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2024 Robin Lindén +// SPDX-FileCopyrightText: 2021-2025 Robin Lindén // // SPDX-License-Identifier: BSD-2-Clause @@ -832,7 +832,15 @@ void App::render_layout() { std::optional{geom::Rect{0, -scroll_offset_y_, static_cast(window_.getSize().x), - static_cast(window_.getSize().y)}}); + static_cast(window_.getSize().y)}}, + [this](std::string_view id) -> std::optional { + auto it = images_.find(id); + if (it == end(images_)) { + return std::nullopt; + } + + return render::ImageView{it->second.width, it->second.height, it->second.rgba_bytes}; + }); } }