Procházet zdrojové kódy

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.
Matthew Olsson před 4 roky
rodič
revize
c3c2121b57

+ 72 - 20
Userland/Applications/PDFViewer/PDFViewer.cpp

@@ -14,27 +14,33 @@ PDFViewer::PDFViewer()
     set_should_hide_unnecessary_scrollbars(true);
     set_should_hide_unnecessary_scrollbars(true);
     set_focus_policy(GUI::FocusPolicy::StrongFocus);
     set_focus_policy(GUI::FocusPolicy::StrongFocus);
     set_scrollbars_enabled(true);
     set_scrollbars_enabled(true);
-}
 
 
-PDFViewer::~PDFViewer()
-{
+    start_timer(30'000);
 }
 }
 
 
 void PDFViewer::set_document(RefPtr<PDF::Document> document)
 void PDFViewer::set_document(RefPtr<PDF::Document> document)
 {
 {
     m_document = document;
     m_document = document;
     m_current_page_index = document->get_first_page_index();
     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<u32, RefPtr<Gfx::Bitmap>>());
+
     update();
     update();
 }
 }
 
 
 RefPtr<Gfx::Bitmap> PDFViewer::get_rendered_page(u32 index)
 RefPtr<Gfx::Bitmap> 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())
     if (existing_rendered_page.has_value())
         return existing_rendered_page.value();
         return existing_rendered_page.value();
 
 
     auto rendered_page = render_page(m_document->get_page(index));
     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;
     return rendered_page;
 }
 }
 
 
@@ -50,39 +56,85 @@ void PDFViewer::paint_event(GUI::PaintEvent& event)
     if (!m_document)
     if (!m_document)
         return;
         return;
 
 
-    painter.translate(frame_thickness(), frame_thickness());
-    painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
-
     auto page = get_rendered_page(m_current_page_index);
     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)
 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();
     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<Gfx::Bitmap> PDFViewer::render_page(const PDF::Page& page)
 RefPtr<Gfx::Bitmap> PDFViewer::render_page(const PDF::Page& page)
 {
 {
+    auto zoom_scale_factor = static_cast<float>(zoom_levels[m_zoom_level]) / 100.0f;
+
     float page_width = page.media_box.upper_right_x - page.media_box.lower_left_x;
     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_height = page.media_box.upper_right_y - page.media_box.lower_left_y;
     float page_scale_factor = page_height / page_width;
     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;
     float height = width * page_scale_factor;
     auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height });
     auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height });
 
 

+ 32 - 2
Userland/Applications/PDFViewer/PDFViewer.h

@@ -11,11 +11,35 @@
 #include <LibGfx/Bitmap.h>
 #include <LibGfx/Bitmap.h>
 #include <LibPDF/Document.h>
 #include <LibPDF/Document.h>
 
 
+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 {
 class PDFViewer : public GUI::AbstractScrollableWidget {
     C_OBJECT(PDFViewer)
     C_OBJECT(PDFViewer)
 
 
 public:
 public:
-    virtual ~PDFViewer() override;
+    virtual ~PDFViewer() override = default;
 
 
     void set_document(RefPtr<PDF::Document>);
     void set_document(RefPtr<PDF::Document>);
 
 
@@ -24,12 +48,18 @@ protected:
 
 
     virtual void paint_event(GUI::PaintEvent&) override;
     virtual void paint_event(GUI::PaintEvent&) override;
     virtual void mousewheel_event(GUI::MouseEvent&) override;
     virtual void mousewheel_event(GUI::MouseEvent&) override;
+    virtual void timer_event(Core::TimerEvent&) override;
 
 
 private:
 private:
     RefPtr<Gfx::Bitmap> get_rendered_page(u32 index);
     RefPtr<Gfx::Bitmap> get_rendered_page(u32 index);
     RefPtr<Gfx::Bitmap> render_page(const PDF::Page&);
     RefPtr<Gfx::Bitmap> render_page(const PDF::Page&);
 
 
+    void zoom_in();
+    void zoom_out();
+
     RefPtr<PDF::Document> m_document;
     RefPtr<PDF::Document> m_document;
     u32 m_current_page_index { 0 };
     u32 m_current_page_index { 0 };
-    HashMap<u32, RefPtr<Gfx::Bitmap>> m_rendered_pages;
+    Vector<HashMap<u32, RefPtr<Gfx::Bitmap>>> m_rendered_page_list;
+
+    u8 m_zoom_level { initial_zoom_level };
 };
 };