From f7ea1eb6101617e127605173deb381bee84e72e5 Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Mon, 10 May 2021 11:50:15 -0700 Subject: [PATCH] Applications: Add a very simple PDFViewer --- Base/home/anon/.config/LaunchServer.ini | 1 + Base/res/apps/PDFViewer.af | 4 + Base/res/icons/16x16/app-pdf-viewer.png | Bin 0 -> 218 bytes Base/res/icons/32x32/app-pdf-viewer.png | Bin 0 -> 257 bytes Userland/Applications/CMakeLists.txt | 1 + .../Applications/PDFViewer/CMakeLists.txt | 8 ++ Userland/Applications/PDFViewer/PDFViewer.cpp | 91 ++++++++++++++++++ Userland/Applications/PDFViewer/PDFViewer.h | 35 +++++++ .../PDFViewer/PDFViewerWidget.cpp | 48 +++++++++ .../Applications/PDFViewer/PDFViewerWidget.h | 27 ++++++ Userland/Applications/PDFViewer/main.cpp | 34 +++++++ Userland/Libraries/LibPDF/Document.cpp | 17 ---- Userland/Libraries/LibPDF/Document.h | 16 ++- 13 files changed, 264 insertions(+), 18 deletions(-) create mode 100644 Base/res/apps/PDFViewer.af create mode 100644 Base/res/icons/16x16/app-pdf-viewer.png create mode 100644 Base/res/icons/32x32/app-pdf-viewer.png create mode 100644 Userland/Applications/PDFViewer/CMakeLists.txt create mode 100644 Userland/Applications/PDFViewer/PDFViewer.cpp create mode 100644 Userland/Applications/PDFViewer/PDFViewer.h create mode 100644 Userland/Applications/PDFViewer/PDFViewerWidget.cpp create mode 100644 Userland/Applications/PDFViewer/PDFViewerWidget.h create mode 100644 Userland/Applications/PDFViewer/main.cpp diff --git a/Base/home/anon/.config/LaunchServer.ini b/Base/home/anon/.config/LaunchServer.ini index 87acdb03ac..afa150f254 100644 --- a/Base/home/anon/.config/LaunchServer.ini +++ b/Base/home/anon/.config/LaunchServer.ini @@ -16,6 +16,7 @@ txt=/bin/TextEditor font=/bin/FontEditor sheets=/bin/Spreadsheet gml=/bin/Playground +pdf=/bin/PDFViewer *=/bin/TextEditor [Protocol] diff --git a/Base/res/apps/PDFViewer.af b/Base/res/apps/PDFViewer.af new file mode 100644 index 0000000000..03e79885a3 --- /dev/null +++ b/Base/res/apps/PDFViewer.af @@ -0,0 +1,4 @@ +[App] +Name=PDFViewer +Executable=/bin/PDFViewer +Category=Graphics diff --git a/Base/res/icons/16x16/app-pdf-viewer.png b/Base/res/icons/16x16/app-pdf-viewer.png new file mode 100644 index 0000000000000000000000000000000000000000..830412e82c3a387c3354d75337070b3c3056e524 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd7G?$phPQVgfdm+fgWR1MZ=TetXJBC9Ebxdd zW?zmvv4F FO#n7eMqZFmM)l zL>4nJa0`PlBg3pY5)2FsLIFM@t_)z%(9rPz|NjFA4m^-&dSSEHih+TFtt7}VnBhMN zR8>Cs$H2f)=IP=XQZXkv;Q*TiE6YL-tp#gRSX`MB>?SYVXdq&_h;v3q!&C#w!wg$l zBAh2WaD+7`BtO28kQ}UFX4c9%!-*x4X_*1X + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "PDFViewer.h" +#include +#include +#include + +PDFViewer::PDFViewer() +{ + set_should_hide_unnecessary_scrollbars(true); + set_focus_policy(GUI::FocusPolicy::StrongFocus); + set_scrollbars_enabled(true); +} + +PDFViewer::~PDFViewer() +{ +} + +void PDFViewer::set_document(RefPtr document) +{ + m_document = document; + m_current_page_index = document->get_first_page_index(); + update(); +} + +RefPtr PDFViewer::get_rendered_page(u32 index) +{ + auto existing_rendered_page = m_rendered_pages.get(index); + 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); + return rendered_page; +} + +void PDFViewer::paint_event(GUI::PaintEvent& event) +{ + GUI::Frame::paint_event(event); + + GUI::Painter painter(*this); + painter.add_clip_rect(widget_inner_rect()); + painter.add_clip_rect(event.rect()); + painter.fill_rect(event.rect(), Color(0x80, 0x80, 0x80)); + + 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); + + auto total_width = width() - frame_thickness() * 2; + auto total_height = height() - frame_thickness() * 2; + auto bitmap_width = page->width(); + auto bitmap_height = page->height(); + + Gfx::IntPoint p { (total_width - bitmap_width) / 2, (total_height - bitmap_height) / 2 }; + + painter.blit(p, *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--; + } + update(); +} + +RefPtr PDFViewer::render_page(const PDF::Page& page) +{ + 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 height = width * page_scale_factor; + auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }); + + PDF::Renderer::render(*m_document, page, bitmap); + return bitmap; +} diff --git a/Userland/Applications/PDFViewer/PDFViewer.h b/Userland/Applications/PDFViewer/PDFViewer.h new file mode 100644 index 0000000000..528bfa12f2 --- /dev/null +++ b/Userland/Applications/PDFViewer/PDFViewer.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +class PDFViewer : public GUI::AbstractScrollableWidget { + C_OBJECT(PDFViewer) + +public: + virtual ~PDFViewer() override; + + void set_document(RefPtr); + +protected: + PDFViewer(); + + virtual void paint_event(GUI::PaintEvent&) override; + virtual void mousewheel_event(GUI::MouseEvent&) override; + +private: + RefPtr get_rendered_page(u32 index); + RefPtr render_page(const PDF::Page&); + + RefPtr m_document; + u32 m_current_page_index { 0 }; + HashMap> m_rendered_pages; +}; diff --git a/Userland/Applications/PDFViewer/PDFViewerWidget.cpp b/Userland/Applications/PDFViewer/PDFViewerWidget.cpp new file mode 100644 index 0000000000..6eb0e52b42 --- /dev/null +++ b/Userland/Applications/PDFViewer/PDFViewerWidget.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "PDFViewerWidget.h" +#include +#include +#include +#include +#include +#include +#include + +PDFViewerWidget::PDFViewerWidget() +{ + set_fill_with_background_color(true); + set_layout(); + + m_viewer = add(); +} + +PDFViewerWidget::~PDFViewerWidget() +{ +} + +void PDFViewerWidget::initialize_menubar(GUI::Menubar& menubar) +{ + auto& file_menu = menubar.add_menu("&File"); + file_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) { + Optional open_path = GUI::FilePicker::get_open_filepath(window()); + if (open_path.has_value()) + open_file(open_path.value()); + })); + file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) { + GUI::Application::the()->quit(); + })); +} + +void PDFViewerWidget::open_file(const String& path) +{ + window()->set_title(String::formatted("{} - PDFViewer", path)); + auto file_result = Core::File::open(path, Core::OpenMode::ReadOnly); + VERIFY(!file_result.is_error()); + m_buffer = file_result.value()->read_all(); + m_viewer->set_document(adopt_ref(*new PDF::Document(m_buffer))); +} diff --git a/Userland/Applications/PDFViewer/PDFViewerWidget.h b/Userland/Applications/PDFViewer/PDFViewerWidget.h new file mode 100644 index 0000000000..8f897d9720 --- /dev/null +++ b/Userland/Applications/PDFViewer/PDFViewerWidget.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "PDFViewer.h" +#include + +class PDFViewer; + +class PDFViewerWidget final : public GUI::Widget { + C_OBJECT(PDFViewerWidget) +public: + ~PDFViewerWidget() override; + void open_file(const String& path); + void initialize_menubar(GUI::Menubar&); + +private: + PDFViewerWidget(); + + RefPtr m_viewer; + ByteBuffer m_buffer; + RefPtr m_open_action; +}; diff --git a/Userland/Applications/PDFViewer/main.cpp b/Userland/Applications/PDFViewer/main.cpp new file mode 100644 index 0000000000..0f5e0ed38d --- /dev/null +++ b/Userland/Applications/PDFViewer/main.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "PDFViewerWidget.h" +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + auto app = GUI::Application::construct(argc, argv); + auto app_icon = GUI::Icon::default_icon("app-pdf-viewer"); + + auto window = GUI::Window::construct(); + window->set_title("PDFViewer"); + window->resize(640, 400); + + auto& pdf_viewer_widget = window->set_main_widget(); + + auto menubar = GUI::Menubar::construct(); + pdf_viewer_widget.initialize_menubar(menubar); + window->set_menubar(menubar); + window->show(); + window->set_icon(app_icon.bitmap_for_size(16)); + + if (argc >= 2) + pdf_viewer_widget.open_file(argv[1]); + + return app->exec(); +} diff --git a/Userland/Libraries/LibPDF/Document.cpp b/Userland/Libraries/LibPDF/Document.cpp index 627593fa73..b5ff2df818 100644 --- a/Userland/Libraries/LibPDF/Document.cpp +++ b/Userland/Libraries/LibPDF/Document.cpp @@ -115,23 +115,6 @@ Value Document::resolve(const Value& value) return obj; } -template -UnwrappedValueType Document::resolve_to(const Value& value) -{ - auto resolved = resolve(value); - - if constexpr (IsSame) - return resolved.as_bool(); - if constexpr (IsSame) - return resolved.as_int(); - if constexpr (IsSame) - return resolved.as_float(); - if constexpr (IsObject) - return object_cast(resolved.as_object()); - - VERIFY_NOT_REACHED(); -} - void Document::build_page_tree() { auto page_tree = m_catalog->get_dict(this, "Pages"); diff --git a/Userland/Libraries/LibPDF/Document.h b/Userland/Libraries/LibPDF/Document.h index 5adc24a3ec..14463d11d1 100644 --- a/Userland/Libraries/LibPDF/Document.h +++ b/Userland/Libraries/LibPDF/Document.h @@ -65,7 +65,21 @@ public: // Like resolve, but unwraps the Value into the given type. Accepts // any object type, and the three primitive Value types. template - UnwrappedValueType resolve_to(const Value& value); + UnwrappedValueType resolve_to(const Value& value) + { + auto resolved = resolve(value); + + if constexpr (IsSame) + return resolved.as_bool(); + if constexpr (IsSame) + return resolved.as_int(); + if constexpr (IsSame) + return resolved.as_float(); + if constexpr (IsObject) + return object_cast(resolved.as_object()); + + VERIFY_NOT_REACHED(); + } private: // FIXME: Currently, to improve performance, we don't load any pages at Document