Bug#1106716: unblock: krop/0.7.0-0.1
Package: release.debian.org
Severity: normal
X-Debbugs-Cc: krop@packages.debian.org, Alexandre Mestiashvili <mestia@debian.org>
Control: affects -1 + src:krop
User: release.debian.org@packages.debian.org
Usertags: unblock
Please unblock package krop
[ Reason ]
I did not noticed that upstream published a new release
freeing us from the ties on old & deprecated python3-pypdf2.
[ Impact ]
I use krop, it's a nice tool with a sensible popcon.
[ Tests ]
I use it, it works. There are autopkgtest too.
[ Risks ]
Leaf package.
[ Checklist ]
[x] all changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in testing
[ Other info ]
I plan to continue maintaining this package
and will file an ITS to document so.
Most of the debdiff is renamed .desktop & AppStream files and a nice new icon.
The Qt4 & Qt6 versions are not used anymore/yet and not relevant.
In Forky cycle this will be switched to Qt6 but not now.
Files in second .deb but not in first
-------------------------------------
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop-0.7.0.egg-info/PKG-INFO
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop-0.7.0.egg-info/dependency_links.txt
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop-0.7.0.egg-info/entry_points.txt
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop-0.7.0.egg-info/top_level.txt
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop/mainwindowui_qt6.py
-rw-r--r-- root/root /usr/share/applications/com.arminstraub.krop.desktop
-rw-r--r-- root/root /usr/share/icons/hicolor/scalable/apps/com.arminstraub.krop.svg
-rw-r--r-- root/root /usr/share/metainfo/com.arminstraub.krop.metainfo.xml
Files in first .deb but not in second
-------------------------------------
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop-0.6.0.egg-info/PKG-INFO
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop-0.6.0.egg-info/dependency_links.txt
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop-0.6.0.egg-info/entry_points.txt
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop-0.6.0.egg-info/top_level.txt
-rw-r--r-- root/root /usr/lib/python3/dist-packages/krop/mainwindowui_qt4.py
-rw-r--r-- root/root /usr/share/applications/krop.desktop
-rw-r--r-- root/root /usr/share/metainfo/krop.appdata.xml
Control files: lines which differ (wdiff format)
------------------------------------------------
Depends: python3-poppler-qt5, [-python3-pypdf2,-] {+python3-fitz, python3-pypdf,+} python3:any
Installed-Size: [-179-] {+204+}
Version: [-0.6.0-3-] {+0.7.0-0.1+}
unblock krop/0.7.0-0.1
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/ChangeLog krop-0.7.0/ChangeLog
--- krop-0.6.0/ChangeLog 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/ChangeLog 2025-04-06 21:04:10.000000000 +0200
@@ -1,3 +1,16 @@
+krop (0.7.0) 2025-04-06 -- Armin Straub
+
+ + Support PyQt6 and update packaging (thanks to PunkPangolin for this work).
+ + Support PyMuPDF for rendering and cropping.
+ + Support pikepdf for cropping (thanks to Hongzhuo Liang for the idea and
+ initial code).
+ * Support the new version of pypdf in addition to its predecessor PyPDF2
+ (thanks to Emmanuel Rosa for the idea and initial code).
+ * Support both old (<2) and new (>=2) versions of PyPDF2 (thanks to milahu
+ for the idea and initial code).
+ * When using PyPDF2, preserve links within a PDF (thanks to chrthi for this).
+ * Display PDF file name in window title.
+
krop (0.6.0) 2020-06-09 -- Armin Straub
+ Fixed aspect ratios, like letter size, can be chosen for selections.
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/com.arminstraub.krop.desktop krop-0.7.0/com.arminstraub.krop.desktop
--- krop-0.6.0/com.arminstraub.krop.desktop 1970-01-01 01:00:00.000000000 +0100
+++ krop-0.7.0/com.arminstraub.krop.desktop 2025-04-06 21:04:10.000000000 +0200
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Type=Application
+Name=krop
+GenericName=Crop PDF files
+Comment=A tool to crop PDF files
+TryExec=krop
+Exec=krop %F
+Icon=com.arminstraub.krop
+MimeType=application/pdf;
+Categories=Utility;
+Keywords=pdf;crop;rotate;ereader;
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/com.arminstraub.krop.metainfo.xml krop-0.7.0/com.arminstraub.krop.metainfo.xml
--- krop-0.6.0/com.arminstraub.krop.metainfo.xml 1970-01-01 01:00:00.000000000 +0100
+++ krop-0.7.0/com.arminstraub.krop.metainfo.xml 2025-04-06 21:04:10.000000000 +0200
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2014-2020 Armin Straub <mail@arminstraub.com> -->
+<component type="desktop-application">
+ <id>com.arminstraub.krop</id>
+ <launchable type="desktop-id">com.arminstraub.krop.desktop</launchable>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-3.0+</project_license>
+ <name>krop</name>
+ <developer id="com.arminstraub">
+ <name>Armin Straub</name>
+ </developer>
+ <summary>Crop PDF files</summary>
+ <description>
+ <p>
+ krop is a simple graphical tool to crop the pages of PDF files. It is written in Python and relies on PyQt and PyMuPDF (or a suitable subset of pypdf/pikepdf/python-poppler-qt) for its functionality. A unique feature of krop is its ability to automatically split pages into subpages to fit the limited screen size of devices such as eReaders. This is particularly useful, if your eReader does not support convenient scrolling.
+ </p>
+ </description>
+ <keywords>
+ <keyword>pdf</keyword>
+ <keyword>crop</keyword>
+ <keyword>rotate</keyword>
+ <keyword>ereader</keyword>
+ </keywords>
+ <screenshots>
+ <screenshot type="default">
+ <image type="source" width="1024" height="576">https://arminstraub.com/images/krop/screenshot-appdata.png</image>
+ <caption>Main window of krop</caption>
+ </screenshot>
+ </screenshots>
+ <url type="homepage">https://arminstraub.com/software/krop</url>
+ <url type="bugtracker">https://github.com/arminstraub/krop/issues</url>
+ <update_contact>mail@arminstraub.com</update_contact>
+ <content_rating type="oars-1.1" />
+ <releases>
+ <release version="0.6.0" date="2020-06-09"/>
+ </releases>
+</component>
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/com.arminstraub.krop.svg krop-0.7.0/com.arminstraub.krop.svg
--- krop-0.6.0/com.arminstraub.krop.svg 1970-01-01 01:00:00.000000000 +0100
+++ krop-0.7.0/com.arminstraub.krop.svg 2025-04-06 21:04:10.000000000 +0200
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48">
+ <defs>
+ <linearGradient id="h">
+ <stop offset="0"/>
+ <stop offset="1" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="g">
+ <stop offset="0" stop-color="#9a0c00"/>
+ <stop offset="1" stop-color="#9a0c00" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="f">
+ <stop offset="0" stop-color="#df2a2a"/>
+ <stop offset="1" stop-color="#df2a2a" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="e">
+ <stop offset="0" stop-color="#e2e2e2"/>
+ <stop offset="1" stop-color="#d8d8d8"/>
+ </linearGradient>
+ <radialGradient id="d" cx="165.061" cy="23.333" r="7.285" gradientTransform="matrix(1 0 0 1.0103 0 -.16)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" stop-color="#ef3535"/>
+ <stop offset="1" stop-color="#a40000" stop-opacity="0"/>
+ </radialGradient>
+ <linearGradient id="c" x1="292.972" x2="296.94" y1="4.759" y2="10.711" gradientUnits="userSpaceOnUse">
+ <stop offset="0" stop-color="#eeeeec"/>
+ <stop offset="1" stop-color="#fff"/>
+ </linearGradient>
+ <radialGradient id="b" cx="165.061" cy="23.333" r="7.285" gradientTransform="matrix(1 0 0 1.0103 0 -.16)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" stop-color="#ef3535"/>
+ <stop offset="0" stop-color="#c91a1a"/>
+ <stop offset="1" stop-color="#ff4c4c"/>
+ </radialGradient>
+ <linearGradient id="a" x1="68.175" x2="74.587" y1="21.425" y2="27.837" gradientUnits="userSpaceOnUse">
+ <stop offset="0" stop-color="#babdb6"/>
+ <stop offset="1" stop-color="#eeeeec"/>
+ </linearGradient>
+ <linearGradient id="o" x1="22.225" x2="24.19" y1="23.843" y2="22.861" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
+ <linearGradient id="k" x1="294.595" x2="297.185" y1="12.188" y2="13.34" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
+ <linearGradient id="v" x1="296.762" x2="297.798" y1="12.012" y2="10.947" gradientTransform="translate(-1053.179 -16.847) scale(3.62444)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
+ <linearGradient id="l" x1="296.486" x2="296.529" y1="15.507" y2="9.877" gradientTransform="matrix(3.6379 0 0 3.47038 -1056.116 -16.007)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
+ <linearGradient id="i" x1="292.972" x2="296.94" y1="4.759" y2="10.711" gradientTransform="matrix(-4.12776 0 0 4.1366 1244.465 -11.905)" gradientUnits="userSpaceOnUse" xlink:href="#c"/>
+ <linearGradient id="m" x1="292.972" x2="296.94" y1="4.759" y2="10.711" gradientTransform="matrix(4.05343 0 0 4.1366 -1175.535 -11.905)" gradientUnits="userSpaceOnUse" xlink:href="#c"/>
+ <linearGradient id="w" x1="39.62" x2="-3.533" y1="44.541" y2="-11.889" gradientTransform="matrix(1.16188 0 0 .9925 -5.112 .064)" gradientUnits="userSpaceOnUse" xlink:href="#d"/>
+ <linearGradient id="r" x1="13.825" x2="7.67" y1="40.069" y2="2.326" gradientTransform="matrix(1.16188 0 0 .9925 -2.667 .064)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
+ <linearGradient id="q" x1="7.185" x2="25.152" y1="31.057" y2="50.775" gradientTransform="matrix(1.16188 0 0 .9925 -2.43 .266)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
+ <linearGradient id="n" x1="20.288" x2="24.326" y1="6.46" y2="23.943" gradientUnits="userSpaceOnUse" xlink:href="#e"/>
+ <linearGradient id="j" x1="20.288" x2="24.326" y1="6.46" y2="23.943" gradientUnits="userSpaceOnUse" xlink:href="#e"/>
+ <radialGradient id="s" cx="34.376" cy="37.5" r="8.389" fx="34.376" fy="37.5" gradientTransform="matrix(1 0 0 1.06038 0 -2.3)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
+ <linearGradient id="t" x1="298.479" x2="298.869" y1="13.6" y2="13.803" gradientUnits="userSpaceOnUse" xlink:href="#f"/>
+ <linearGradient id="u" x1="298.479" x2="298.869" y1="13.6" y2="13.803" gradientUnits="userSpaceOnUse" xlink:href="#g"/>
+ <radialGradient id="p" cx="25.188" cy="41.625" r="18.063" fx="25.188" fy="41.625" gradientTransform="matrix(1 0 0 .32526 0 28.086)" gradientUnits="userSpaceOnUse" xlink:href="#h"/>
+ </defs>
+ <path fill="url(#i)" stroke="#888a85" stroke-linecap="round" stroke-linejoin="round" d="M 34.174311,1.6249997 C 34.386261,1.6935355 34.59157,1.7696619 34.798294,1.842502 C 35.449709,4.0395037 38.469777,6.2612218 37.321354,8.4491328 C 33.495509,14.82952 29.697021,21.294565 25.899759,27.72527 C 25.154013,27.872172 24.401732,27.952183 23.647995,27.96996 C 22.061603,28.01017 20.433063,27.775465 18.927431,27.23589 C 23.978303,18.684616 29.031301,10.114483 34.174311,1.6249997 z"/>
+ <path fill="url(#j)" d="M 34.288823,4.25 C 34.057702,4.5574529 33.839208,5.120942 33.602793,5.40625 C 29.555938,12.158979 25.440784,18.900329 21.378976,25.625 C 21.318425,25.878117 20.565047,26.637291 21.366935,26.567963 C 22.478492,26.765843 23.638682,26.918567 24.746762,26.625 C 28.505752,20.407794 32.192639,14.142582 35.943048,7.9231779 C 36.285519,7.5359043 36.352163,6.9979201 35.992403,6.611197 C 35.462387,5.7945892 34.925464,4.9364821 34.382373,4.15625 L 34.311813,4.2269607 L 34.288823,4.25 z"/>
+ <path fill="url(#k)" stroke="#9a0c00" stroke-linecap="round" stroke-linejoin="round" stroke-width=".281" d="M297.044 12.3L296.399 13.385 295.133 14.719 294.732 13.673 295.747 11.96 297.044 12.3z" transform="matrix(3.6379 0 0 3.47038 -1056.116 -16.007)"/>
+ <path fill="url(#l)" d="M 20.40625,26.96875 C 19.183905,27.455468 19.192232,29.003929 18.481272,29.932762 C 18.138949,30.648557 17.537483,31.278989 17.28125,32.03125 C 17.271571,32.546641 17.729203,33.391474 18.3125,32.9375 C 19.697476,31.791172 20.876866,30.398821 21.756725,28.810629 C 21.989088,28.320596 22.552476,27.916466 22.625,27.40625 C 22.086431,26.835441 21.112182,26.873225 20.40625,26.96875 z"/>
+ <path fill="url(#m)" stroke="#888a85" stroke-linecap="round" stroke-linejoin="round" d="M 12.960099,1.6249997 C 12.751966,1.6935355 12.550355,1.7696619 12.347353,1.842502 C 11.707669,4.0395037 8.7419877,6.2612218 9.8697297,8.4491328 C 13.626677,14.82952 17.35676,21.294565 21.085639,27.72527 C 21.817956,27.872172 22.55669,27.952183 23.296853,27.96996 C 24.854677,28.01017 26.453889,27.775465 27.932407,27.23589 C 22.972493,18.684616 18.010492,10.114483 12.960099,1.6249997 z"/>
+ <path fill="url(#n)" d="M 12.719667,4.25 C 12.336632,5.3766793 11.270006,6.2059645 11.004855,7.40625 C 14.713376,13.800362 18.475798,20.175378 22.181757,26.5625 C 23.380123,26.820799 24.610198,26.655657 25.795112,26.40625 C 25.606339,25.665807 25.056911,25.075319 24.765129,24.3767 C 20.870526,17.806174 16.941429,11.242872 13.087127,4.65625 C 13.072466,4.5046403 12.870425,4.1721152 12.719667,4.25 z"/>
+ <path fill="url(#o)" d="M 24.190449 23.843431 A 0.98252523 0.98252523 0 1 1 22.225399,23.843431 A 0.98252523 0.98252523 0 1 1 24.190449 23.843431 z" color="#000" style="marker:none" transform="matrix(.9799 0 0 1 .311 .174)"/>
+ <path fill="url(#p)" fill-rule="evenodd" d="M 43.25 41.625 A 18.0625 5.875 0 1 1 7.125,41.625 A 18.0625 5.875 0 1 1 43.25 41.625 z" color="#000" opacity=".267" style="marker:none" transform="matrix(1.25605 0 0 .81915 -7.2 9.09)"/>
+ <path fill="url(#q)" stroke="#a40000" d="M 17.700393,30.286934 C 20.935404,32.013583 21.196229,36.899851 18.278338,41.201286 C 15.360479,45.50525 10.373849,47.596472 7.1373807,45.877418 C 3.9008825,44.150767 3.6415462,39.267032 6.5594356,34.965597 C 9.4758075,30.664166 14.463925,28.572944 17.700393,30.286934 z M 15.845268,33.029079 C 14.408745,32.26545 11.33781,33.569599 9.3789266,36.463107 C 7.4160164,39.356612 7.5560293,42.376624 8.991202,43.137951 C 10.426348,43.906181 13.499985,42.597432 15.458868,39.703925 C 17.42313,36.81042 17.281765,33.792709 15.845268,33.029079 z"/>
+ <path fill="url(#r)" d="M 14.3255,30.583289 C 12.400369,30.97051 10.691041,32.037306 9.2785926,33.064531 C 8.5268294,33.759433 8.0350294,34.514452 7.3629449,35.31874 C 5.6546178,37.670805 4.9387067,40.762168 6.2901069,43.388409 C 6.90956,44.841515 8.9327419,45.435852 10.658323,45.067542 C 12.110236,44.819078 13.339639,43.906473 14.470735,43.268641 C 15.391637,42.47786 16.024749,41.642131 16.803626,40.677364 C 18.612986,38.202962 19.595537,34.928687 18.101604,32.165081 C 17.377898,31.022952 15.866963,30.41829 14.3255,30.583289 z M 14.797513,31.54477 C 16.814017,31.795124 18.154487,33.577585 17.92006,35.266634 C 17.940833,37.553573 16.774038,39.710728 15.196909,41.500756 C 13.779705,42.902737 11.848294,44.229027 9.5327534,44.137076 C 8.1738996,44.134209 7.100179,43.224779 6.7169325,42.176618 C 6.1002938,39.644695 6.9116496,36.911389 8.6831288,34.83862 C 10.041367,33.315308 11.877976,31.95152 14.150642,31.596926 C 14.366331,31.581652 14.581522,31.554432 14.797513,31.54477 z"/>
+ <path fill="url(#s)" stroke="#a40000" d="M 30.331764,30.286934 C 27.096753,32.013583 26.835929,36.899851 29.75382,41.201286 C 32.671679,45.50525 37.658309,47.596472 40.894777,45.877418 C 44.131276,44.150767 44.390611,39.267032 41.472722,34.965597 C 38.55635,30.664166 33.568233,28.572944 30.331764,30.286934 z M 32.18689,33.029079 C 33.623412,32.26545 36.694348,33.569599 38.653231,36.463107 C 40.616141,39.356612 40.476128,42.376624 39.040956,43.137951 C 37.60581,43.906181 34.532173,42.597432 32.57329,39.703925 C 30.609028,36.81042 30.750393,33.792709 32.18689,33.029079 z"/>
+ <path fill="url(#t)" stroke="url(#u)" stroke-linecap="round" stroke-linejoin="round" stroke-width=".276" d="M296.956 12.3L297.6 13.385 298.867 14.719 299.268 13.673 298.254 11.96 296.956 12.3z" transform="translate(-1053.179 -16.847) scale(3.62444)"/>
+ <path fill="url(#v)" d="M 26.15625,27.9375 C 25.729502,28.136321 25.139436,28.138981 24.8125,28.4375 C 25.76252,29.838889 26.702412,31.352161 27.663379,32.650077 C 28.331933,33.404621 29.019194,34.150303 29.78125,34.8125 C 30.516527,33.421076 29.91641,31.751292 28.96875,30.625 C 28.366215,29.725307 28.138928,28.512038 27.125,28.03125 C 26.820951,27.912839 26.474385,27.853373 26.15625,27.9375 z"/>
+ <path fill="url(#w)" d="M 32.280087,30.449093 C 30.759703,30.678844 29.385141,31.534748 29.039639,32.837057 C 27.908495,35.232508 28.824763,37.950571 30.319418,40.063908 C 31.421345,41.40911 32.259488,42.993821 33.959001,43.837878 C 35.429654,44.761502 37.300143,45.728452 39.176641,45.138766 C 40.689956,44.705317 41.547313,43.4582 41.856813,42.166912 C 42.461243,39.856882 41.561117,37.490951 40.149846,35.530428 C 39.491173,34.616722 38.816861,33.647222 38.036528,32.835783 C 36.841969,31.932329 35.398614,31.184254 33.947688,30.603431 C 33.41359,30.493019 32.832464,30.37069 32.280087,30.449093 z M 32.715792,31.658699 C 34.473095,31.591923 35.950305,32.398157 37.092162,33.427664 C 38.124459,34.396792 39.113817,35.23287 39.754673,36.426541 C 40.831856,38.24711 41.142534,40.4065 40.594777,42.390073 C 40.066397,43.714585 38.368623,44.362109 36.803657,44.006518 C 34.821776,43.77769 33.586317,42.335503 32.277091,41.198158 C 30.771344,39.766768 29.83647,37.719532 29.76651,35.715783 C 29.780622,34.698114 29.740042,33.53736 30.464653,32.682212 C 30.876926,32.139062 31.84466,31.627886 32.715792,31.658699 z"/>
+</svg>
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/debian/changelog krop-0.7.0/debian/changelog
--- krop-0.6.0/debian/changelog 2024-06-30 17:26:18.000000000 +0200
+++ krop-0.7.0/debian/changelog 2025-05-28 12:31:55.000000000 +0200
@@ -1,3 +1,12 @@
+krop (0.7.0-0.1) unstable; urgency=medium
+
+ * Non-maintainer upload.
+ * New upstream version 0.7.0
+ * Replace deprecated python3-pypdf2 with python3-pymupdf + python3-fitz
+ (Closes: #1029735)
+
+ -- Alexandre Detiste <tchet@debian.org> Wed, 28 May 2025 12:31:55 +0200
+
krop (0.6.0-3) unstable; urgency=medium
* Non-maintainer upload.
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/debian/control krop-0.7.0/debian/control
--- krop-0.6.0/debian/control 2024-06-30 17:24:33.000000000 +0200
+++ krop-0.7.0/debian/control 2025-05-28 12:28:28.000000000 +0200
@@ -3,13 +3,11 @@
Section: utils
Priority: optional
Build-Depends: debhelper-compat (= 13),
- dh-python,
+ dh-sequence-python3,
help2man,
python3,
- python3-poppler-qt5,
- python3-pypdf2,
python3-setuptools
-Standards-Version: 4.6.1
+Standards-Version: 4.7.2
Vcs-Browser: https://salsa.debian.org/debian/krop
Vcs-Git: https://salsa.debian.org/debian/krop.git
Homepage: https://arminstraub.com/software/krop
@@ -17,7 +15,8 @@
Package: krop
Architecture: all
Depends: python3-poppler-qt5,
- python3-pypdf2,
+ python3-fitz,
+ python3-pypdf,
${misc:Depends},
${python3:Depends}
Description: tool to crop PDF files
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/debian/copyright krop-0.7.0/debian/copyright
--- krop-0.6.0/debian/copyright 2024-06-29 22:19:07.000000000 +0200
+++ krop-0.7.0/debian/copyright 2025-05-28 12:31:49.000000000 +0200
@@ -13,7 +13,7 @@
2018 Alexandre Mestiashvili <mestia@debian.org>
License: GPL-3+
-Files: krop.appdata.xml
+Files: com.arminstraub.krop.metainfo.xml
Copyright: 2014-2020 Armin Straub
License: CC0-1.0
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/debian/krop.install krop-0.7.0/debian/krop.install
--- krop-0.6.0/debian/krop.install 2024-06-29 22:19:07.000000000 +0200
+++ krop-0.7.0/debian/krop.install 2025-05-28 12:24:32.000000000 +0200
@@ -1,2 +1,2 @@
-krop.appdata.xml usr/share/metainfo
-krop.desktop usr/share/applications
+com.arminstraub.krop.metainfo.xml usr/share/metainfo
+com.arminstraub.krop.desktop usr/share/applications
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/debian/rules krop-0.7.0/debian/rules
--- krop-0.6.0/debian/rules 2024-06-29 22:19:07.000000000 +0200
+++ krop-0.7.0/debian/rules 2025-05-28 12:31:55.000000000 +0200
@@ -6,11 +6,7 @@
mandir := $(usrshare)/man/man1
%:
- dh $@ --with python3 --buildsystem=pybuild
-
-override_dh_auto_clean:
- dh_auto_clean
- rm --verbose -f krop/version.py
+ dh $@ --buildsystem=pybuild
override_dh_auto_install:
python3 setup.py install --root=debian/krop --install-layout=deb --install-scripts=/usr/share/krop
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/.gitignore krop-0.7.0/.gitignore
--- krop-0.6.0/.gitignore 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/.gitignore 2025-04-06 21:04:10.000000000 +0200
@@ -2,3 +2,4 @@
dist/
.pybuild/
MANIFEST
+krop.egg-info/
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/application.py krop-0.7.0/krop/application.py
--- krop-0.6.0/krop/application.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/application.py 2025-04-06 21:04:10.000000000 +0200
@@ -10,7 +10,11 @@
To additionally trim each of these pages:
krop --go --grid=2x2 --trim --trim-use=all file.pdf
-Copyright (C) 2010-2020 Armin Straub, http://arminstraub.com
+Automatically crop the margins every single page individually:
+ krop --go --selections=individual --grid=1@all --trim file.pdf
+Omit the --go to further edit the selections in the graphical interface before cropping.
+
+Copyright (C) 2010-2025 Armin Straub, http://arminstraub.com
"""
"""
@@ -23,7 +27,6 @@
import sys
from krop.version import __version__
-from krop.config import KDE
def main():
@@ -51,29 +54,19 @@
parser.add_argument('--go', action='store_true', help='output PDF without opening the krop GUI (using the choices supplied on the command line); if used in a script without X server access, you can run krop using xvfb-run')
- parser.add_argument('--no-kde', action='store_true', help='do not use KDE libraries (default: use if available)')
- parser.add_argument('--no-qt5', action='store_true', help='do not use PyQt5 instead of PyQt4 (default: use PyQt5 if available)')
- parser.add_argument('--no-PyPDF2', action='store_true', help='do not use PyPDF2 instead of pyPdf (default: use PyPDF2 if available)')
+ parser.add_argument('--use-qt5', action='store_true', help='use PyQt5 instead of PyQt6 (default: use PyQt6 if available)')
+ parser.add_argument('--use-pymupdf', action='store_true', help='use PyMuPDF for rendering and cropping (default)')
+ parser.add_argument('--use-pikepdf', action='store_true', help='use pikepdf for cropping (PyQt5 only, default: use PyMuPDF)')
+ parser.add_argument('--use-pypdf', action='store_true', help='use pypdf for cropping (PyQt5 only, default: use PyMuPDF)')
+ parser.add_argument('--use-pypdf2', action='store_true', help='use PyPDF2 for cropping (PyQt5 only, default: use PyMuPDF)')
+ parser.add_argument('--use-poppler', action='store_true', help='use Poppler Qt for rendering (PyQt5 only, default: use PyMuPDF)')
args = parser.parse_args()
- # start the GUI
- if KDE:
- #TODO also use PyKDE5 once more easily available
- from PyKDE4.kdecore import ki18n, KCmdLineArgs, KAboutData
- from PyKDE4.kdeui import KApplication
- appName = "krop"
- catalog = ""
- programName = ki18n("krop")
-
- aboutData = KAboutData(appName, catalog, programName, __version__)
-
- KCmdLineArgs.init(aboutData)
- app = KApplication()
- else:
- from krop.qt import QApplication
- app = QApplication(sys.argv)
- app.setApplicationName("krop")
+ from krop.qt import QApplication
+ app = QApplication(sys.argv)
+ app.setApplicationName("krop")
+ app.setApplicationDisplayName("krop")
app.setOrganizationName("arminstraub.com")
app.setOrganizationDomain("arminstraub.com")
@@ -83,11 +76,6 @@
if args.file is not None:
fileName = args.file
- try:
- fileName = fileName.decode(sys.stdin.encoding or sys.getdefaultencoding())
- except AttributeError:
- # not necessary (or possible) in python3, which uses unicode
- pass
window.openFile(fileName)
if args.output is not None:
@@ -137,5 +125,4 @@
window.show()
window.slotFitInView(window.ui.actionFitInView.isChecked())
- # using exec_ because exec is a reserved keyword before python 3
- sys.exit(app.exec_())
+ sys.exit(app.exec())
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/config.py krop-0.7.0/krop/config.py
--- krop-0.6.0/krop/config.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/config.py 2025-04-06 21:04:10.000000000 +0200
@@ -1,40 +1,17 @@
import sys
-try:
- # the following is only needed in python2
- # for PyQt5 >=5.11 and PyQt4 >= 4.12.2, PyQt uses a private copy of sip
- # that can be included as: from PyQt4 import sip
- # however, we don't need sip.setapi except in the old python2 setup
- import sip
- sip.setapi('QString', 2)
- sip.setapi('QVariant', 2)
-except ImportError:
- pass
+PYQT6 = False
-PYQT5 = False
try:
- # use PyQt5 unless not available or specified otherwise
- if '--no-qt5' not in sys.argv:
+ # use PyQt6 unless PyQt5 is specified
+ if '--use-qt5' not in sys.argv:
try:
- from PyQt5 import QtCore
- PYQT5 = True
+ from PyQt6 import QtCore
+ PYQT6 = True
except ImportError:
pass
- if not PYQT5:
- from PyQt4 import QtCore
+ if not PYQT6:
+ from PyQt5 import QtCore
except ImportError:
- _msg = "Please install PyQt4 or PyQt5 first."
- raise RuntimeError(_msg)
-
-# use KDE unless not available or specified otherwise
-KDE = False
-if '--no-kde' not in sys.argv:
- if PYQT5:
- #TODO use PyKDE5 once more easily available
- pass
- else:
- try:
- import PyKDE4
- KDE = True
- except ImportError:
- pass
+ _msg = "Please install PyQt6 (or PyQt5) first."
+ raise RuntimeError(_msg)
\ Pas de fin de ligne à la fin du fichier
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/mainwindow.py krop-0.7.0/krop/mainwindow.py
--- krop-0.6.0/krop/mainwindow.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/mainwindow.py 2025-04-06 21:04:10.000000000 +0200
@@ -3,7 +3,7 @@
"""
The main window of krop
-Copyright (C) 2010-2020 Armin Straub, http://arminstraub.com
+Copyright (C) 2010-2025 Armin Straub, http://arminstraub.com
"""
"""
@@ -15,31 +15,15 @@
import sys
from os.path import exists, splitext
-
-try:
- from shutil import which
-except:
- # shutil is not available in python2; instead we use the following:
- from distutils.spawn import find_executable as which
-
-try:
- str_unicode = unicode
-except:
- str_unicode = str
-
+from shutil import which
from krop.qt import *
-from krop.config import PYQT5, KDE
+from krop.config import PYQT6
-if KDE:
- from PyKDE4.kdeui import KMainWindow as QKMainWindow
+if PYQT6:
+ from krop.mainwindowui_qt6 import Ui_MainWindow
else:
- QKMainWindow = QMainWindow
-
-if PYQT5:
from krop.mainwindowui_qt5 import Ui_MainWindow
-else:
- from krop.mainwindowui_qt4 import Ui_MainWindow
from krop.viewerselections import ViewerSelections, aspectRatioFromStr
from krop.vieweritem import ViewerItem
@@ -133,12 +117,12 @@
self.addType("Nook 1st Ed. (widescreen)", 730, 600)
-class MainWindow(QKMainWindow):
+class MainWindow(QMainWindow):
fileName = None
def __init__(self):
- QKMainWindow.__init__(self)
+ QMainWindow.__init__(self)
self.selAspectRatioTypes = SelAspectRatioTypeManager()
self.deviceTypes = DeviceTypeManager()
@@ -335,7 +319,7 @@
self.viewer.load(fileName)
if not self.viewer.isEmpty():
self.fileName = fileName
- outputFileName = "%s-cropped.pdf" % splitext(str_unicode(fileName))[0]
+ outputFileName = "%s-cropped.pdf" % splitext(fileName)[0]
self.slotFitInView(self.ui.actionFitInView.isChecked())
else:
self.fileName = ''
@@ -343,6 +327,7 @@
self.showWarning(self.tr("Something got in our way"),
self.tr("The PDF file couldn't be read. "
"Please check the file and its permissions."))
+ self.setWindowFilePath(self.fileName)
self.ui.actionKrop.setEnabled(not self.viewer.isEmpty())
self.ui.actionTrimMarginsAll.setEnabled(not self.viewer.isEmpty())
self.ui.editFile.setText(outputFileName)
@@ -351,10 +336,7 @@
def slotOpenFile(self):
fileName = QFileDialog.getOpenFileName(self,
self.tr("Open PDF"), "", self.tr("PDF Files (*.pdf)"));
- # in PyQt5, getOpenFileName is what used to be
- # getOpenFileNameAndFilter
- if PYQT5:
- fileName = fileName[0]
+ fileName = fileName[0]
self.openFile(fileName)
def slotSelectFile(self):
@@ -388,11 +370,11 @@
def slotKrop(self):
# file names
- inputFileName = str_unicode(self.fileName)
- outputFileName = str_unicode(self.ui.editFile.text())
+ inputFileName = self.fileName
+ outputFileName = self.ui.editFile.text()
# which pages
- s = str(self.ui.editWhichPages.text())
+ s = self.ui.editWhichPages.text()
if not s:
pages = range(self.viewer.numPages())
else:
@@ -408,11 +390,12 @@
# self.tr("A file named \"...\" already exists. Are you sure you want to overwrite it?"))
# return
- QApplication.setOverrideCursor(Qt.WaitCursor)
+ QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
try:
pdf = PdfFile()
pdf.loadFromFile(inputFileName)
cropper = PdfCropper()
+ cropper.copyDocumentRoot(pdf)
for nr in pages:
c = self.viewer.cropValues(nr)
cropper.addPageCropped(pdf, nr, c, alwaysinclude, rotation)
@@ -460,7 +443,7 @@
def slotFitInView(self, checked):
if checked:
self.ui.documentView.fitInView(self.pdfScene.sceneRect(),
- Qt.KeepAspectRatio)
+ Qt.AspectRatioMode.KeepAspectRatio)
def slotSplitterMoved(self, pos, idx):
self.slotFitInView(self.ui.actionFitInView.isChecked())
@@ -511,13 +494,13 @@
self.ui.editSelExceptions.setEnabled(enableExceptions)
def slotSelExceptionsChanged(self):
- s = str(self.ui.editSelExceptions.text())
+ s = self.ui.editSelExceptions.text()
pages = self.str2pages(s)
self.selections.selectionExceptions = pages
def slotSelExceptionsEdited(self, text):
try:
- pages = self.str2pages(str(text))
+ pages = self.str2pages(text)
self.selections.selectionExceptions = pages
except ValueError:
pass
@@ -694,7 +677,7 @@
if self.ui.checkTrimUseAllPages.isChecked():
pages = [i for i in range(self.viewer.numPages()) if sel.selectionVisibleOnPage(i)]
- QApplication.setOverrideCursor(Qt.WaitCursor)
+ QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
try:
# orect is the original selection, nrect is the trimmed version
orect = sel.mapRectToImage(sel.rect).toRect()
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/mainwindow.ui krop-0.7.0/krop/mainwindow.ui
--- krop-0.6.0/krop/mainwindow.ui 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/mainwindow.ui 2025-04-06 21:04:10.000000000 +0200
@@ -10,9 +10,6 @@
<height>736</height>
</rect>
</property>
- <property name="windowTitle">
- <string>krop: A tool to crop your PDFs</string>
- </property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
@@ -440,7 +437,7 @@
<p>Right-click a selection to delete it. Or simply press the Delete key.</p>
<p>You can choose to create individual selections for each page.</p>
<p>You can automatically trim the margins of your selections.</p>
-<p>Examples and more information can be found at: <a href='http://arminstraub.com/software/krop'>arminstraub.com</a></p>
+<p>Examples and more information can be found at: <a href='https://arminstraub.com/software/krop'>arminstraub.com</a></p>
</string>
</property>
<property name="textFormat">
@@ -486,8 +483,8 @@
<string><p>This program is free software and available to you in the hope that it will be useful; but without any warranty. It is distributed under the terms of the GNU General Public License (GPLv3+). See the accompanying files for more information.</p></string>
</property>
<property name="text">
- <string><p>Copyright (C) 2010-2020 Armin Straub
-<br><a href='http://arminstraub.com'>http://arminstraub.com</a></p></string>
+ <string><p>Copyright (C) 2010-2025 Armin Straub
+<br><a href='https://arminstraub.com'>https://arminstraub.com</a></p></string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/mainwindowui_qt5.py krop-0.7.0/krop/mainwindowui_qt5.py
--- krop-0.6.0/krop/mainwindowui_qt5.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/mainwindowui_qt5.py 2025-04-06 21:04:10.000000000 +0200
@@ -2,13 +2,15 @@
# Form implementation generated from reading ui file 'mainwindow.ui'
#
-# Created: Sat Jun 6 20:27:18 2020
-# by: PyQt5 UI code generator 5.2.1
+# Created by: PyQt5 UI code generator 5.15.6
#
-# WARNING! All changes made in this file will be lost!
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
from PyQt5 import QtCore, QtGui, QtWidgets
+
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
@@ -365,16 +367,15 @@
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(0)
- self.buttonPrevious.clicked.connect(self.actionPreviousPage.trigger)
- self.buttonNext.clicked.connect(self.actionNextPage.trigger)
- self.buttonFirst.clicked.connect(self.actionFirstPage.trigger)
- self.buttonLast.clicked.connect(self.actionLastPage.trigger)
- self.buttonFileSelect.clicked.connect(self.actionSelectFile.trigger)
+ self.buttonPrevious.clicked.connect(self.actionPreviousPage.trigger) # type: ignore
+ self.buttonNext.clicked.connect(self.actionNextPage.trigger) # type: ignore
+ self.buttonFirst.clicked.connect(self.actionFirstPage.trigger) # type: ignore
+ self.buttonLast.clicked.connect(self.actionLastPage.trigger) # type: ignore
+ self.buttonFileSelect.clicked.connect(self.actionSelectFile.trigger) # type: ignore
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
- MainWindow.setWindowTitle(_translate("MainWindow", "krop: A tool to crop your PDFs"))
self.groupSaveTo.setTitle(_translate("MainWindow", "Save cropped PDF to"))
self.editFile.setToolTip(_translate("MainWindow", "<p>This is where the cropped PDF will be saved after you choose <i>Krop!</i> in the menu.</p>"))
self.buttonFileSelect.setToolTip(_translate("MainWindow", "<p>This is where the cropped PDF will be saved after you choose <i>Krop!</i> in the menu.</p>"))
@@ -426,11 +427,11 @@
"<p>Right-click a selection to delete it. Or simply press the Delete key.</p>\n"
"<p>You can choose to create individual selections for each page.</p>\n"
"<p>You can automatically trim the margins of your selections.</p>\n"
-"<p>Examples and more information can be found at: <a href=\'http://arminstraub.com/software/krop\'>arminstraub.com</a></p>\n"
+"<p>Examples and more information can be found at: <a href=\'https://arminstraub.com/software/krop\'>arminstraub.com</a></p>\n"
""))
self.labelHelpCopyright.setToolTip(_translate("MainWindow", "<p>This program is free software and available to you in the hope that it will be useful; but without any warranty. It is distributed under the terms of the GNU General Public License (GPLv3+). See the accompanying files for more information.</p>"))
- self.labelHelpCopyright.setText(_translate("MainWindow", "<p>Copyright (C) 2010-2020 Armin Straub\n"
-"<br><a href=\'http://arminstraub.com\'>http://arminstraub.com</a></p>"))
+ self.labelHelpCopyright.setText(_translate("MainWindow", "<p>Copyright (C) 2010-2025 Armin Straub\n"
+"<br><a href=\'https://arminstraub.com\'>https://arminstraub.com</a></p>"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabHelp), _translate("MainWindow", "Help"))
self.label.setText(_translate("MainWindow", "of"))
self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
@@ -463,4 +464,3 @@
self.actionNewSelectionGrid.setText(_translate("MainWindow", "New Selection Grid..."))
self.actionNewSelectionGrid.setToolTip(_translate("MainWindow", "New Selection Grid..."))
self.actionNewSelectionGrid.setShortcut(_translate("MainWindow", "Shift+Ins"))
-
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/pdfcropper.py krop-0.7.0/krop/pdfcropper.py
--- krop-0.6.0/krop/pdfcropper.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/pdfcropper.py 2025-04-06 21:04:10.000000000 +0200
@@ -3,7 +3,7 @@
"""
Cropping functionality for krop.
-Copyright (C) 2010-2020 Armin Straub, http://arminstraub.com
+Copyright (C) 2010-2025 Armin Straub, http://arminstraub.com
"""
"""
@@ -16,26 +16,13 @@
import copy
import sys
-# Unless specified otherwise, use PyPDF2 instead of pyPdf if available.
-usepypdf2 = '--no-PyPDF2' not in sys.argv
-if usepypdf2:
- try:
- from PyPDF2 import PdfFileReader, PdfFileWriter
- except ImportError:
- usepypdf2 = False
-if not usepypdf2:
- try:
- from pyPdf import PdfFileReader, PdfFileWriter
- except ImportError:
- _msg = "Please install PyPDF2 (or its predecessor pyPdf) first."\
- "\n\tOn recent versions of Ubuntu, the following should do the trick:"\
- "\n\tsudo apt-get install python-pypdf2"\
- "\n\t(or, if using python3) sudo apt-get install python3-pypdf2"
- raise RuntimeError(_msg)
+from krop.config import PYQT6
+
class PdfEncryptedError(Exception):
pass
+
class AbstractPdfFile:
"""Abstract class for loading a PDF document used in a corresponding
PdfCropper class"""
@@ -44,6 +31,58 @@
def loadFromFile(self, filename):
self.loadFromStream(open(filename, "rb"))
+class PyPdfFile(AbstractPdfFile):
+ """Implementation of PdfFile using the new pypdf"""
+ def __init__(self):
+ self.reader = None
+ def loadFromStream(self, stream):
+ self.reader = self.PdfReader(stream)
+ if self.reader.is_encrypted:
+ try:
+ if not self.reader.decrypt(''):
+ raise PdfEncryptedError
+ except:
+ raise PdfEncryptedError
+ def getPage(self, nr):
+ return self.reader.pages[nr]
+
+class PyPdfOldFile(PyPdfFile):
+ """Implementation of PdfFile using PyPDF2 (<2) which uses camelCase rather
+ than snake_case"""
+ def loadFromStream(self, stream):
+ self.reader = self.PdfReader(stream, strict=False)
+ if self.reader.isEncrypted:
+ try:
+ if not self.reader.decrypt(''):
+ raise PdfEncryptedError
+ except:
+ raise PdfEncryptedError
+ def getPage(self, nr):
+ return self.reader.getPage(nr)
+
+class PyMuPdfFile(AbstractPdfFile):
+ """Implementation of PdfFile using PyMuPDF"""
+ def __init__(self):
+ self.reader = None
+ def loadFromStream(self, stream):
+ self.reader = self.pymupdf.open(stream)
+ if self.reader.is_encrypted:
+ raise PdfEncryptedError
+ def getPage(self, nr):
+ return self.reader[nr]
+
+class PikePdfFile(AbstractPdfFile):
+ """Implementation of PdfFile using pikepdf"""
+ def __init__(self):
+ self.reader = None
+ def loadFromStream(self, stream):
+ self.reader = self.Pdf.open(stream)
+ if self.reader.is_encrypted:
+ raise PdfEncryptedError
+ def getPage(self, nr):
+ return self.reader.pages[nr]
+
+
class AbstractPdfCropper:
"""Abstract class for writing a PDF documents composed of cropped pages
from PdfFile instances"""
@@ -55,66 +94,236 @@
stream.close()
def addPageCropped(self, pdffile, pagenumber, croplist, rotate=0):
pass
+ def copyDocumentRoot(self, pdffile):
+ pass
-
-class PyPdfFile(AbstractPdfFile):
- """Implementation of PdfFile using pyPdf"""
- def __init__(self):
- self.reader = None
- def loadFromStream(self, stream):
- if usepypdf2:
- self.reader = PdfFileReader(stream, strict=False)
+class SemiAbstractPdfCropper(AbstractPdfCropper):
+ """An extension of AbstractPdfCropper that implements the basic logic
+ breaking it down to certain functions on pages"""
+ def addPageCropped(self, pdffile, pagenumber, croplist, alwaysinclude, rotate=0):
+ page = pdffile.getPage(pagenumber)
+ if not croplist and alwaysinclude:
+ self.doAddPage(page, rotate)
else:
- self.reader = PdfFileReader(stream)
- if self.reader.isEncrypted:
- try:
- if not self.reader.decrypt(''):
- raise PdfEncryptedError
- except:
- raise PdfEncryptedError
- def getPage(self, nr):
- page = self.reader.getPage(nr-1)
+ box = self.pageGetCropBox(page)
+ for crop in croplist:
+ new_page = copy.copy(page)
+ new_box = computeCropBoxCoords(box, crop)
+ self.pageSetCropBox(new_page, new_box)
+ self.doAddPage(new_page, rotate)
+ def doAddPage(self, page, rotate):
+ pass
+ def pageGetCropBox(self, page):
+ pass
+ def pageSetCropBox(self, page, box):
+ pass
-class PyPdfCropper(AbstractPdfCropper):
- """Implementation of PdfCropper using pyPdf"""
+class PyPdfCropper(SemiAbstractPdfCropper):
+ """Implementation of PdfCropper using pypdf"""
def __init__(self):
- self.output = PdfFileWriter()
+ self.output = self.PdfWriter()
def writeToStream(self, stream):
- # For certain large pdf files, PdfFileWriter.write() causes the error:
- # maximum recursion depth exceeded while calling a Python object
- # This issue is present in pyPdf as well as PyPDF2 1.23
+ # For certain large pdf files, PdfWriter.write() causes the error:
+ # maximum recursion depth exceeded while calling a Python object
+ # This issue is present, for instance, in PyPDF2 1.23.
# We therefore temporarily increase the recursion limit.
old_reclimit = sys.getrecursionlimit()
sys.setrecursionlimit(10000)
self.output.write(stream)
sys.setrecursionlimit(old_reclimit)
- def addPageCropped(self, pdffile, pagenumber, croplist, alwaysinclude, rotate=0):
- page = pdffile.reader.getPage(pagenumber)
- if not croplist and alwaysinclude:
- self.output.addPage(page)
- for c in croplist:
- newpage = copy.copy(page)
- self.cropPage(newpage, c, rotate)
- self.output.addPage(newpage)
- def cropPage(self, page, crop, rotate):
- # Note that the coordinate system is up-side down compared with Qt.
+ def doAddPage(self, page, rotate):
+ if rotate != 0:
+ page.rotate(rotate)
+ self.output.add_page(page)
+ def pageGetCropBox(self, page):
+ x0, y0 = page.cropbox.lower_left
+ x1, y1 = page.cropbox.upper_right
+ return x0, y0, x1, y1
+ def pageSetCropBox(self, page, box):
+ x0, y0, x1, y1 = box
+ for page_box in (page.artbox, page.bleedbox, page.cropbox, page.mediabox, page.trimbox):
+ page_box.lower_left = (x0, y0)
+ page_box.upper_right = (x1, y1)
+ def copyDocumentRoot(self, pdffile):
+ # Copy the named destinations for links.
+ # TODO: this worked for links in PyPDF2 but doesn't seem to work for pypdf
+ for dest in pdffile.reader.named_destinations.values():
+ self.output.add_named_destination_object(dest)
+
+class PyPdfOldCropper(PyPdfCropper):
+ """Implementation of PdfCropper using PyPDF2 (<2)"""
+ def doAddPage(self, page, rotate):
+ if rotate != 0:
+ page.rotateClockwise(rotate)
+ self.output.addPage(page)
+ def pageGetCropBox(self, page):
x0, y0 = page.cropBox.lowerLeft
x1, y1 = page.cropBox.upperRight
- x0, y0, x1, y1 = float(x0), float(y0), float(x1), float(y1)
- x0, x1 = x0+crop[0]*(x1-x0), x1-crop[2]*(x1-x0)
- y0, y1 = y0+crop[3]*(y1-y0), y1-crop[1]*(y1-y0)
- # Update the various PDF boxes
- for box in (page.artBox, page.bleedBox, page.cropBox, page.mediaBox, page.trimBox):
- box.lowerLeft = (x0, y0)
- box.upperRight = (x1, y1)
+ return x0, y0, x1, y1
+ def pageSetCropBox(self, page, box):
+ x0, y0, x1, y1 = box
+ for page_box in (page.artBox, page.bleedBox, page.cropBox, page.mediaBox, page.trimBox):
+ page_box.lowerLeft = (x0, y0)
+ page_box.upperRight = (x1, y1)
+ def copyDocumentRoot(self, pdffile):
+ # Copy the named destinations for links.
+ for dest in pdffile.reader.namedDestinations.values():
+ self.output.addNamedDestinationObject(dest)
+
+
+class PyMuPdfCropper(SemiAbstractPdfCropper):
+ """Implementation of PdfCropper using PyMuPDF"""
+ def __init__(self):
+ self.output = self.pymupdf.open()
+ def writeToStream(self, stream):
+ self.output.save(stream)
+ def addPageCropped(self, pdffile, pagenumber, croplist, alwaysinclude, rotate=0):
+ def addPage():
+ # https://pymupdf.readthedocs.io/en/latest/the-basics.html
+ r = pdffile.reader[pagenumber].rotation + rotate
+ self.output.insert_pdf(pdffile.reader, from_page=pagenumber, to_page=pagenumber, rotate=r)
+ if not croplist and alwaysinclude:
+ addPage()
+ else:
+ for crop in croplist:
+ addPage()
+ new_page = self.output[-1]
+ box = self.pageGetCropBox(new_page)
+ # MuPDF uses coordinates where (0,0) is the top-right point, unlike
+ # PDF where (0,0) is the bottom-left.
+ new_box = computeCropBoxCoords(box, crop, pdf_coords=False)
+ self.pageSetCropBox(new_page, new_box)
+ # if rotate != 0:
+ # self.pageRotateClockwise(new_page, rotate)
+ def pageGetCropBox(self, page):
+ return page.cropbox
+ def pageSetCropBox(self, page, box):
+ page.set_cropbox(box)
+ try:
+ page.set_artbox(page.cropbox)
+ page.set_bleedbox(page.cropbox)
+ # careful: mediabox in MuPDF is an exception and uses PDF coordinates
+ # page.set_mediabox(page.cropbox)
+ page.set_trimbox(page.cropbox)
+ except:
+ # these functions did not exist prior to v1.19.4
+ pass
+ def copyDocumentRoot(self, pdffile):
+ pass
+
+
+class PikePdfCropper(SemiAbstractPdfCropper):
+ """Implementation of PdfCropper using pikepdf"""
+ def __init__(self):
+ self.output = self.Pdf.new()
+ def writeToStream(self, stream):
+ self.output.save(stream)
+ def doAddPage(self, page, rotate):
if rotate != 0:
- page.rotateClockwise(rotate)
+ page.rotate(rotate, relative=True)
+ self.output.pages.append(page)
+ def pageGetCropBox(self, page):
+ try:
+ # only page.MediaBox exists in pikepdf version 1.10.3 as currently in Ubuntu 20.04
+ return page.cropbox
+ except AttributeError:
+ raise RuntimeError("Please install a more recent version of pikepdf.")
+ def pageSetCropBox(self, page, box):
+ page.cropbox = box
+ page.mediabox = box
+ page.trimbox = box
+ def copyDocumentRoot(self, pdffile):
+ pass
+
+
+def computeCropBoxCoords(box, crop, pdf_coords=True):
+ x0, y0, x1, y1 = box
+ x0, y0, x1, y1 = float(x0), float(y0), float(x1), float(y1)
+ # In PDF coordinates (0,0) is the bottom-left point; otherwise, as in
+ # MuPDF or Qt, this would be the top-left point.
+ if not pdf_coords:
+ crop = (crop[0], crop[3], crop[2], crop[1])
+ x0, x1 = x0+crop[0]*(x1-x0), x1-crop[2]*(x1-x0)
+ y0, y1 = y0+crop[3]*(y1-y0), y1-crop[1]*(y1-y0)
+ return x0, y0, x1, y1
def optimizePdfGhostscript(oldfilename, newfilename):
import subprocess
subprocess.check_call(('gs', '-sDEVICE=pdfwrite', '-sOutputFile=' + newfilename,
'-dNOPAUSE', '-dBATCH', oldfilename))
-PdfFile = PyPdfFile
-PdfCropper = PyPdfCropper
+
+# In the following, we determine which cropping library to use.
+# See lib_crop_options below for a list of the supported libraries.
+
+def import_pymupdf():
+ import fitz
+ # note: since 1.24.3, one can use 'import pymupdf' instead but fitz is
+ # promised to always be supported as well
+ PyMuPdfFile.pymupdf = fitz
+ PyMuPdfCropper.pymupdf = fitz
+ return PyMuPdfFile, PyMuPdfCropper
+
+def import_pikepdf():
+ from pikepdf import Pdf
+ PikePdfFile.Pdf = Pdf
+ PikePdfCropper.Pdf = Pdf
+ return PikePdfFile, PikePdfCropper
+
+def import_pypdf():
+ from pypdf import PdfReader, PdfWriter
+ PyPdfFile.PdfReader = PdfReader
+ PyPdfCropper.PdfWriter = PdfWriter
+ return PyPdfFile, PyPdfCropper
+
+def import_pypdf2():
+ import PyPDF2
+ # PyPDF2 (<2) uses camelCase while newer versions (just like pypdf) use snake_case
+ if PyPDF2.__version__.startswith("1."):
+ from PyPDF2 import PdfFileReader as PdfReader, PdfFileWriter as PdfWriter
+ PdfFile = PyPdfOldFile
+ PdfCropper = PyPdfOldCropper
+ else:
+ from PyPDF2 import PdfReader, PdfWriter
+ PdfFile = PyPdfFile
+ PdfCropper = PyPdfCropper
+ PdfFile.PdfReader = PdfReader
+ PdfCropper.PdfWriter = PdfWriter
+ return PdfFile, PdfCropper
+
+# for each cropping library: [name, import function, flag to request it]
+lib_crop_options = [
+ ['PyMuPDF', import_pymupdf, '--use-pymupdf'],
+ ['pikepdf', import_pikepdf, '--use-pikepdf'],
+ ['pypdf', import_pypdf, '--use-pypdf'],
+ ['PyPDF2', import_pypdf2, '--use-pypdf2'],
+ ]
+# lib_crop will be set to the name of the cropping library in use
+lib_crop = None
+
+# We go through all options for libraries twice:
+# during the first round, we load a library if it is specifically requested,
+# and during the second round, we load the first available library.
+for load_only_if_requested in [True, False]:
+ for lib, import_func, flag in lib_crop_options:
+ if not lib_crop and (not load_only_if_requested or (flag and flag in sys.argv)):
+ try:
+ PdfFile, PdfCropper = import_func()
+ lib_crop = lib
+ print(f"Using {lib} for cropping.", file=sys.stderr)
+ except ImportError:
+ if load_only_if_requested:
+ print(f"{lib} was requested but failed to load.", file=sys.stderr)
+
+# complain if no library could be imported
+if not lib_crop:
+ _msg = "Please install one of the supported cropping libraries first (PyMuPDF, pypdf, or pikepdf)."
+ if PYQT6:
+ _msg += "\n\tFor instance, on recent versions of Ubuntu, the following should do the trick:"\
+ "\n\tsudo apt-get install python3-pymupdf"
+ else:
+ _msg += "\n\tFor instance, on versions of Ubuntu such as 22.04, one of the following should do the trick:"\
+ "\n\tsudo apt install python3-fitz"\
+ "\n\tsudo apt-get install python3-pypdf2"
+ raise RuntimeError(_msg)
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/qt.py krop-0.7.0/krop/qt.py
--- krop-0.6.0/krop/qt.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/qt.py 2025-04-06 21:04:10.000000000 +0200
@@ -1,10 +1,10 @@
-from krop.config import PYQT5
+from krop.config import PYQT6
-if PYQT5:
+if PYQT6:
+ from PyQt6.QtCore import *
+ from PyQt6.QtGui import *
+ from PyQt6.QtWidgets import *
+else:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
- # QApplication now resides in the new QtWidgets
from PyQt5.QtWidgets import *
-else:
- from PyQt4.QtCore import *
- from PyQt4.QtGui import *
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/version.py krop-0.7.0/krop/version.py
--- krop-0.6.0/krop/version.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/version.py 2025-04-06 21:04:10.000000000 +0200
@@ -1 +1 @@
-__version__ = '0.5.1'
+__version__ = '0.7.0'
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/vieweritem.py krop-0.7.0/krop/vieweritem.py
--- krop-0.6.0/krop/vieweritem.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/vieweritem.py 2025-04-06 21:04:10.000000000 +0200
@@ -3,7 +3,7 @@
"""
Viewer for krop used to display PDF files.
-Copyright (C) 2010-2020 Armin Straub, http://arminstraub.com
+Copyright (C) 2010-2025 Armin Straub, http://arminstraub.com
"""
"""
@@ -13,24 +13,10 @@
(at your option) any later version.
"""
-from krop.config import PYQT5
-from krop.qt import *
+import sys
-if PYQT5:
- try:
- from popplerqt5 import Poppler
- except ImportError:
- _msg = "Please install popplerqt5 first."
- raise RuntimeError(_msg)
-else:
- try:
- from popplerqt4 import Poppler
- except ImportError:
- _msg = "Please install popplerqt4 first."\
- "\n\tOn recent versions of Ubuntu, the following should do the trick:"\
- "\n\tsudo apt-get install python-poppler-qt4"\
- "\n\t(or, if using python3) sudo apt-get install python3-poppler-qt4"
- raise RuntimeError(_msg)
+from krop.config import PYQT6
+from krop.qt import *
from krop.viewerselections import ViewerSelections
@@ -143,8 +129,22 @@
def cacheImage(self, idx):
return None
+ def pageGetRotation(self, idx):
+ return 0
+
def cropValues(self, idx):
- return self.selections.cropValues(idx)
+ def adjustForOrientation(cv):
+ if r == 90: # Landscape
+ return [ cv[1], cv[2], cv[3], cv[0] ]
+ elif r == 180: # UpsideDown
+ return [ cv[2], cv[3], cv[0], cv[1] ]
+ elif r == 270: # Seascape
+ return [ cv[3], cv[0], cv[1], cv[2] ]
+ else: # r == 0, Portrait
+ return cv
+ crop_values = self.selections.cropValues(idx)
+ r = self.pageGetRotation(idx)
+ return [ adjustForOrientation(cv) for cv in crop_values ]
class PopplerViewerItem(AbstractViewerItem):
@@ -167,23 +167,98 @@
def cacheImage(self, idx):
page = self._pdfdoc.page(idx)
- return page.renderToImage(96.0, 96.0)
- # return page.renderToImage()
+ return page.renderToImage(96.0, 96.0) # dpi = 96
+ # return page.renderToImage() # default dpi = 72
- def cropValues(self, idx):
- def adjustForOrientation(cv):
- if o == page.Landscape:
- return [ cv[1], cv[2], cv[3], cv[0] ]
- elif o == page.UpsideDown:
- return [ cv[2], cv[3], cv[0], cv[1] ]
- elif o == page.Seascape:
- return [ cv[3], cv[0], cv[1], cv[2] ]
- else: # o == page.Portrait
- return cv
+ def pageGetRotation(self, idx):
page = self._pdfdoc.page(idx)
o = page.orientation()
- return [ adjustForOrientation(cv)
- for cv in self.selections.cropValues(idx) ]
+ if o == page.Landscape:
+ return 90
+ elif o == page.UpsideDown:
+ return 180
+ elif o == page.Seascape:
+ return 270
+ else: # o == page.Portrait
+ return 0
+
+
+class MuPDFViewerItem(AbstractViewerItem):
+ """Viewer implementation which uses PyMuPDF to display PDF documents."""
+ def reset(self):
+ AbstractViewerItem.reset(self)
+ self._pdfdoc = None
+ def doLoad(self, filename):
+ self._pdfdoc = fitz.open(filename)
+ # if self._pdfdoc:
+ # self._pdfdoc.setRenderHint(Poppler.Document.Antialiasing and
+ # Poppler.Document.TextAntialiasing)
+
+ def numPages(self):
+ if self._pdfdoc is None:
+ return 0
+ else:
+ return len(self._pdfdoc)
-ViewerItem = PopplerViewerItem
+ def cacheImage(self, idx):
+ page = self._pdfdoc[idx]
+ pix = page.get_pixmap(alpha=False, dpi=96) # default dpi is 72
+ return QImage(pix.samples, pix.width, pix.height, pix.stride, QImage.Format.Format_RGB888)
+ # It might be faster to use samples_ptr but the code results in crashes.
+ # https://pymupdf.readthedocs.io/en/latest/tutorial.html
+ # pix = page.get_pixmap()
+ # set the correct QImage format depending on alpha
+ # fmt = QImage.Format_RGBA8888 if pix.alpha else QImage.Format.Format_RGB888
+ # return QImage(pix.samples_ptr, pix.width, pix.height, fmt)
+
+ def pageGetRotation(self, idx):
+ page = self._pdfdoc[idx]
+ return page.rotation
+
+
+# determine whether to use PopplerQt or PyMuPDF for rendering
+POPPLERQT = 1
+PYMUPDF = 2
+lib_render = 0
+
+from krop.config import PYQT6
+
+# for PyQt6 use PyMuPDF
+if PYQT6:
+ try:
+ import fitz
+ lib_render = PYMUPDF
+ except ImportError:
+ _msg = "Please install PyMuPDF first (PyQt6 is being used)."\
+ "\n\tOn recent versions of Ubuntu, the following should do the trick:"\
+ "\n\tsudo apt-get install python3-pymupdf"
+ raise RuntimeError(_msg)
+else:
+ # PyQt5 was requested
+ if not '--use-poppler' in sys.argv:
+ try:
+ import fitz
+ lib_render = PYMUPDF
+ except ImportError:
+ pass
+ if not lib_render:
+ try:
+ from popplerqt5 import Poppler
+ lib_render = POPPLERQT
+ except ImportError:
+ pass
+ # complain if no version is available
+ if not lib_render:
+ _msg = "Please install PyMuPDF or Poppler Qt first (PyQt5 is being used)."\
+ "\n\tOn versions of Ubuntu such as 22.04, one of the following should do the trick:"\
+ "\n\tsudo apt install python3-fitz"\
+ "\n\tsudo apt install python3-poppler-qt5"
+ raise RuntimeError(_msg)
+
+if lib_render == PYMUPDF:
+ ViewerItem = MuPDFViewerItem
+ print("Using PyMuPDF for rendering.", file=sys.stderr)
+else:
+ ViewerItem = PopplerViewerItem
+ print("Using PopplerQt for rendering.", file=sys.stderr)
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop/viewerselections.py krop-0.7.0/krop/viewerselections.py
--- krop-0.6.0/krop/viewerselections.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop/viewerselections.py 2025-04-06 21:04:10.000000000 +0200
@@ -131,7 +131,7 @@
for c in s.cropValues() ]
def mousePressEvent(self, event):
- if event.button() == Qt.LeftButton:
+ if event.button() == Qt.MouseButton.LeftButton:
pos = event.pos()
rect = QRectF(pos, QSizeF())
self.currentSelection = self.addSelection(rect)
@@ -154,7 +154,7 @@
"""An individual user-created selection"""
def __init__(self, parent, rect=None):
QGraphicsItem.__init__(self, parent)
- self.setFlag(QGraphicsItem.ItemIsFocusable)
+ self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsFocusable)
if rect is None:
rect = self.mapRectFromParent(self.viewer.irect)
@@ -176,7 +176,7 @@
SelectionCornerHandleItem(self, 1, 1)
self.adjustBoundingRect()
- self.setCursor(Qt.OpenHandCursor)
+ self.setCursor(Qt.CursorShape.OpenHandCursor)
@property
@@ -386,14 +386,14 @@
# outer dashed rectangle
outerPen = QPen()
- outerPen.setStyle(Qt.DashLine)
- painter.setPen(Qt.white)
+ outerPen.setStyle(Qt.PenStyle.DashLine)
+ painter.setPen(QColorConstants.White)
painter.drawRect(rect)
painter.setPen(outerPen)
painter.drawRect(rect)
def drawLine(pt1, pt2):
- painter.setPen(Qt.white)
+ painter.setPen(QColorConstants.White)
painter.drawLine(pt1, pt2)
painter.setPen(outerPen)
painter.drawLine(pt1, pt2)
@@ -403,9 +403,9 @@
brush = QBrush()
brush.setColor(QColor(0,0,0,50))
for r in self.distributeRect():
- brush.setStyle(even and Qt.BDiagPattern or Qt.FDiagPattern)
+ brush.setStyle(even and Qt.BrushStyle.BDiagPattern or Qt.BrushStyle.FDiagPattern)
painter.setBrush(brush)
- painter.setPen(Qt.NoPen)
+ painter.setPen(Qt.PenStyle.NoPen)
painter.drawRect(r)
if r.top() > self.rect.top():
drawLine(r.topLeft(),r.topRight())
@@ -416,10 +416,10 @@
# inner number
font = QFont()
font.setPointSize(20)
- font.setWeight(QFont.Bold)
+ font.setWeight(700)
painter.setPen(QColor(0,0,0,155))
painter.setFont(font)
- painter.drawText(rect, Qt.AlignCenter, str(self.orderIndex))
+ painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, str(self.orderIndex))
def mousePressEvent(self, event):
# happens after right-click (trim margins) and then clicking
@@ -427,10 +427,10 @@
if not self.boundingRect().contains(event.pos()):
self.lastPos = None
# event.ignore()
- elif event.button() == Qt.LeftButton:
+ elif event.button() == Qt.MouseButton.LeftButton:
pos = event.pos()
self.lastPos = pos
- self.setCursor(Qt.ClosedHandCursor)
+ self.setCursor(Qt.CursorShape.ClosedHandCursor)
self.setAsCurrent()
def mouseMoveEvent(self, event):
@@ -454,19 +454,19 @@
def mouseReleaseEvent(self, event):
self.lastPos = None
- self.setCursor(Qt.OpenHandCursor)
+ self.setCursor(Qt.CursorShape.OpenHandCursor)
def keyPressEvent(self, event):
accepted = False
- if event.modifiers() == Qt.ShiftModifier:
+ if event.modifiers() == Qt.KeyboardModifier.ShiftModifier:
accepted = True
- if event.key() == Qt.Key_Left:
+ if event.key() == Qt.Key.Key_Left:
self.moveBoundingRect(-1, 0)
- elif event.key() == Qt.Key_Right:
+ elif event.key() == Qt.Key.Key_Right:
self.moveBoundingRect(1, 0)
- elif event.key() == Qt.Key_Down:
+ elif event.key() == Qt.Key.Key_Down:
self.moveBoundingRect(0, 1)
- elif event.key() == Qt.Key_Up:
+ elif event.key() == Qt.Key.Key_Up:
self.moveBoundingRect(0, -1)
else:
accepted = False
@@ -495,9 +495,9 @@
self.ac = self.ah
if self.role==SelectionHandleItem.LeftHandle or self.role==SelectionHandleItem.RightHandle:
- self.setCursor(Qt.SizeHorCursor)
+ self.setCursor(Qt.CursorShape.SizeHorCursor)
elif self.role==SelectionHandleItem.TopHandle or self.role==SelectionHandleItem.BottomHandle:
- self.setCursor(Qt.SizeVerCursor)
+ self.setCursor(Qt.CursorShape.SizeVerCursor)
@property
def selection(self):
@@ -549,7 +549,7 @@
painter.drawConvexPolygon(QPolygonF([pt+p for p in pts]))
def mousePressEvent(self, event):
- if event.button() == Qt.LeftButton:
+ if event.button() == Qt.MouseButton.LeftButton:
pos = event.pos()
self.lastPos = pos
self.selection.setAsCurrent()
@@ -586,7 +586,7 @@
# size (half, that is)
self.bs = 4
- self.setCursor((Qt.SizeFDiagCursor, Qt.SizeBDiagCursor)[(lr+tb)%2])
+ self.setCursor((Qt.CursorShape.SizeFDiagCursor, Qt.CursorShape.SizeBDiagCursor)[(lr+tb)%2])
@property
def selection(self):
@@ -621,7 +621,7 @@
painter.drawRect(rect)
def mousePressEvent(self, event):
- if event.button() == Qt.LeftButton:
+ if event.button() == Qt.MouseButton.LeftButton:
pos = event.pos()
self.lastPos = pos
self.selection.setAsCurrent()
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop.1 krop-0.7.0/krop.1
--- krop-0.6.0/krop.1 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop.1 2025-04-06 21:04:10.000000000 +0200
@@ -12,10 +12,11 @@
command.
.PP
\fBkrop\fP is a simple graphical tool to crop the pages of PDF files. It is written
-in Python and relies on PyQT, python-poppler-qt5 and PyPDF2 for its functionality.
-A unique feature of krop is its ability to automatically split pages into subpages
-to fit the limited screen size of devices such as eReaders. This is particularly
-useful, if your eReader does not support convenient scrolling.
+in Python and relies on PyQt and PyMuPDF (or a suitable subset of pypdf/pikepdf/
+python-poppler-qt) for its functionality. A unique feature of krop is its
+ability to automatically split pages into subpages to fit the limited screen
+size of devices such as eReaders. This is particularly useful, if your eReader
+does not support convenient scrolling.
.SH OPTIONS
A summary of options is included below.
For a complete description, use the \fB\-\-help\fP option.
@@ -66,12 +67,3 @@
.TP
.B \-\-go
output PDF without opening the krop GUI (using the choices supplied on the command line); if used in a script without X server access, you can run krop using xvfb\-run
-.TP
-.B \-\-no\-kde
-do not use KDE libraries (default: use if available)
-.TP
-.B \-\-no\-qt5
-do not use PyQt5 instead of PyQt4 (default: use PyQt5 if available)
-.TP
-.B \-\-no\-PyPDF2
-do not use PyPDF2 instead of pyPdf (default: use PyPDF2 if available)
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop.appdata.xml krop-0.7.0/krop.appdata.xml
--- krop-0.6.0/krop.appdata.xml 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop.appdata.xml 1970-01-01 01:00:00.000000000 +0100
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright 2014-2020 Armin Straub <mail@arminstraub.com> -->
-<component>
- <id>com.arminstraub.krop</id>
- <launchable type="desktop-id">com.arminstraub.krop.desktop</launchable>
- <metadata_license>CC0-1.0</metadata_license>
- <project_license>GPL-3.0+</project_license>
- <name>krop</name>
- <summary>A tool to crop PDF files</summary>
- <description>
- <p>
- krop is a simple graphical tool to crop the pages of PDF files.
- </p>
- <p>
- It is written in Python and relies on PyQT, python-poppler-qt5 and PyPDF2 for its functionality.
- </p>
- <p>
- A unique feature of krop is its ability to automatically split pages into subpages to fit the limited screen size of devices such as eReaders. This is particularly useful, if your eReader does not support convenient scrolling.
- </p>
- </description>
- <keywords>
- <keyword>pdf</keyword>
- <keyword>crop</keyword>
- <keyword>rotate</keyword>
- <keyword>ereader</keyword>
- </keywords>
- <screenshots>
- <screenshot type="default">
- <image type="source" width="1024" height="576">http://arminstraub.com/images/krop/screenshot-appdata.png</image>
- </screenshot>
- </screenshots>
- <url type="homepage">http://arminstraub.com/software/krop</url>
- <url type="bugtracker">https://github.com/arminstraub/krop/issues</url>
- <update_contact>mail@arminstraub.com</update_contact>
-</component>
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/krop.desktop krop-0.7.0/krop.desktop
--- krop-0.6.0/krop.desktop 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/krop.desktop 1970-01-01 01:00:00.000000000 +0100
@@ -1,11 +0,0 @@
-[Desktop Entry]
-Type=Application
-Name=krop
-GenericName=Crop PDF files
-Comment=A tool to crop PDF files
-TryExec=krop
-Exec=krop %F
-Icon=edit-cut
-MimeType=application/pdf;
-Categories=Qt;KDE;Utility;
-Keywords=pdf;crop;rotate;ereader;
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/MANIFEST.in krop-0.7.0/MANIFEST.in
--- krop-0.6.0/MANIFEST.in 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/MANIFEST.in 1970-01-01 01:00:00.000000000 +0100
@@ -1 +0,0 @@
-include ChangeLog LICENSE krop.desktop krop.appdata.xml krop/mainwindow.ui
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/README.md krop-0.7.0/README.md
--- krop-0.6.0/README.md 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/README.md 2025-04-06 21:04:10.000000000 +0200
@@ -2,9 +2,9 @@
krop is a simple graphical tool to crop the pages of PDF files.
-It is written in Python and relies on PyQT, python-poppler-qt5 and PyPDF2 for its functionality. A unique feature of krop is its ability to automatically split pages into subpages to fit the limited screen size of devices such as eReaders. This is particularly useful, if your eReader does not support convenient scrolling.
+It is written in Python and relies on PyQt and PyMuPDF (or a suitable subset of pypdf/pikepdf/python-poppler-qt) for its functionality. A unique feature of krop is its ability to automatically split pages into subpages to fit the limited screen size of devices such as eReaders. This is particularly useful, if your eReader does not support convenient scrolling.
For more information and how to install krop, see:
-[http://arminstraub.com/software/krop](http://arminstraub.com/software/krop)
+[https://arminstraub.com/software/krop](https://arminstraub.com/software/krop)
-
+
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/setup.py krop-0.7.0/setup.py
--- krop-0.6.0/setup.py 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/setup.py 2025-04-06 21:04:10.000000000 +0200
@@ -2,7 +2,7 @@
# -*- coding: iso-8859-1 -*-
"""
-Copyright (C) 2014-2020 Armin Straub, http://arminstraub.com
+Copyright (C) 2014-2025 Armin Straub, http://arminstraub.com
"""
"""
@@ -31,19 +31,21 @@
version = version,
author = 'Armin Straub',
author_email = 'mail@arminstraub.com',
- url = 'http://arminstraub.com/software/krop',
+ url = 'https://arminstraub.com/software/krop',
description = 'A tool to crop PDF files',
- long_description = 'krop is a simple graphical tool to crop the pages of PDF files. It is written in Python and relies on PyQT, python-poppler-qt5 and PyPDF2 for its functionality. A unique feature of krop is its ability to automatically split pages into subpages to fit the limited screen size of devices such as eReaders. This is particularly useful, if your eReader does not support convenient scrolling.',
+ long_description = 'krop is a simple graphical tool to crop the pages of PDF files. It is written in Python and relies on PyQt and PyMuPDF (or a suitable subset of pypdf/pikepdf/python-poppler-qt) for its functionality. A unique feature of krop is its ability to automatically split pages into subpages to fit the limited screen size of devices such as eReaders. This is particularly useful, if your eReader does not support convenient scrolling.',
# this is redundant but the egg/wheel produced by setuptools otherwise reports "License: UNKNOWN"
- license = 'GPLv3+',
+ license = 'GPL-3.0-or-later',
keywords = 'pdf crop ereader',
packages = ['krop'],
entry_points = {
'console_scripts': ['krop = krop.__main__:main']
},
data_files = [
- ('share/applications', ['krop.desktop']),
+ ('share/applications', ['com.arminstraub.krop.desktop']),
('share/man/man1', ['krop.1']),
+ ('share/metainfo', ['com.arminstraub.krop.metainfo.xml']),
+ ('share/icons/hicolor/scalable/apps', ['com.arminstraub.krop.svg'])
],
# https://pypi.org/classifiers/
classifiers = [
@@ -51,7 +53,6 @@
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'Topic :: Utilities',
'Intended Audience :: End Users/Desktop',
- 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Environment :: X11 Applications :: Qt',
],
diff -Nru --exclude '*qt4*' --exclude '*qt6*' krop-0.6.0/TODO krop-0.7.0/TODO
--- krop-0.6.0/TODO 2020-06-09 18:23:53.000000000 +0200
+++ krop-0.7.0/TODO 2025-04-06 21:04:10.000000000 +0200
@@ -1,9 +1,10 @@
Roadmap (ideas planned for one of the next releases)
-+ Allow translations (once UI has stabilized)
-+ Support PySide in addition to PyQT
-+ Support pikepdf in addition to or instead of pyPdf/PyPDF2
++ Improve support for pikepdf, PyMuPDF and pypdf (e.g. preserve links)
++ Support QtPy, an abstraction layer for both PyQt and PySide (for Qt5 and Qt6)
+ Expose further Ghostscript features (like exporting to PDF 1.7)
+ Save and later reuse regions for cropping
++ Use parallel processing for certain tasks like cropping
++ Allow translations (once UI has stabilized)
Wishlist (ideas that are likely difficult or currently impractical)
+ Preserve meta data, table of contents and bookmarks when cropping
Reply to: