You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
9.0 KiB
263 lines
9.0 KiB
#!/usr/bin/env python |
|
# -*- coding: utf-8 -*- |
|
|
|
# Copyright (C) 2018 Andy Stewart |
|
# |
|
# Author: Andy Stewart <lazycat.manatee@gmail.com> |
|
# Maintainer: Andy Stewart <lazycat.manatee@gmail.com> |
|
# |
|
# 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 <http://www.gnu.org/licenses/>. |
|
|
|
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_())
|
|
|