Skip to content

Commit

Permalink
PDFViewer: Allow zooming in and out + scrolling
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mattco98 authored and awesomekling committed May 18, 2021
1 parent d5f94aa commit c3c2121
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 22 deletions.
92 changes: 72 additions & 20 deletions Userland/Applications/PDFViewer/PDFViewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<PDF::Document> 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<u32, RefPtr<Gfx::Bitmap>>());

update();
}

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())
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;
}

Expand All @@ -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<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_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 });

Expand Down
34 changes: 32 additions & 2 deletions Userland/Applications/PDFViewer/PDFViewer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,35 @@
#include <LibGfx/Bitmap.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 {
C_OBJECT(PDFViewer)

public:
virtual ~PDFViewer() override;
virtual ~PDFViewer() override = default;

void set_document(RefPtr<PDF::Document>);

Expand All @@ -24,12 +48,18 @@ class PDFViewer : public GUI::AbstractScrollableWidget {

virtual void paint_event(GUI::PaintEvent&) override;
virtual void mousewheel_event(GUI::MouseEvent&) override;
virtual void timer_event(Core::TimerEvent&) override;

private:
RefPtr<Gfx::Bitmap> get_rendered_page(u32 index);
RefPtr<Gfx::Bitmap> render_page(const PDF::Page&);

void zoom_in();
void zoom_out();

RefPtr<PDF::Document> m_document;
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 };
};

0 comments on commit c3c2121

Please sign in to comment.