Skip to content

Commit

Permalink
PDF GUI Tools v2.0.0 | New interface in QT6 with PySide6 (#7)
Browse files Browse the repository at this point in the history
## New Features:
- A major shift from PyQt5 to PySide6 with all its new features.
- New enhanced interface, you can preview the selected document, hide or
resize the utilities, and more.
- The fitz module of PyMuPDF is included.
- Improvement in the project documentation.
- New installation options for pdfgui_tools.
## Fixed Bugs:
- Error when trying to obtain the selected icon when hovering over
QListWidget.
- Resolution of minor errors in pdfgui_tools utilities.
  • Loading branch information
TheWatcherMultiversal authored Nov 4, 2023
2 parents 896b3eb + 7bea197 commit b9abb17
Show file tree
Hide file tree
Showing 13 changed files with 1,856 additions and 1,289 deletions.
49 changes: 36 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<a href="#!" target="_blank"><img src="https://img.shields.io/badge/-Git-161618?style=for-the-badge&logo=git&logoColor=orange"></a>
</div>


<div align="center">
<h1>pdfgui_tools
</div>
Expand All @@ -24,7 +23,7 @@
<p>pdfgui_tools
</div>

![1](./Screenshots/Screenshot_2.png?raw=true)
![1](./Screenshots/Screenshot_1.png?raw=true)

# Sections
### Install pdfgui_tools:
Expand All @@ -42,11 +41,13 @@
## Install via deb package
To install pdfgui_tools, you will first need to download the **Debian package**, which can be found at the following link:

<a href="https://github.com/TheWatcherMultiversal/pdfgui_tools/releases/download/v1.1.0/pdfgui_tools_stable-release_1.1.0_all.deb" target="_blank">📦 Download deb package</a>
<a href="https://github.com/TheWatcherMultiversal/pdfgui_tools/releases/download/v2.0.0/pdfgui_tools_stable-release_2.0.0_amd64.deb" target="_blank">📦 Download deb package</a>

- Note: This will download the default version with the `amd64` **architecture**. If you want to install the version with `all` architectures, it is recommended to read the [Dependencies](#dependencies) section.

Once we have our **Debian package** installed, simply execute the following command, and it will be downloaded to our system:

sudo dpkg -i pdfgui_tools_stable-release_1.1.0_all.deb
sudo dpkg -i pdfgui_tools_stable-release_2.0.0_amd64.deb

- Note: If we find any missing dependencies, it's just a matter of installing them with the `sudo apt install -f` command

Expand All @@ -62,7 +63,7 @@ In case you are using an **Arch-based** distribution, you can download pdfgui_to
- <p>You can find the package at this <a href="https://aur.archlinux.org/packages/pdfgui_tools-bin">link</a>, thanks to <a href="https://github.com/begin-theadventure">begin-theadventure</a>.</p>

## Install using a script
If you do not have a Debian-based distribution or if you have a different package manager, you can use the installation script `./install.py`.
If you don't have a **Debian-based** distribution or don't want to install from the **AUR**, you can install **pdfgui_tools** using the installation script `./install.py`. Before starting, first install **python3-colorama** to avoid import conflicts

To do this, first make sure that the `./install.py` script have the necessary permissions to run on the system:

Expand All @@ -72,7 +73,11 @@ Now we can install pdfgui_tools by running the installation script:

./install.py

- Note: To uninstall pdfgui_tools, you can use `./install.py --uninstall` to remove pdfgui_tools from the system.
If you want to install the version for **all architectures**, use the `--arch-all` option.

- Note: This will install the default version with the `x86_64` **architecture**. If you need to install the `all` **architecture** version, which contains the unpacked Python packages, you will need to install the dependencies. Refer to the [Dependencies](#dependencies) section for more information.

To **uninstall pdfgui_tools**, simply use the argument `-u` or `--uninstall` to perform this action.

### poppler-utils

Expand All @@ -89,22 +94,40 @@ Now we just need to check if the program was installed correctly, for this we ex

In case you encounter any errors while running the script, please read the error messages provided by the script. Additionally, you will need to install the necessary dependencies to run pdfgui_tools correctly.



## Start using pdfgui_tools
To start using pdfgui_tools, run the `pdfgui_tools` command, and a window like the following should appear:

![1](./Screenshots/Screenshot_1.png?raw=true)
![1](./Screenshots/Screenshot_2.png?raw=true)

If you need help or assistance navigating PDF GUI Tools, use the `F1` key to display the help window.

## Dependencies
Before being able to use pdfgui_tools, you need to have the following **dependencies** installed on your system for the program to function properly:

- **poppler-utils**
- **python3-pypdf2**
- **python3-pyqt5**
- **qtbase5-dev**
#### Dependencies

<ul>
<li><a href="https://poppler.freedesktop.org/" target="_blank"><b>poppler-utils</b></a></li>
<li><a href="" target="_blank"><b>xdg-utils</b></a></li>
</ul>

#### Optional dependencies

<ul>
<li><a href="" target="_blank"><b>breeze-icon-theme (default)</b></a></li>
</ul>

- Note: A default **icon theme** that works with **Qt** is required to properly display the **pdfgui_tools interface**. In some distributions like **KDE**, this dependency is not necessary.

#### Python dependencies

<ul>
<li><a href="https://pypi.org/project/PyPDF2/" target="_blank"><b>PyPDF2 (1.26.0)</b></a></li>
<li><a href="https://pypi.org/project/PyMuPDF/" target="_blank"><b>PyMuPDF (1.23.5)</b></a></li>
<li><a href="https://pypi.org/project/PySide6/" target="_blank"><b>PySide6 (6.6.0)</b></a></li>
</ul>

- Note: Starting from version `2.0.0` of **pdfgui_tools**, for versions with the `amd64` or `x86_64` **architecture**, it is not necessary to install **Python-related dependencies** separately. pdfgui_tools is bundled with pyinstaller along with **all the necessary dependencies** to run. It will only require those dependencies that are not part of Python.

## Report bugs or give suggestions
To notify errors in the program or give suggestions for it, write your request in the following email: <universepenguin@protonmail.com>
Binary file modified Screenshots/Screenshot_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Screenshots/Screenshot_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 102 additions & 4 deletions code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,119 @@

Here is where the code and program logic are stored. Modify the files only if you know what you're doing; the code is documented to enhance readability. If you intend to modify something, I recommend reading this first: https://doc.qt.io/qtforpython-6/

## File `pdfguiUtils.py`:
# Sections
### File `pdfgui_tools.py`
- [Description](#pdfgui_toolspy)
- [class pdfguiMainWindow](#class-pdfguimainwindowui_mainwindow)
- [class aboutWindow](#class-aboutwindowui_aboutwindow)

### File `pdfguiUtils.py`
- [Description](#pdfguiutilspy)
- [class PyPDF2utils](#class-pypdf2utils)
- [class PyMuPDF_Utils](#class-pymupdf_utilsobject)

### File `pdfguiWindows.py`
- [Description](#pdfguiwindowspy)
- [class Ui_MainWindow](#class-ui_mainwindowobject)
- [class class Ui_AboutWindow](#class-ui_aboutwindowobject)
---

## `pdfgui_tools.py`:
This is the main file to run **pdfgui_tools**; it is the Python code that **connects to the logic** in `pdfguiUtils.py` and the **interface** in `Ui_Windows.py`. It links **user interactions** in the pdfgui_tools interface to the functions it includes for working with PDF documents.

### Classes from the file:

#### `class pdfguiMainWindow(Ui_MainWindow)`
This class inherits from the **main window** class of **pdfgui_tools**. It solely connects **user interactions** with the app and makes a few modifications depending on user interactions. If you want to modify the interface, go to the `designer` directory of this repository

- Methods:
```python
# Methods: # Arguments: # Description:
def __init__ (self): # -> Set up the main window and connect user interactions to the rest of the app's functions
def click_listWidget (self): # -> Make changes when an item in the QListWidget is touched
def click_checkBox_range (self): # -> Detects the change in the range checkbox and stores it in the value of the self.dictPDFs dictionary
def spinbox_initial_changed (self): # -> Detects the change in the initial range spinbox, and stores it in the self.dictPDFs dictionary
def spinbox_final_changed (self): # -> Detects the change in the final range spinbox, and stores it in the self.dictPDFs dictionary
def click_view (self): # -> Open the PDF document in the default PDF viewer using xdg-open
def click_add (self): # -> Add the PDF documents to the QListWidget from their paths
def click_delete (self): # -> Delete the currently selected item from the QListWidget
def move_up (self): # -> Move the currently selected item one position up in the QListWidget
def move_down (self): # -> Move the currently selected item one position down in the QListWidget
def merge_pdf (self): # -> Merge all the PDF documents from the QListWidget into a single PDF
def encrypt_decrypt (self, encrypt:bool, icon_file:str): # -> Encrypt or decrypt the PDF documents from the QListWidget
def separate_pdf (self): # -> Split the selected PDF document into parts in the QListWidget
def convert (self): # -> Convert the selected document in the QListWidget into a selected multimedia file
def _help (self): # -> Display a help window
def _about (self): # -> Show the 'About' window
def inf_messages (self, title, message): # -> Function to call in case of an information or error message
```

#### `class aboutWindow(Ui_AboutWindow)`

This class inherits from the **Ui_AboutWindow** window class. If you need to make modifications, use the design file from the `designer` directory of this repository.

- Methods:
```python
# Methods: # Arguments: # Description:
def __init__ (self, aboutWin): # -> Set up the About window and connect the OK button to a lambda function.
```

---

## `pdfguiUtils.py`:
This is the main utilities file for **pdfgui_tools**, where all the application's utilities are included and will continue to be included. This file will contain variables and classes with their respective functions that will provide functionality to **pdfgui_tools**. Any module can be used as long as it is **stable**.

### Classes from the file:

#### `class PyPDF2utils`
It contains functions for the utilities included in the `PyPDF2` module. These functions return a value depending on the state and integrity of the **PDF document**. They should return a **tuple** with information for the **QMessageBox** window in case of an **error** or **informational** message regarding the **PDF documents**.
It contains methods for the utilities included in the `PyPDF2` module. These methods return a value depending on the state and integrity of the **PDF document**. They should return a **tuple** with information for the **QMessageBox** window in case of an **error** or **informational** message regarding the **PDF documents**.

- Functions:
- Methods:
```python
# Functions: # Arguments: # Description:
# Methods: # Arguments: # Description:
def merge_pdf (name_file:str, pdfs:list, pages:list): # -> Combine the PDFs from the list.
def separate_pdf (pdf:str, dest:str): # -> Split a PDF document into multiple parts, which are saved in a directory.
def extract_text (name_file:str, pdf:str): # -> Extract the text from a PDF per page.
def encrypt_pdf (pdf:str, password:str): # -> Encrypt a PDF
def decrypt_pdf (pdf:str, password:str): # -> Decrypt a PDF
def fileEncrypted (pdf): # -> Determines if a file is encrypted, to prevent importing the PyPDF2 module twice.
```
#### `class PyMuPDF_Utils(object)`
This contains the utilities included in `PyMuPDF`. Currently, its functionality is not extensive within pdfgui_tools, more features from this Python module will be added.

This class takes a path to a PDF document as an argument, opens the document, and checks if it is encrypted to work with it.

- Methods:
```python
# Methods: # Arguments: # Description:
def __init__ (self, pdf:str): # -> It takes a PDF path as an argument and opens the document with fitz
def getSize (self): # -> Obtains the dimensions of the PDF document
def documentScale (self): # -> Obtains the scale of the PDF document
```

---

## `pdfguiWindows.py`:
This file contains the configuration of the **pdfgui_tools interface**. This Python file is generated by the corresponding design file in the `designer` directory of this repository, and it encompasses a combination of all the interfaces created in **Qt Designer**.

### Classes from the file:

#### `class Ui_MainWindow(object)`
This class contains the code generated by the `pdfgui_tools.ui` file in the `designer` directory. It constructs the main interface of **pdfgui_tools**.

- Methods:
```python
# Methods: # Arguments: # Description:
def setupUi (self, MainWindow, icon): # -> File to generate the main interface, it takes two arguments, the window and the icon of pdfgui_tools
def retranslateUi (self, MainWindow): # -> Generated by pyside6-uic, this file for translations and setting text
```

#### `class Ui_AboutWindow(object):`
This contains the code generated by `about.ui` from the `designer` directory, generating an **About window** with information about **pdfgui_tools**.

- Methods:
```python
# Methods: # Arguments: # Description:
def setupUi (self, AboutWindow, icon, version_app): # -> This generates the About window, taking three arguments: the About window, the pdfgui_tools icon, and its version
def retranslateUi (self, AboutWindow, version_app): # -> Generated by pyside6-uic, this file for translations and setting text
```
72 changes: 50 additions & 22 deletions code/pdfguiUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@
#
# -----------------------------------------------------------------------------------


from PyPDF2 import PdfFileMerger, PdfFileReader, PdfFileWriter
import os, subprocess
from PyPDF2 import PdfFileMerger, PdfFileReader, PdfFileWriter # <--- PyPDF2 v1.26.0
import os, subprocess, fitz # <-------------------------------------- PyMuPDF v1.23.5

# Variables and Paths
title_app = "PDF GUI Tools"#-------------------> Title app
version_app = "1.1.0"#---------------------------> Version pdfgui_tools
version_app = "2.0.0"#---------------------------> Version pdfgui_tools
path_pdfgui_tools = ("/usr/share/pdfgui_tools")#-------> Path pdfgui_tools
spinBox_range = (1, 1000)#-------------------------> Allowed spinbox range
repeat_symbol = "*"#-------------------------------> Symbol of repeated PDFs in the list (avoids conflicts in the self.dictPDFs dictionary)
maxSizeDocument = 300#-------------------------------> Maximum document display size.
icon_pdf = "application-pdf"#-----------------> Qt icon name for PDF document
icon_pdfEncrypt = "encrypted"#-----------------------> Qt icon name for encrypted files


Paths = {
"icon_app" : (f"{path_pdfgui_tools}/assets/pdfguitools.svg"),
Expand All @@ -31,29 +34,30 @@
class PyPDF2utils:

# >> Merge PDFs:
def merge_pdf(name_file:str, pdfs:list, pages:list):
def merge_pdf(name_file:str, pdfs:list):
"""PDF Merger using the `PyPDF2` module`
:param name_file: Name of the document where the PDFs will be merged
:param pdfs: Receives a `list` with the paths of the PDFs to merge
:param pages: Receives a `list` with the page ranges for merging each document"""
:param pdfs: Receives a `list` with the paths of the PDFs to merge"""

try:
pdf_merger = PdfFileMerger()

try:
for pdf, page in zip(pdfs, pages):
if PdfFileReader(pdf).isEncrypted is True:
return("Info", f"The document {pdf} is encrypted, decrypt it to perform this action")
for pdf in pdfs:
path_pdf = pdf[2]; checkRangeBox = pdf[0]; pdf_range = (pdf[1][0], pdf[1][1])

if page[0]: page = (page[1][0] - 1, page[1][1])
else: page = None
pdf_merger.append(open(pdf, 'rb'), pages=page)
if PyPDF2utils.fileEncrypted(path_pdf):
return("Info", f"The document {path_pdf} is encrypted, decrypt it to perform this action")

except IndexError: return("Error", f"Index error in the file {pdf}, please enter a valid value")
except: return("Error", f"The document {pdf} could not be processed, please check the integrity of the document")
if checkRangeBox: ran_page = pdf_range
else: ran_page = None

pdf_merger.append(open(path_pdf, 'rb'), pages=ran_page)

except IndexError: return("Error", f"Index error in the file {path_pdf}, please enter a valid value")
except: return("Error", f"The document {path_pdf} could not be processed, please check the integrity of the document")

with open(name_file, 'wb') as f:
pdf_merger.write(f)
Expand All @@ -62,7 +66,6 @@ def merge_pdf(name_file:str, pdfs:list, pages:list):
return True



# >> Separate PDFs:
def separate_pdf(pdf:str, dest:str):
"""PDF Splitter using the `PyPDF2` module
Expand Down Expand Up @@ -90,7 +93,6 @@ def separate_pdf(pdf:str, dest:str):
return True



# >> Extract Text:
def extract_text(name_file:str, pdf:str):
"""Text Extractor using the `PyPDF2` module.
Expand All @@ -113,7 +115,6 @@ def extract_text(name_file:str, pdf:str):
return True



# >> Encrypt PDFs:
def encrypt_pdf(pdf:str, password:str):
"""Encrypt PDF documents using the `PyPDF2` module
Expand Down Expand Up @@ -142,7 +143,6 @@ def encrypt_pdf(pdf:str, password:str):
return True



# >> Decrypt PDFs:
def decrypt_pdf(pdf:str, password:str):
"""Decrypt PDF documents using the `PyPDF2` module
Expand Down Expand Up @@ -171,10 +171,38 @@ def decrypt_pdf(pdf:str, password:str):

except: return("Error", "The file could not be encrypted, please check the document's integrity")
return True



# >> PDF id Encrypted:
def fileEncrypted(pdf):
try: return PdfFileReader(pdf).isEncrypted
except: return False
except: return False



class PyMuPDF_Utils(object):

def __init__(self, pdf:str):
"""Utilities included in the `fitz` module of PyMuPDF"""

self.document = fitz.open(pdf)
self.isEncrypted = PyPDF2utils.fileEncrypted(pdf)


# Obtains the dimensions of the PDF document
def getSize(self):
"""Obtains the dimensions of the PDF document"""

if not self.isEncrypted:
firts_page = self.document[0]
return (firts_page.rect.width, firts_page.rect.height)


# Obtains the scale of the PDF document
def documentScale(self):
"""Obtains the scale of the PDF document"""

if not self.isEncrypted:
documentSize = self.getSize()
if documentSize is None: return
return round(documentSize[1] / documentSize[0], 2)
Loading

0 comments on commit b9abb17

Please sign in to comment.