#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2018 Andy Stewart # # Author: Andy Stewart # Maintainer: Andy Stewart # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from PyQt5.QtCore import Qt, QRect, QRectF from PyQt5.QtGui import QColor, QPixmap, QImage from PyQt5.QtGui import QPainter from PyQt5.QtWidgets import QWidget import fitz from core.buffer import Buffer class PdfViewerBuffer(Buffer): def __init__(self, buffer_id, url): Buffer.__init__(self, buffer_id, url, False, QColor(0, 0, 0, 255)) self.add_widget(PdfViewerWidget(url, QColor(0, 0, 0, 255))) class PdfViewerWidget(QWidget): def __init__(self, url, background_color): super(PdfViewerWidget, self).__init__() self.background_color = background_color # Load document first. self.document = fitz.open(url) # Get document's page information. self.first_pixmap = self.document.getPagePixmap(0) self.page_width = self.first_pixmap.width self.page_height = self.first_pixmap.height self.page_total_number = self.document.pageCount # Init scale and scale mode. self.scale = 1.0 self.read_mode = "fit_to_width" # Init scroll attributes. self.scroll_step = 20 self.scroll_offset = 0 # Padding between pages. self.page_padding = 10 # Goto page status. self.is_waiting_jump = False self.jump_number = "" def resizeEvent(self, event): # Update scale attributes after widget resize. self.update_scale() QWidget.resizeEvent(self, event) def paintEvent(self, event): # Init painter. painter = QPainter(self) painter.save() # Draw background. painter.setBrush(self.background_color) painter.drawRect(0, 0, self.rect().width(), self.rect().height()) # Get start/last render index. start_page_index = int(self.scroll_offset * 1.0 / self.scale / self.page_height) last_page_index = int((self.scroll_offset + self.rect().height()) * 1.0 / self.scale / self.page_height) # Translate painter at y coordinate. translate_y = (start_page_index * self.scale * self.page_height) - self.scroll_offset painter.translate(0, translate_y) # Render pages in visible area. for index in list(range(start_page_index, last_page_index + 1)): if index < self.page_total_number: # Get page image. page = self.document[index] trans = fitz.Matrix(self.scale, self.scale) pixmap = page.getPixmap(matrix = trans, alpha = False) img = QImage(pixmap.samples, pixmap.width, pixmap.height, pixmap.stride, QImage.Format_RGB888) qpixmap = QPixmap.fromImage(img) # Init render rect. render_width = self.page_width * self.scale render_height = self.page_height * self.scale render_x = (self.rect().width() - render_width) / 2 render_y = (index - start_page_index) * self.scale * self.page_height # Add padding between pages. if (index - start_page_index) > 0: painter.translate(0, self.page_padding) # Draw page image. painter.drawPixmap(QRect(render_x, render_y, render_width, render_height), qpixmap) painter.restore() # Draw jump page bar. if self.is_waiting_jump: jump_bar_height = 60 painter.setBrush(QColor(0, 0, 0, 128)) render_x = (self.rect().width() - render_width) / 2 render_y = self.rect().y() + self.rect().height() - jump_bar_height render_width = self.page_width * self.scale painter.drawRect(render_x, render_y, render_width, jump_bar_height) prompt_padding_x = 20 font = painter.font() font.setPointSize(28); painter.setFont(font); painter.setPen(QColor(255, 255, 255, 255)) painter.drawText(QRectF(render_x + prompt_padding_x, render_y, render_width - prompt_padding_x, jump_bar_height), "Jump to [{0} - {1}]: {2}".format(1, self.page_total_number, self.jump_number)) def keyPressEvent(self, event): if event.key() == Qt.Key_J: self.scroll_up() elif event.key() == Qt.Key_K: self.scroll_down() elif event.key() == Qt.Key_Space: self.scroll_up_page() elif event.key() == Qt.Key_B: self.scroll_down_page() elif event.key() == Qt.Key_T: self.switch_to_read_mode() elif event.key() == Qt.Key_Period: self.scroll_to_home() elif event.key() == Qt.Key_Comma: self.scroll_to_end() elif event.key() == Qt.Key_0: self.zoom_reset() elif event.key() == Qt.Key_Equal: self.zoom_in() elif event.key() == Qt.Key_Minus: self.zoom_out() elif event.key() == Qt.Key_G: if self.is_waiting_jump: self.hide_jump_bar() else: self.show_jump_bar() elif event.key() == Qt.Key_Return: if self.is_waiting_jump: self.jump_page() if self.is_waiting_jump: if event.key() in [Qt.Key_0, Qt.Key_1, Qt.Key_2, Qt.Key_3, Qt.Key_4, Qt.Key_5, Qt.Key_6, Qt.Key_7, Qt.Key_8, Qt.Key_9]: self.insert_page_number(event.text()) elif event.key() == Qt.Key_Backspace: self.delete_page_number() def update_scale(self): if self.read_mode == "fit_to_width": new_scale = self.rect().width() * 1.0 / self.page_width self.scroll_offset = new_scale * 1.0 / self.scale * self.scroll_offset self.scale = new_scale elif self.read_mode == "fit_to_height": new_scale = self.rect().size().height() * 1.0 / self.page_height self.scroll_offset = new_scale * 1.0 / self.scale * self.scroll_offset self.scale = new_scale def max_scroll_offset(self): return self.scale * self.page_height * self.page_total_number - self.rect().height() def switch_to_read_mode(self): if self.read_mode == "fit_to_width": self.read_mode = "fit_to_height" else: self.read_mode = "fit_to_width" self.update_scale() self.update() def scroll_up(self): self.scroll_offset = min(self.scroll_offset + self.scale * self.scroll_step, self.max_scroll_offset()) self.update() def scroll_down(self): self.scroll_offset = max(self.scroll_offset - self.scale * self.scroll_step, 0) self.update() def scroll_up_page(self): self.scroll_offset = min(self.scroll_offset + self.rect().height(), self.max_scroll_offset()) self.update() def scroll_down_page(self): self.scroll_offset = max(self.scroll_offset - self.rect().height(), 0) self.update() def scroll_to_home(self): self.scroll_offset = 0 self.update() def scroll_to_end(self): self.scroll_offset = self.max_scroll_offset() self.update() def zoom_in(self): self.scale = min(10, self.scale + 0.2) self.update() def zoom_out(self): self.scale = max(1, self.scale - 0.2) self.update() def zoom_reset(self): self.update_scale() self.update() def show_jump_bar(self): self.is_waiting_jump = True self.update() def hide_jump_bar(self): self.is_waiting_jump = False self.update() def jump_page(self): self.hide_jump_bar() self.is_waiting_jump = False self.scroll_offset = min(max(self.scale * (int(self.jump_number) - 1) * self.page_height, 0), self.max_scroll_offset()) self.jump_number = "" self.update() def insert_page_number(self, number): self.jump_number += number self.update() def delete_page_number(self): if len(self.jump_number) > 0: self.jump_number = self.jump_number[0:-1] self.update() if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) w = PdfViewerWidget(sys.argv[1], QColor(0, 0, 0, 255)) w.resize(1920, 1080) w.show() sys.exit(app.exec_())