From c3c2121b5730f6b7307a93003b04281cd980e601 Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Mon, 10 May 2021 14:00:06 -0700 Subject: PDFViewer: Allow zooming in and out + scrolling When holding ctrl and scrolling, the page will be zoomed in an out. When zoomed in on a page, scrolling with move vertically up and down the page (or horizontally if shift is being held). In order to speed up zooming, zoomed bitmaps are cached per-page at each distinct zoom level. This cache is cleared every 30 seconds to prevent OOM problems. --- Userland/Applications/PDFViewer/PDFViewer.cpp | 92 +++++++++++++++++++++------ Userland/Applications/PDFViewer/PDFViewer.h | 34 +++++++++- 2 files changed, 104 insertions(+), 22 deletions(-) diff --git a/Userland/Applications/PDFViewer/PDFViewer.cpp b/Userland/Applications/PDFViewer/PDFViewer.cpp index a69fcb4bfa..d2e018cb50 100644 --- a/Userland/Applications/PDFViewer/PDFViewer.cpp +++ b/Userland/Applications/PDFViewer/PDFViewer.cpp @@ -14,27 +14,33 @@ PDFViewer::PDFViewer() set_should_hide_unnecessary_scrollbars(true); set_focus_policy(GUI::FocusPolicy::StrongFocus); set_scrollbars_enabled(true); -} -PDFViewer::~PDFViewer() -{ + start_timer(30'000); } void PDFViewer::set_document(RefPtr document) { m_document = document; m_current_page_index = document->get_first_page_index(); + m_zoom_level = initial_zoom_level; + m_rendered_page_list.clear(); + + m_rendered_page_list.ensure_capacity(document->get_page_count()); + for (u32 i = 0; i < document->get_page_count(); i++) + m_rendered_page_list.unchecked_append(HashMap>()); + update(); } RefPtr PDFViewer::get_rendered_page(u32 index) { - auto existing_rendered_page = m_rendered_pages.get(index); + auto& rendered_page_map = m_rendered_page_list[index]; + auto existing_rendered_page = rendered_page_map.get(m_zoom_level); if (existing_rendered_page.has_value()) return existing_rendered_page.value(); auto rendered_page = render_page(m_document->get_page(index)); - m_rendered_pages.set(index, rendered_page); + rendered_page_map.set(m_zoom_level, rendered_page); return rendered_page; } @@ -50,39 +56,85 @@ void PDFViewer::paint_event(GUI::PaintEvent& event) if (!m_document) return; - painter.translate(frame_thickness(), frame_thickness()); - painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); - auto page = get_rendered_page(m_current_page_index); + set_content_size(page->size()); - auto total_width = width() - frame_thickness() * 2; - auto total_height = height() - frame_thickness() * 2; - auto bitmap_width = page->width(); - auto bitmap_height = page->height(); + painter.translate(frame_thickness(), frame_thickness()); + painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); - Gfx::IntPoint p { (total_width - bitmap_width) / 2, (total_height - bitmap_height) / 2 }; + int x = max(0, (width() - page->width()) / 2); + int y = max(0, (height() - page->height()) / 2); - painter.blit(p, *page, page->rect()); + painter.blit({ x, y }, *page, page->rect()); } void PDFViewer::mousewheel_event(GUI::MouseEvent& event) { - if (event.wheel_delta() > 0) { - if (m_current_page_index < m_document->get_page_count() - 1) - m_current_page_index++; - } else if (m_current_page_index > 0) { - m_current_page_index--; + bool scrolled_down = event.wheel_delta() > 0; + + if (event.ctrl()) { + if (scrolled_down) { + zoom_out(); + } else { + zoom_in(); + } + } else { + auto& scrollbar = event.shift() ? horizontal_scrollbar() : vertical_scrollbar(); + + if (scrolled_down) { + if (scrollbar.value() == scrollbar.max()) { + if (m_current_page_index < m_document->get_page_count() - 1) { + m_current_page_index++; + scrollbar.set_value(0); + } + } else { + scrollbar.set_value(scrollbar.value() + 20); + } + } else { + if (scrollbar.value() == 0) { + if (m_current_page_index > 0) { + m_current_page_index--; + scrollbar.set_value(scrollbar.max()); + } + } else { + scrollbar.set_value(scrollbar.value() - 20); + } + } } + update(); } +void PDFViewer::timer_event(Core::TimerEvent&) +{ + // Clear the bitmap vector of all pages except the current page + for (size_t i = 0; i < m_rendered_page_list.size(); i++) { + if (i != m_current_page_index) + m_rendered_page_list[i].clear(); + } +} + +void PDFViewer::zoom_in() +{ + if (m_zoom_level < number_of_zoom_levels - 1) + m_zoom_level++; +} + +void PDFViewer::zoom_out() +{ + if (m_zoom_level > 0) + m_zoom_level--; +} + RefPtr PDFViewer::render_page(const PDF::Page& page) { + auto zoom_scale_factor = static_cast(zoom_levels[m_zoom_level]) / 100.0f; + float page_width = page.media_box.upper_right_x - page.media_box.lower_left_x; float page_height = page.media_box.upper_right_y - page.media_box.lower_left_y; float page_scale_factor = page_height / page_width; - float width = 300.0f; + float width = 300.0f * zoom_scale_factor; float height = width * page_scale_factor; auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }); diff --git a/Userland/Applications/PDFViewer/PDFViewer.h b/Userland/Applications/PDFViewer/PDFViewer.h index 528bfa12f2..66ea4eb73c 100644 --- a/Userland/Applications/PDFViewer/PDFViewer.h +++ b/Userland/Applications/PDFViewer/PDFViewer.h @@ -11,11 +11,35 @@ #include #include +static constexpr u16 zoom_levels[] = { + 17, + 21, + 26, + 33, + 41, + 51, + 64, + 80, + 100, + 120, + 144, + 173, + 207, + 249, + 299, + 358, + 430 +}; + +static constexpr size_t number_of_zoom_levels = sizeof(zoom_levels) / sizeof(zoom_levels[0]); + +static constexpr size_t initial_zoom_level = 8; + class PDFViewer : public GUI::AbstractScrollableWidget { C_OBJECT(PDFViewer) public: - virtual ~PDFViewer() override; + virtual ~PDFViewer() override = default; void set_document(RefPtr); @@ -24,12 +48,18 @@ protected: virtual void paint_event(GUI::PaintEvent&) override; virtual void mousewheel_event(GUI::MouseEvent&) override; + virtual void timer_event(Core::TimerEvent&) override; private: RefPtr get_rendered_page(u32 index); RefPtr render_page(const PDF::Page&); + void zoom_in(); + void zoom_out(); + RefPtr m_document; u32 m_current_page_index { 0 }; - HashMap> m_rendered_pages; + Vector>> m_rendered_page_list; + + u8 m_zoom_level { initial_zoom_level }; }; -- cgit v1.2.3