Serenity Operating System
at master 398 lines 12 kB view raw
1/* 2 * Copyright (c) 2021-2022, Matthew Olsson <mattco@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "PDFViewer.h" 9#include <AK/Array.h> 10#include <AK/BinarySearch.h> 11#include <AK/HashFunctions.h> 12#include <LibConfig/Client.h> 13#include <LibGUI/Action.h> 14#include <LibGUI/MessageBox.h> 15#include <LibGUI/Painter.h> 16#include <LibPDF/Renderer.h> 17 18static constexpr int PAGE_PADDING = 10; 19 20static constexpr Array zoom_levels = { 21 17, 22 21, 23 26, 24 33, 25 41, 26 51, 27 64, 28 80, 29 100, 30 120, 31 144, 32 173, 33 207, 34 249, 35 299, 36 358, 37 430 38}; 39 40PDFViewer::PDFViewer() 41{ 42 set_should_hide_unnecessary_scrollbars(true); 43 set_focus_policy(GUI::FocusPolicy::StrongFocus); 44 set_scrollbars_enabled(true); 45 46 start_timer(30'000); 47 48 m_page_view_mode = static_cast<PageViewMode>(Config::read_i32("PDFViewer"sv, "Display"sv, "PageMode"sv, 0)); 49 m_rendering_preferences.show_clipping_paths = Config::read_bool("PDFViewer"sv, "Rendering"sv, "ShowClippingPaths"sv, false); 50 m_rendering_preferences.show_images = Config::read_bool("PDFViewer"sv, "Rendering"sv, "ShowImages"sv, true); 51} 52 53PDF::PDFErrorOr<void> PDFViewer::set_document(RefPtr<PDF::Document> document) 54{ 55 m_document = document; 56 m_current_page_index = document->get_first_page_index(); 57 m_zoom_level = initial_zoom_level; 58 m_rendered_page_list.clear(); 59 60 m_rendered_page_list.ensure_capacity(document->get_page_count()); 61 for (u32 i = 0; i < document->get_page_count(); i++) 62 m_rendered_page_list.unchecked_append(HashMap<u32, RenderedPage>()); 63 64 TRY(cache_page_dimensions(true)); 65 update(); 66 67 return {}; 68} 69 70PDF::PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> PDFViewer::get_rendered_page(u32 index) 71{ 72 auto key = pair_int_hash(m_rendering_preferences.hash(), m_zoom_level); 73 auto& rendered_page_map = m_rendered_page_list[index]; 74 auto existing_rendered_page = rendered_page_map.get(key); 75 if (existing_rendered_page.has_value() && existing_rendered_page.value().rotation == m_rotations) 76 return existing_rendered_page.value().bitmap; 77 78 auto rendered_page = TRY(render_page(index)); 79 rendered_page_map.set(key, { rendered_page, m_rotations }); 80 return rendered_page; 81} 82 83void PDFViewer::paint_event(GUI::PaintEvent& event) 84{ 85 GUI::Frame::paint_event(event); 86 87 GUI::Painter painter(*this); 88 painter.add_clip_rect(widget_inner_rect()); 89 painter.add_clip_rect(event.rect()); 90 painter.fill_rect(event.rect(), Color(0x80, 0x80, 0x80)); 91 92 if (!m_document) 93 return; 94 95 auto handle_error = [&](PDF::Error& error) { 96 warnln("{}", error.message()); 97 GUI::MessageBox::show_error(nullptr, "Failed to render the page."sv); 98 m_document.clear(); 99 }; 100 101 if (m_page_view_mode == PageViewMode::Single) { 102 auto maybe_page = get_rendered_page(m_current_page_index); 103 if (maybe_page.is_error()) { 104 handle_error(maybe_page.error()); 105 return; 106 } 107 108 auto page = maybe_page.release_value(); 109 set_content_size(page->size()); 110 111 painter.translate(frame_thickness(), frame_thickness()); 112 painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); 113 114 int x = max(0, (width() - page->width()) / 2); 115 int y = max(0, (height() - page->height()) / 2); 116 117 painter.blit({ x, y }, *page, page->rect()); 118 return; 119 } 120 121 set_content_size({ m_page_dimension_cache.max_width, m_page_dimension_cache.total_height }); 122 123 size_t first_page_index = 0; 124 size_t last_page_index = 0; 125 126 binary_search(m_page_dimension_cache.render_info, vertical_scrollbar().value(), &first_page_index, [](int height, PageDimensionCache::RenderInfo const& render_info) { 127 return height - render_info.total_height_before_this_page; 128 }); 129 130 binary_search(m_page_dimension_cache.render_info, vertical_scrollbar().value() + height(), &last_page_index, [](int height, PageDimensionCache::RenderInfo const& render_info) { 131 return height - render_info.total_height_before_this_page; 132 }); 133 134 auto initial_offset = m_page_dimension_cache.render_info[first_page_index].total_height_before_this_page - vertical_scrollbar().value(); 135 136 painter.translate(frame_thickness(), frame_thickness()); 137 painter.translate(-horizontal_scrollbar().value(), initial_offset); 138 auto middle = height() / 2; 139 auto y_offset = initial_offset; 140 141 for (size_t page_index = first_page_index; page_index <= last_page_index; page_index++) { 142 auto maybe_page = get_rendered_page(page_index); 143 if (maybe_page.is_error()) { 144 handle_error(maybe_page.error()); 145 return; 146 } 147 148 auto page = maybe_page.release_value(); 149 150 auto x = max(0, (width() - page->width()) / 2); 151 152 painter.blit({ x, PAGE_PADDING }, *page, page->rect()); 153 auto diff_y = page->height() + PAGE_PADDING * 2; 154 painter.translate(0, diff_y); 155 156 if (y_offset < middle && y_offset + diff_y >= middle) 157 change_page(page_index); 158 159 y_offset += diff_y; 160 } 161} 162 163void PDFViewer::set_current_page(u32 current_page) 164{ 165 m_current_page_index = current_page; 166 vertical_scrollbar().set_value(m_page_dimension_cache.render_info[current_page].total_height_before_this_page); 167 update(); 168} 169 170void PDFViewer::set_show_clipping_paths(bool show_clipping_paths) 171{ 172 m_rendering_preferences.show_clipping_paths = show_clipping_paths; 173 Config::write_bool("PDFViewer"sv, "Rendering"sv, "ShowClippingPaths"sv, show_clipping_paths); 174 update(); 175} 176 177void PDFViewer::set_show_images(bool show_images) 178{ 179 m_rendering_preferences.show_images = show_images; 180 Config::write_bool("PDFViewer"sv, "Rendering"sv, "ShowImages"sv, show_images); 181 update(); 182} 183 184void PDFViewer::resize_event(GUI::ResizeEvent&) 185{ 186 for (auto& map : m_rendered_page_list) 187 map.clear(); 188 if (m_document) 189 MUST(cache_page_dimensions()); 190 update(); 191} 192 193void PDFViewer::mousewheel_event(GUI::MouseEvent& event) 194{ 195 if (!m_document) 196 return; 197 198 bool scrolled_down = event.wheel_delta_y() > 0; 199 200 if (event.ctrl()) { 201 if (scrolled_down) { 202 zoom_out(); 203 } else { 204 zoom_in(); 205 } 206 return; 207 } 208 209 auto& scrollbar = event.shift() ? horizontal_scrollbar() : vertical_scrollbar(); 210 auto delta = abs(event.wheel_delta_y() * 20); 211 212 if (m_page_view_mode == PageViewMode::Multiple) { 213 if (scrolled_down) { 214 if (scrollbar.value() != scrollbar.max()) 215 scrollbar.increase_slider_by(delta); 216 } else { 217 if (scrollbar.value() > 0) 218 scrollbar.decrease_slider_by(delta); 219 } 220 } else { 221 if (scrolled_down) { 222 if (scrollbar.value() == scrollbar.max()) { 223 if (m_current_page_index < m_document->get_page_count() - 1) { 224 change_page(m_current_page_index + 1); 225 scrollbar.set_value(0); 226 } 227 } else { 228 scrollbar.increase_slider_by(delta); 229 } 230 } else { 231 if (scrollbar.value() == 0) { 232 if (m_current_page_index > 0) { 233 change_page(m_current_page_index - 1); 234 scrollbar.set_value(scrollbar.max()); 235 } 236 } else { 237 scrollbar.decrease_slider_by(delta); 238 } 239 } 240 } 241 242 update(); 243} 244 245void PDFViewer::mousedown_event(GUI::MouseEvent& event) 246{ 247 if (event.button() == GUI::MouseButton::Middle) { 248 m_pan_starting_position = to_content_position(event.position()); 249 set_override_cursor(Gfx::StandardCursor::Drag); 250 } 251} 252 253void PDFViewer::mouseup_event(GUI::MouseEvent&) 254{ 255 set_override_cursor(Gfx::StandardCursor::None); 256} 257 258void PDFViewer::mousemove_event(GUI::MouseEvent& event) 259{ 260 if (event.buttons() & GUI::MouseButton::Middle) { 261 auto delta = to_content_position(event.position()) - m_pan_starting_position; 262 horizontal_scrollbar().decrease_slider_by(delta.x()); 263 vertical_scrollbar().decrease_slider_by(delta.y()); 264 update(); 265 } 266} 267 268void PDFViewer::timer_event(Core::TimerEvent&) 269{ 270 // Clear the bitmap vector of all pages except the current page 271 for (size_t i = 0; i < m_rendered_page_list.size(); i++) { 272 if (i != m_current_page_index) 273 m_rendered_page_list[i].clear(); 274 } 275} 276 277void PDFViewer::zoom_in() 278{ 279 if (m_zoom_level < zoom_levels.size() - 1) { 280 m_zoom_level++; 281 MUST(cache_page_dimensions()); 282 update(); 283 } 284} 285 286void PDFViewer::zoom_out() 287{ 288 if (m_zoom_level > 0) { 289 m_zoom_level--; 290 MUST(cache_page_dimensions()); 291 update(); 292 } 293} 294 295void PDFViewer::reset_zoom() 296{ 297 m_zoom_level = initial_zoom_level; 298 MUST(cache_page_dimensions()); 299 update(); 300} 301 302void PDFViewer::rotate(int degrees) 303{ 304 m_rotations = (m_rotations + degrees + 360) % 360; 305 MUST(cache_page_dimensions()); 306 update(); 307} 308 309void PDFViewer::set_page_view_mode(PageViewMode mode) 310{ 311 m_page_view_mode = mode; 312 Config::write_i32("PDFViewer"sv, "Display"sv, "PageMode"sv, static_cast<i32>(mode)); 313 update(); 314} 315 316PDF::PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> PDFViewer::render_page(u32 page_index) 317{ 318 auto page = TRY(m_document->get_page(page_index)); 319 auto& page_size = m_page_dimension_cache.render_info[page_index].size; 320 auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, page_size.to_type<int>())); 321 322 auto maybe_errors = PDF::Renderer::render(*m_document, page, bitmap, m_rendering_preferences); 323 if (maybe_errors.is_error()) { 324 auto errors = maybe_errors.release_error(); 325 on_render_errors(page_index, errors); 326 return bitmap; 327 } 328 329 if (page.rotate + m_rotations != 0) { 330 int rotation_count = ((page.rotate + m_rotations) / 90) % 4; 331 if (rotation_count == 3) { 332 bitmap = TRY(bitmap->rotated(Gfx::RotationDirection::CounterClockwise)); 333 } else { 334 for (int i = 0; i < rotation_count; i++) 335 bitmap = TRY(bitmap->rotated(Gfx::RotationDirection::Clockwise)); 336 } 337 } 338 339 return bitmap; 340} 341 342PDF::PDFErrorOr<void> PDFViewer::cache_page_dimensions(bool recalculate_fixed_info) 343{ 344 if (recalculate_fixed_info) 345 m_page_dimension_cache.page_info.clear_with_capacity(); 346 347 if (m_page_dimension_cache.page_info.is_empty()) { 348 m_page_dimension_cache.page_info.ensure_capacity(m_document->get_page_count()); 349 for (size_t i = 0; i < m_document->get_page_count(); i++) { 350 auto page = TRY(m_document->get_page(i)); 351 auto box = page.media_box; 352 m_page_dimension_cache.page_info.unchecked_append(PageDimensionCache::PageInfo { 353 { box.width(), box.height() }, 354 page.rotate, 355 }); 356 } 357 } 358 359 auto zoom_scale_factor = static_cast<float>(zoom_levels[m_zoom_level]) / 100.0f; 360 361 m_page_dimension_cache.render_info.clear_with_capacity(); 362 m_page_dimension_cache.render_info.ensure_capacity(m_page_dimension_cache.page_info.size()); 363 364 float max_width = 0; 365 float total_height = 0; 366 367 for (size_t i = 0; i < m_page_dimension_cache.page_info.size(); i++) { 368 auto& [size, rotation] = m_page_dimension_cache.page_info[i]; 369 rotation += m_rotations; 370 auto page_scale_factor = size.height() / size.width(); 371 372 auto height = static_cast<float>(this->height() - 2 * frame_thickness()) * zoom_scale_factor - PAGE_PADDING * 2; 373 auto width = height / page_scale_factor; 374 if (rotation % 2) 375 swap(width, height); 376 377 max_width = max(max_width, width); 378 379 m_page_dimension_cache.render_info.append({ 380 { width, height }, 381 total_height, 382 }); 383 384 total_height += height; 385 } 386 387 m_page_dimension_cache.max_width = max_width; 388 m_page_dimension_cache.total_height = total_height; 389 390 return {}; 391} 392 393void PDFViewer::change_page(u32 new_page) 394{ 395 m_current_page_index = new_page; 396 if (on_page_change) 397 on_page_change(m_current_page_index); 398}