Compare commits

...

206 commits
0.3.5 ... main

Author SHA1 Message Date
Art Gabdullin
b91007fef8
README.md - Fix Windows bin URL 2025-03-26 03:54:38 +01:00
a8cb28ba31
src/img2pdf_test.py: skip test_miff_cmyk16 on s390x because of https://github.com/ImageMagick/ImageMagick/issues/8055 2025-03-23 15:37:32 +01:00
c6d12d6239
src/img2pdf_test.py: skip test_tiff_float on s390x because of https://github.com/ImageMagick/ImageMagick/issues/8054 2025-03-23 15:37:32 +01:00
59132f20f8
src/img2pdf_test.py: exiftool -all= now sets the unit to Undefined since version 13.23
Thanks: gregor herrmann <gregoa@debian.org>
2025-03-23 15:37:32 +01:00
3ba7d17e15
HACKING: document gitea release 2025-02-21 00:40:17 +01:00
43c16ac369
HACKING: add final git push 2025-02-16 17:45:09 +01:00
08c4d9beec
release version 0.6.0 2025-02-15 15:07:29 +01:00
9e6eba9f40
reformat with black 2025-02-15 14:59:04 +01:00
5aeb628506
Extract an API to predict the DPI used by img2pdf 2025-02-15 14:48:33 +01:00
b6dbfdb481
Slightly simplify imgformat retrieval
No need for a loop here - we can access the enum like a dictionary,
which should be more efficient.
2025-02-15 14:40:22 +01:00
23436114f8
Slightly simplify the getexif procedure if PIL is new enough
The getexif() procedure is available since Pillow 6.0.0. If it's
available, change the algorithm to a simplified version.

In the future, the _getexif() branch can be deleted.
2025-02-15 14:36:37 +01:00
2d5e4e3cb7
break out convert_to_docobject() from convert() which returns a document handle 2025-02-15 14:35:57 +01:00
5e515abb6f
src/tests/output/mono.jb2.pdf: strip off the last 22 bytes (end-of-page and end-of-file segments) 2025-02-15 09:49:39 +01:00
a2e2998fb1
Strip end-of-page and end-of-file segments from JBIG2
As noted by @phmccarty in

and subsequent comments, we were not properly stripping end-of-page and
end-of-file segments. These are valid segments in a JBIG2 file, but not
when embedded in PDF.

From the PDF spec:
> The JBIG2 file header, end-of-page segments, and end-of-file segment
> shall not be used in PDF.

We were already stripping out the JBIG2 file header, but not yet the
end-of-page and end-of-file segments.

For this, I'm expanding the approach that we were already taking, of
only supporting a narrow subset of JBIG2 files. We assert that the input
file has such a footer, and then we strip it.

We validated that the issue raised by @phmccarty is indeed resolved by
running the following code before and after applying this commit:

```sh
src/img2pdf.py src/tests/input/mono.jb2 > test.pdf
pdfimages -tiff test.pdf img
```

Before this commit, this returned "Syntax Error (1143): Unknown segment
type in JBIG2 stream". After this commit, the error is gone.
2025-02-15 08:12:51 +01:00
14948e7ba8
Add support for JBIG2 (generic coding)
Implements the proposal detailed at


This is a limited implementation of JBIG2, which can be extended to
support multiple pages, symbol tables, and other features of the format
in the future.

Added a test case based on mono.tif.

Updated the README.md based on
2025-02-15 08:12:51 +01:00
bcfdf8b54e
src/img2pdf_test.py: test_jpg_2000_rgba8 no longer works with compare_poppler() 2025-02-15 08:12:08 +01:00
9f74740c95
src/img2pdf_test.py: test_miff_cmyk8 now compares exactly 2025-02-15 08:12:07 +01:00
cbc3d50c63
src/img2pdf_test.py: support None for tiff:alpha 2025-02-15 08:12:07 +01:00
4b549592bf
README.md: add example of how to use img2pdf together with scanimage 2024-09-11 11:35:07 +02:00
5540365cfd
add example for how to specify custom dpi 2024-09-11 11:34:14 +02:00
819b366bf5
release version 0.5.1 2023-11-26 06:33:10 +01:00
cc8c708295
HACKING: how to bisect 2023-11-25 09:47:53 +01:00
fb9537d8b7
src/img2pdf.py: allow PNG input without dpi units but non-square dpi aspect ratio
Closes: 
2023-11-25 09:47:52 +01:00
7678435eb7
validate icc profile and no default location on windows
closes: 
2023-11-07 18:50:07 +01:00
ba7a360866
release version 0.5.0 2023-10-28 08:35:54 +02:00
7f0bf47ff3
src/img2pdf.py: reformat with black 2023-10-28 08:35:53 +02:00
Leo
5cd0918d50 Issue related. The original was SmartAlbums, but another case with 'Adobe PS', so delete the exif_software check part 2023-10-18 13:33:44 +08:00
Leo
f157ced05d
ignore RGB icc profile for grayscale jpegs produced by SmartAlbums
closes: 
2023-10-17 11:32:25 +02:00
09064e8e70
jp2: rudimentary support for raw jpeg2000 without jp2 boxes 2023-08-08 07:40:38 +02:00
2f736d7891
allow 'matte' to be missing in MIFF 2023-08-06 19:43:19 +02:00
e05580a49a
src/img2pdf_test.py: IM7 dropped 'baseType' in json output, so use 'type' instead which works for both IM6 and IM7 2023-08-06 19:27:01 +02:00
acc25a4926
Support JPEG2000 images with transparency
Closes: 
2023-08-05 16:06:30 +02:00
f597887088
The GIMP ICC bug does not only apply to 1-bit tiff but also to black/white palette PNG
https://gitlab.gnome.org/GNOME/gimp/-/issues/3438

Closes: 
2023-08-05 14:43:18 +02:00
3e832fbcc2
add information about how to convert images to 8 bit (closes: ) 2023-08-05 14:43:07 +02:00
1e8557cef1
src/img2pdf_test.py: drop check for endianness for tests where it does not matter
IM7 defaults to big-endian on architectures other than x86 even if they
are little endian: https://github.com/ImageMagick/ImageMagick/issues/6300

Closes: 
2023-08-05 14:42:48 +02:00
29921eeabd
the default PDF/A icc profile is /usr/share/color/icc/sRGB.icc, /usr/share/color/icc/OpenICC/sRGB.icc or /usr/share/color/icc/colord/sRGB.icc depending on which one exists 2023-06-11 21:56:21 +02:00
33139612f8
src/img2pdf_test.py: make endianness dependant on sys.byteorder (closes: ) 2023-06-11 14:45:09 +02:00
64d27f4a8b
src/img2pdf_test.py: allow Bilevel as well as Grayscale type for png_gray1_img (closes: ) 2023-06-11 13:24:30 +02:00
85cbe1d128
factor out argparse.ArgumentParser to allow for generating completions via shtab 2023-06-11 08:09:46 +02:00
b25429a4c1
src/img2pdf_test.py: add tests for timestamps 2023-06-11 08:01:36 +02:00
c703e9df06
fix date(1) based timestamp parser 2023-06-11 07:48:23 +02:00
79e9985f35
src/img2pdf_test.py: black 2023-06-11 07:47:22 +02:00
cb2644c34f
do not include thumbnails in the output by default unless --include-thumbnails is used
This is relevant for the MPO format which otherwise would result in PDF
files containing the same image in different sizes multiple times. With
this change, the default is to only have a single page containing the
full MPO. This means that extracting that MPO also gets the thumbnails
back.

With the --include-thumbnails option, each frame gets stored on its own
page as it is done for multi-frame GIF, for example.

Closes: 
2023-06-11 07:31:07 +02:00
81502f21af Convert creation/modification dates to UTC (fixes )
Ensure that timezones are correctly interpreted in the input by calling
`.astimezone()` as appropriate on datetime objects, and store the
resulting date fields as UTC.

One could argue that datetimes in the local timezone be stored in the
PDF, but then the date string handling becomes more complicated; the PDF
and XMP date specs both use the `Z` suffix to indicate UTC time, but
other +/- offsets require different syntax between the two specs.
2023-06-10 17:53:03 -07:00
0cbcb8fa12
avoid converting palette PNG with alpha to RGB (closes: ) 2023-06-08 08:54:37 +02:00
e9e04b6dd9
extend comments around dropping ICC profile stored by GIMP for bilevel input 2023-06-08 08:53:22 +02:00
fc059ee471
use quotes around caret in examples for windows users
Closes: 
2023-06-08 07:14:17 +02:00
25466113e9
another small fixup for the last commit 2023-05-30 08:06:36 +02:00
7405635b72
only check whether icc profile can be dropped if there is any 2023-05-30 07:10:32 +02:00
aea472101b
strip off RGB color profile from bilevel TIFF images produced by gimp (closes: ) 2023-05-30 06:25:26 +02:00
7fa67bb337
demote print() to logger.debug() 2023-05-29 09:25:21 +02:00
7d40569aa1
Inform the user what is happening when running without any arguments and suggest using --help to get the help text (closes: ) 2023-05-28 15:25:28 +02:00
83f9c32328
appveyor.yml: try out --console --nowindowed 2023-05-28 15:25:28 +02:00
be8369373f
pass deterministic_id=True to writer.save() for pikepdf >= 6.2.0
Closes: 
2022-10-16 14:13:35 +02:00
10c6901fa3
src/img2pdf_test.py: do not test the depth attribute and rely on baseDepth
closes: 
2022-09-23 23:10:53 +02:00
57d7e07e6b
Support imagemagick 7.1.0-48
- the output of -metric PSNR changed
 - CMYK output can now be exactly compared

closes: 
2022-09-15 04:36:16 +02:00
272fe0433f allow pathlib.Path objects by allowing objects implementing read_bytes function 2022-07-02 21:19:34 +02:00
ef7b9e739d add miff tests for cmyk8 and rgb8 2022-07-02 20:39:18 +02:00
af6fe27d53 avoid match/case for now until python 3.10 is available on more platforms 2022-06-28 14:22:14 +01:00
bad6fcae39 support for MIFF which allows 16 bit CMYK images
closes: 
2022-06-27 13:22:07 +01:00
d9b90499f3
README.md: compare to econvert (closes: ) 2022-05-18 13:08:05 +02:00
edb0d29a14
README.md: fix link 2022-05-13 21:27:12 +02:00
bb3e8b0098
README.md: document that img2pdf.exe can now be downloaded via release 2022-05-13 21:25:37 +02:00
f454ebc6a6
release version 0.4.4 2022-04-07 22:40:36 +02:00
c3db273e23
Remove outdated readme entry concerning JP2 colorspace
If I understood the code in `jp2.py` correctly, this should now work.
Moreover, Pillow should usually be able to open JP2 files, so `jp2.py` is only a fallback.
2022-04-07 22:08:41 +02:00
87afabd3cf
add .mailmap 2022-04-07 22:08:18 +02:00
homocomputeris
5045282cc2
Add B and JB paper sizes 2022-04-07 22:02:16 +02:00
fb4b96452a
reformat with black 2022-04-07 21:58:34 +02:00
c553e169a4
use TiffImagePlugin.STRIP_SIZE for Pillow >= 8.4.0 2022-04-07 21:58:23 +02:00
d9345ac767
set GifImagePlugin.LOADING_STRATEGY to RGB_AFTER_DIFFERENT_PALETTE_ONLY in Pillow >= 9.1.0 (closes: ) 2022-04-07 21:57:37 +02:00
1d52530229
support new pikepdf.Page object when making indirect objects (closes: ) 2022-04-07 21:55:45 +02:00
3b117e674b
clarify that input with an alpha channel requires additional computation (closes: ) 2022-04-07 17:13:16 +02:00
e8ca53738f
--viewer-page-layout support for twopageright and twopageleft 2022-03-25 14:01:18 +01:00
7c48bfb868
find_scale(): better document why we use powers of 10 2022-03-24 12:43:49 +01:00
244f034a2e
src/img2pdf_test.py: format with black 2022-03-22 17:57:12 +01:00
3da370d3bd
add more information about how to ignore invalid rotation values in input images 2022-02-06 20:33:30 +01:00
6cff2931e4
ensure that gif test input uses the same palette for all frames 2022-02-06 20:25:25 +01:00
6a55258804
appveyor.yml: rename pil to Pillow 2022-01-16 23:38:22 +01:00
3cdeab08ab
appveyor.yml: also install pil so that maybe pyinstaller picks it up 2022-01-16 23:36:37 +01:00
cea7c9120b
tox.ini: python 3.5 and 3.6 are not supported anymore 2022-01-16 22:18:22 +01:00
9eacfdaa76
appveyor.yml: don't run tests because we don't have imagemagick 2021-12-11 11:22:59 +01:00
95a313f437
tox.ini: add python 3.10 to envlist 2021-12-11 11:22:31 +01:00
30d705f020
src/img2pdf.py: the /OpenAction array must contain the page as an indirect object 2021-11-28 16:55:42 +01:00
dc926b2cf2
release version 0.4.3 2021-10-24 16:51:02 +02:00
a8fdbd0038
fix --viewer-initial-page (broken in last release) 2021-10-24 16:15:46 +02:00
6ff175d637
update my name 2021-10-13 09:17:44 +02:00
0732dff0be
src/img2pdf_test.py: make img2pdf path configurable 2021-10-13 09:17:17 +02:00
50b7145f64
release version 0.4.2 2021-10-11 17:16:03 +02:00
e522ec14d9
remove pdfrw from tests (closes: ) 2021-10-09 15:29:44 +02:00
9c9e5ece19
src/img2pdf_test.py: add support for imagemagick 7 2021-10-03 22:54:33 +02:00
354fd7c264
src/img2pdf.py: format with black 2021-10-03 21:03:22 +02:00
392d4a665e
adapt AlphaChannelError messages 2021-10-03 21:03:08 +02:00
09ad147d97
support PNG palette images with icc profile (closes: ) 2021-10-03 20:59:59 +02:00
80393b6efa
also support palette+alpha 2021-10-03 20:56:53 +02:00
e265738ac2
Revert "allow palette PNG images with ICC profile (closes: )"
This reverts commit 3d7e0e6812.
2021-10-03 16:09:54 +02:00
1ffb160453
Readme: remove alpha channels section 2021-10-03 16:04:53 +02:00
cde7472d15 Update documentation: img2pdf now supports alpha channels 2021-10-03 11:48:24 +00:00
6eec05c11c
workaround for Pillow >= 8.3.0 which limits CCITT Group4 strip size to 64 KB (closes: ) 2021-10-03 13:24:53 +02:00
Tamás Zahola
f483638b17 Test cases for transparency 2021-08-22 08:08:22 +00:00
Tamás Zahola
7f216a8848 Test support on macOS 2021-08-22 08:08:21 +00:00
Tamás Zahola
2476215f39 Always initialize smask 2021-08-22 08:08:20 +00:00
Tamás Zahola
f62858c245 Formatting 2021-08-22 08:08:20 +00:00
Tamás Zahola
a5e4da5755 Use PNG predictor for /SMask too 2021-08-22 08:08:19 +00:00
Tamás Zahola
64db7909ec Added transparency support for GIFs, palette-based PNGs and grayscale PNGs 2021-08-22 08:08:19 +00:00
Tamás Zahola
af5ae5b9b6 Convert 8-bit PNG alpha channels to /SMasks in PDF 2021-08-22 08:08:19 +00:00
d03f331521
Remove ineffective description-file parameter from setup.cfg
Otherwise, we get a warning:
"Usage of dash-separated 'description-file' will not be supported in
future versions."

This parameter was only used when setup.py was used with pbr=True, which is
not the case here, and setup.py loads the right file into long_description.
2021-08-22 03:15:20 +02:00
635b08c321
README: fix CI URLs 2021-08-21 20:57:15 +02:00
152f6fb629
tests: look for sRGB.icc in several paths, skip related tests if not found
Always use Ghostscript's sRGB.icc.
2021-08-19 19:56:01 +02:00
1f3b456ac9
tests: don't expect ImageMagick to compress TIFF files by default
This is no longer the case with ImageMagick 7.
2021-08-19 13:17:06 +02:00
4c5b72dab0
src/img2pdf_test.py: skip tests when icc profiles are not present 2021-08-19 13:08:32 +02:00
853a1ec363 src/img2pdf_test.py: do not test format descriptions 2021-08-19 10:59:46 +00:00
55d589a548
README.md: document how to remove alpha channel and --rotation=ifvalid 2021-08-14 10:19:46 +02:00
5c617965f5
document the ifvalid option value further 2021-08-14 10:19:06 +02:00
0067edf965
remove first_frame_only from gui 2021-08-14 10:18:15 +02:00
91e3a94c3d
sort globbing result on windows 2021-07-02 16:18:20 +02:00
3d7e0e6812
allow palette PNG images with ICC profile (closes: ) 2021-06-14 18:34:46 +02:00
b4c8aa1a5f
add --rotation argument overwriting exif data (closes: ) 2021-05-14 11:31:53 +02:00
114d7270a2
release version 0.4.1 2021-05-09 18:57:41 +02:00
80d24a1d49
README.md: add more examples (closes: ) 2021-05-07 11:20:47 +02:00
ea2245757f
add --from-file to read arbitrarily many images and circumvent the maximum command length of the shell (closes: ) 2021-05-07 10:59:19 +02:00
9cda595cd5
I got married and my last name changed 2021-05-07 09:41:27 +02:00
2eabebb513
use context manager instead of manually closing BytesIO and PIL.Image 2021-04-28 15:37:44 +02:00
02c85a50ad
move closing code into its own function 2021-04-14 08:25:18 +02:00
c97ce34023
raise exception if border is larger than page size 2021-04-13 13:10:57 +02:00
81325d3998
fix page border computation (closes: ) 2021-04-13 13:10:34 +02:00
8d2ae0f64e
fix reading from stdin 2021-04-13 08:12:33 +02:00
d29c596fe7
add support for MPO images (closes: ) 2021-04-12 22:57:51 +02:00
cd1088a5a9
format with black 2021-04-12 14:13:34 +02:00
2a8779295f
write out pil format as debug message 2021-04-12 14:13:19 +02:00
6cd819d28f
do not use root logger for logging 2021-04-12 14:12:14 +02:00
c48e1dbb1e
use sys.exit() as exit might not be defined under windows -- see 2021-04-12 06:52:27 +02:00
d08d8c5be9
src/img2pdf_test.py: compare icc images as non-exact 2021-04-12 06:27:36 +02:00
0e4f0047b2
src/img2pdf_test.py: refactor comparison code to remove code duplication 2021-04-12 06:27:36 +02:00
0ce25d08c2
src/img2pdf_test.py: also draw RGB letters to identify colors 2021-04-12 06:27:36 +02:00
c5fd43e851
src/img2pdf_test.py: numpy.float got deprecated in NumPy 1.20 2021-04-12 06:16:05 +02:00
17fd73aed8
.travis.yml: set osx_image: xcode12.2 or pikepdf import fails 2021-04-07 12:23:40 +02:00
454d4e7775
src/img2pdf_test.py: distinguish between depth and baseDepth for tiff_rgb12 and tiff_rgb13 2021-04-07 12:23:01 +02:00
cb2243fd10
src/img2pdf_test.py: Pillow >= 8.2.0 only uses half the palette size 2021-04-07 12:23:01 +02:00
129bd15b43
tox.ini: run pytest with -vv to show full diff 2021-04-07 12:22:53 +02:00
b8bfa98218
src/img2pdf_test.py: cater for endianess->endianness in 1.0 output format 2021-04-07 08:06:59 +02:00
b5f0912e13
README.md: minimize scope of opened file in examples
Prompted-by: Joao Rodrigo Windisch Olenscki
2021-03-09 17:35:27 +01:00
213a6af41f
tox.ini: add py39 2020-10-31 08:47:01 +01:00
9290cb4a10
.travis.yml: bump windows python version to 3.9 2020-10-31 08:42:25 +01:00
Paul Ingemi
505344f83e
Add Windows support for paths containing wildcards "*" and "?"
Img2pdf accepts paths containing wildcards such as *.jpg to efficiently refer
to multiple input files that match the wildcard expression. Under POSIX
environments the command line shell is expected to perform this expansion,
however Windows requires the command line utility itself to expand the
wildcard expression.

Ideally this would be performed by argparse as described in this draft PEP:
https://mail.python.org/pipermail/python-ideas/2015-August/035244.html

Since argparse doesn't do it, this commit performs expansion directly.

Some implementation notes:
 - Wildcard characters "*" and "?" are not valid in Windows filenames
 - Code doesn't support bracket wildcards such as [0-3] on Windows since
   they are valid filename characters
 - Due to expansion, the images list collected by argparse may contain
   sub-lists. Code uses chain.from_iterable to create a flat list.
 - Paths that refer to non-existant files raise an error message, while
   wildcards that match no files are silently ignored.
2020-10-05 10:28:08 +02:00
Anna Levenberg
32b4ed1f43
Add check for empty list 2020-09-22 16:23:17 +02:00
b2c3b641db
fix typo: complient -> compliant 2020-08-27 02:48:26 +02:00
c4fb1d886f
remove magick.py and test.sh 2020-08-27 00:56:32 +02:00
11907242a5
src/img2pdf_test.py: we create our own channel-switching ICC profile 2020-08-09 22:03:47 +02:00
692b54ac67
release version 0.4.0 2020-08-07 08:05:27 +02:00
cc79581e2c
.travis.yml: install icc-profiles-free 2020-08-07 00:41:42 +02:00
c7db805dee
don't use /usr/share/color/icc/ghostscript/srgb.icc but /usr/share/color/icc/sRGB.icc because the former converts from sRGB to CIE LAB and PDF/A requires a profile from CIE LAB to sRGB (closes: ) 2020-08-07 00:20:44 +02:00
f0b57985ee
first stab at embedding ICC profiles 2020-08-07 00:13:53 +02:00
1ba02bf838
src/img2pdf_test.py: use json output instead of -verbose everywhere (the former is for machines, the latter for human consumption and can change without notice) 2020-08-06 20:25:20 +02:00
042aac71eb
reformat with black 2020-08-05 08:13:52 +02:00
7da0a00ef3
src/img2pdf_test.py: replace jpg_img check by json output 2020-08-05 08:11:39 +02:00
67dca425d1
.travis.yml: remove jpeg2000 check 2020-08-05 01:15:51 +02:00
86fddab622
src/img2pdf_test.py: check for JP2 support 2020-08-05 01:12:59 +02:00
c229e20547
.travis.yml: test for jpeg2000 support 2020-08-05 00:57:30 +02:00
a53fed5d17
.travis.yml: really switch to focal 2020-08-05 00:50:45 +02:00
788102ee05
.travis.yml: use focal 2020-08-05 00:48:15 +02:00
d92790ad26
try ghostscript version shaningans 2020-08-05 00:10:00 +02:00
f0e7e8daaf
.travis.yml: try xenial 2020-08-05 00:00:00 +02:00
9bd41dba1e
src/img2pdf_test.py: check that psnr is not zero 2020-08-04 23:54:11 +02:00
6c742be642
src/img2pdf_test.py: cater for json output of imagemagick before 6.9.9.34 2020-08-04 23:29:42 +02:00
7ccd987d6d
.travis.yml: use bionic instead of xenial 2020-08-04 13:15:43 +02:00
c506cf0b8d
try to use json output instead of identify -verbose 2020-08-04 13:07:40 +02:00
fbb51d9083
tighten minimum imagemagick version for quantum:polarity=min-is-black 2020-08-04 09:30:03 +02:00
593aeb5d2d
appveyor.yml: install pyinstaller to create an exe 2020-08-04 08:03:34 +02:00
c62ed4d691
appveyor.yml: install wheel for bdist_wheel 2020-08-04 07:54:18 +02:00
2493af173c
tox.ini: also remove support for py34 (fails to compile pikepdf) 2020-08-04 07:45:39 +02:00
a39beb5c5e
tox.ini: remove jython and pypy 2020-08-03 23:27:04 +02:00
7a72c38c3d
appveyor.yml: run tox 2020-08-03 23:26:15 +02:00
1d5be0cc9d
src/img2pdf_test.py: also catch subprocess.CalledProcessError 2020-08-03 18:46:58 +02:00
e151ca27eb
src/img2pdf_test.py: skip tests on win32 and macos 2020-08-03 18:45:40 +02:00
7aed0ea0d0
src/img2pdf_test.py: fixes for Ubuntu Trusty 2020-08-03 18:31:55 +02:00
bed0b4cdae
src/img2pdf_test.py: check for mutool >= 1.10.0 2020-08-03 16:04:58 +02:00
53e991bec2
.travis.yml: add dependency on mupdf-tools 2020-08-03 15:06:35 +02:00
11f7db5003
src/img2pdf_test.py: explicitly convert PosixPath to str 2020-08-03 15:04:51 +02:00
000fc3b5ac
remove obsolete src/tests/__init__.py 2020-08-03 14:56:32 +02:00
78bd869b10
.travis.yml: install additional dependencies 2020-08-03 14:50:38 +02:00
0992832ab0
src/img2pdf_test.py: explicitly convert PosixPath to str 2020-08-03 14:49:58 +02:00
1d0e4c5272
.travis.yml: install imagemagick 2020-08-03 14:13:45 +02:00
bfd822b74e
.travis.yml: install tox 2020-08-03 14:03:00 +02:00
2c8e417c51
run tox on travis 2020-08-03 14:01:25 +02:00
66fd7cc765
rename test.py -> src/img2pdf_test.py 2020-08-03 12:34:02 +02:00
5b7f93bb9a
do not allow negative values in parse_num() 2020-08-03 12:25:14 +02:00
a2d846052e
document pytest in HACKING, tox.ini and .travis.yml 2020-08-03 12:24:41 +02:00
a84a1b8480
add test.py 2020-06-06 01:19:30 +02:00
663010ca61
src/img2pdf.py: tiny logic error 2020-06-06 01:19:30 +02:00
802dd4b1f3
magick.py: prevent floyd_steinberg() from modifying its input 2020-06-03 00:15:55 +02:00
93f65a49c9
add --pdfa argument to attach an icc profile for PDF/A-1b compliant output 2020-06-01 02:10:07 +02:00
bca3f802ac
Add support for pikepdf 2020-05-31 17:18:02 +02:00
65d9aed630
test.sh: compare uncompressed contents 2020-05-31 09:08:00 +02:00
791c9497ed
test.sh: remove leftover 'grep' 2020-05-31 09:07:15 +02:00
1cd2674a2c
src/tests/__init__.py: recursively convert both PDFs into Python data structures and then compare for equality -- this allows comparing PDFs in which streams are compressed differently 2020-05-31 09:05:55 +02:00
f4b296cef3
src/tests/__init__.py: make path OS agnostic (slash vs backslash) 2020-05-28 13:16:09 +02:00
5c7ffb09a1
src/tests/__init__.py: add support for engine instead of with_pdfrw 2020-05-28 13:12:07 +02:00
7b58c4e58d
add pikepdf to test dependencies 2020-05-28 12:57:40 +02:00
c6d04acc4b
test.sh: account for variations in output of identify --verbose 2020-05-28 12:52:42 +02:00
c49a098e7b
test.sh: improve portability on macOS and BSD 2020-05-28 12:52:38 +02:00
e4dece1c9f
replace --without-pdfrw by --engine=internal or --engine=pdfrw 2020-05-28 12:46:37 +02:00
997fe8efd8
src/tests/__init__.py: use pikepdf instead of pdfrw 2020-05-28 12:43:08 +02:00
c808061b4b
test.sh: use pikepdf instead of grep to check PDF
- no requirement on layout chosen by PDF engine anymore, thus we can
   test any pdf writer with this
 - check value at full path instead of anywhere in the document, thus no
   possible false positives if expected value was present in a different
   object
 - allows adding additional checks where we couldn't use grep before
   (multipage pdf created by pdfseparate)
2020-05-28 06:32:29 +02:00
17dd59e722
README.md: clarify PDF transparency (closes: ) 2020-05-06 08:55:46 +02:00
60fa588cfb
release version 0.3.6 2020-04-30 22:18:54 +02:00
559d42cd4a
magick.py: use our own deflate algo for bit-by-bit reproducible output independent of the compressor used 2020-04-29 16:15:55 +02:00
19 changed files with 9096 additions and 2953 deletions

3
.mailmap Normal file
View file

@ -0,0 +1,3 @@
Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>
Johannes Schauer Marin Rodrigues <josch@mister-muffin.de> <j.schauer@email.de>
Johannes Schauer Marin Rodrigues <josch@mister-muffin.de> <josch@pyneo.org>

View file

@ -1,16 +1,26 @@
language: python
matrix:
include:
- name: "python 3.7 Xenial"
dist: xenial
python: 3.7
- name: "python 3.8 Windows"
- name: "Ubuntu Focal"
dist: focal
addons:
apt:
packages:
- imagemagick
- libtiff-tools
- libimage-exiftool-perl
- poppler-utils
- netpbm
- ghostscript
- mupdf-tools
- name: "python 3.9 Windows"
os: windows
language: shell # 'language: python' is an error on Travis CI Windows
before_install: choco install python
env: PATH=/c/Python38:/c/Python38/Scripts:$PATH
before_install: choco install python imagemagick
env: PATH=/c/Python39:/c/Python39/Scripts:$PATH
- name: "python 3.7 MacOs"
os: osx
osx_image: xcode12.2 # pikepdf import fails with earlier versions
language: shell # 'language: python' is an error on Travis CI macOS
cache:
directories:
@ -19,12 +29,14 @@ matrix:
addons:
homebrew:
#update: true
packages: python3
packages:
- python3
- imagemagick
before_install:
- python3 -m pip install --upgrade virtualenv
- virtualenv -p python3 --system-site-packages "$HOME/venv"
- source "$HOME/venv/bin/activate"
install: pip install wheel pillow pytest pdfrw
install: pip install tox
script:
- python --version
- python setup.py test
- python -m tox

View file

@ -2,6 +2,78 @@
CHANGES
=======
0.6.0 (2025-02-15)
------------------
- Add support for JBIG2 (generic coding)
- Add convert_to_docobject() broken out from convert()
- Add pil_get_dpi() broken out from get_imgmetadata()
0.5.1 (2023-11-26)
------------------
- no default ICC profile location for PDF/A-1b on Windows
- workaround for PNG input without dpi units but non-square dpi aspect ratio
0.5.0 (2023-10-28)
------------------
- support MIFF for 16 bit CMYK input
- accept pathlib.Path objects as input
- don't store RGB ICC profiles from bilevel or grayscale TIFF, PNG and JPEG
- thumbnails are no longer included by default and --include-thumbnails has to
be used if you want them
- support for pikepdf (>= 6.2.0)
0.4.4 (2022-04-07)
------------------
- --viewer-page-layout support for twopageright and twopageleft
- Add B and JB paper sizes
- support for pikepdf (>= 5.0.0) and Pillow (>= 9.1.0)
0.4.3 (2021-10-24)
------------------
- fix --viewer-initial-page (broken in last release)
0.4.2 (2021-10-11)
------------------
- add --rotation
- allow palette PNG images with ICC profile
- sort globbing result on windows
- convert 8-bit PNG alpha channels to /SMasks in PDF
- remove pdfrw from tests
0.4.1 (2021-05-09)
------------------
- support wildcards in paths on windows
- support MPO images
- fix page border computation
- use "img2pdf" logger instead of "root" logger
- add --from-file
0.4.0 (2020-08-07)
------------------
- replace --without-pdfrw by --engine=internal or --engine=pdfrw
- add pikepdf as additional rendering engine and add --engine=pikepdf
- support for creating PDF/A-1b compliant PDF using the --pdfa option
(this also requires the presence of an ICC profile somewhere on the system)
- support for images with embedded ICC profile as input
- rewrite tests
* use pytest via tox
* use pikepdf instead of pdfrw
* use imagemagick json output instead of identify -verbose
- format all code with black
0.3.6 (2020-04-30)
------------------
- fix tests for Fedora on arm64
0.3.5 (2020-04-28)
------------------

81
HACKING
View file

@ -8,31 +8,7 @@ img2pdf can be run directly from the cloned git repository:
Running the testsuite
---------------------
$ python3 setup.py test
$ ./test.sh
Fixing the setup.py testsuite
-----------------------------
Sometimes your changes will break the testsuite. The testsuite works by
comparing the images found in `src/tests/input` with the corresponding PDF
documents in `src/tests/output`. The filename of the output file will be the
same name the input has with the `.pdf` suffix appended to it. To generate the
files in the `src/tests/output` directory via the command line, run:
$ src/img2pdf.py --producer="" --nodate src/tests/input/normal.jpg -o src/tests/output/normal.jpg.pdf
This makes it easier to spot where the differences are and how to fix the
issue.
Fixing the test.sh testsuite
----------------------------
If your changes break test.sh, then the script will abort immediately and tell
you where it left the temporary files for you to inspect. You can also try
running the script like this for maximum verbosity:
$ sh -x ./test.sh
$ pytest
Making a new release
--------------------
@ -51,6 +27,57 @@ Making a new release
- Build and upload to pypi:
$ rm dist/*
$ rm -rf dist/*
$ python3 setup.py sdist
$ twine upload --sign dist/*
$ twine upload dist/*
- Push everything to git forge
$ git push
- Push to github
$ git push github
- Obtain img2pdf.exe from appveyor:
https://ci.appveyor.com/project/josch/img2pdf/
- Create new release:
https://gitlab.mister-muffin.de/josch/img2pdf/releases/new
Using debbisect to find regressions
-----------------------------------
$ debbisect --cache=./cache --depends="git,ca-certificates,python3,
ghostscript,imagemagick,mupdf-tools,poppler-utils,python3-pil,
python3-pytest,python3-numpy,python3-scipy,python3-pikepdf" \
--verbose 2023-09-16 2023-10-24 \
'chroot "$1" sh -c "
git clone https://gitlab.mister-muffin.de/josch/img2pdf.git
&& cd img2pdf
&& pytest 'src/img2pdf_test.py::test_jpg_2000_rgba8[internal]"'
Using debbisect cache
---------------------
$ mmdebstrap --variant=apt --aptopt='Acquire::Check-Valid-Until "false"' \
--include=git,ca-certificates,python3,ghostscript,imagemagick \
--include=mupdf-tools,poppler-utils,python3-pil,python3-pytest \
--include=python3-numpy,python3-scipy,python3-pikepdf \
--hook-dir=/usr/share/mmdebstrap/hooks/file-mirror-automount \
--setup-hook='mkdir -p "$1/home/josch/git/devscripts/cache/pool/"' \
--setup-hook='mount -o ro,bind /home/josch/git/devscripts/cache/pool/ "$1/home/josch/git/devscripts/cache/pool/"' \
--chrooted-customize-hook=bash
unstable /dev/null
file:///home/josch/git/devscripts/cache/archive/debian/20231022T090139Z/
Bisecting imagemagick
---------------------
$ git clean -fdx && git reset --hard
$ ./configure --prefix=$(pwd)/prefix
$ make -j$(nproc)
$ make install
$ LD_LIBRARY_PATH=$(pwd)/prefix/lib prefix/bin/compare ...

123
README.md
View file

@ -1,5 +1,5 @@
[![Travis Status](https://travis-ci.org/josch/img2pdf.svg?branch=master)](https://travis-ci.org/josch/img2pdf)
[![Appveyor Status](https://ci.appveyor.com/api/projects/status/2kws3wkqvi526llj/branch/master?svg=true)](https://ci.appveyor.com/project/josch/img2pdf/branch/master)
[![Travis Status](https://travis-ci.com/josch/img2pdf.svg?branch=main)](https://app.travis-ci.com/josch/img2pdf)
[![Appveyor Status](https://ci.appveyor.com/api/projects/status/2kws3wkqvi526llj/branch/main?svg=true)](https://ci.appveyor.com/project/josch/img2pdf/branch/main)
img2pdf
=======
@ -27,18 +27,20 @@ software, because the raw pixel data never has to be loaded into memory.
The following table shows how img2pdf handles different input depending on the
input file format and image color space.
| Format | Colorspace | Result |
| -------------------- | ------------------------------ | ------------- |
| JPEG | any | direct |
| JPEG2000 | any | direct |
| PNG (non-interlaced) | any | direct |
| TIFF (CCITT Group 4) | monochrome | direct |
| any | any except CMYK and monochrome | PNG Paeth |
| any | monochrome | CCITT Group 4 |
| any | CMYK | flate |
| Format | Colorspace | Result |
| ------------------------------------- | ------------------------------------ | ------------- |
| JPEG | any | direct |
| JPEG2000 | any | direct |
| PNG (non-interlaced, no transparency) | any | direct |
| TIFF (CCITT Group 4) | 1-bit monochrome | direct |
| JBIG2 (single-page generic coding) | 1-bit monochrome | direct |
| any | any except CMYK and 1-bit monochrome | PNG Paeth |
| any | 1-bit monochrome | CCITT Group 4 |
| any | CMYK | flate |
For JPEG, JPEG2000, non-interlaced PNG and TIFF images with CCITT Group 4
encoded data, img2pdf directly embeds the image data into the PDF without
For JPEG, JPEG2000, non-interlaced PNG, TIFF images with CCITT Group 4
encoded data, and JBIG2 with single-page generic coding (e.g. using `jbig2enc`),
img2pdf directly embeds the image data into the PDF without
re-encoding it. It thus treats the PDF format merely as a container format for
the image data. In these cases, img2pdf only increases the filesize by the size
of the PDF container (typically around 500 to 700 bytes). Since data is only
@ -47,7 +49,7 @@ solutions for these input formats.
For all other input types, img2pdf first has to transform the pixel data to
make it compatible with PDF. In most cases, the PNG Paeth filter is applied to
the pixel data. For monochrome input, CCITT Group 4 is used instead. Only for
the pixel data. For 1-bit monochrome input, CCITT Group 4 is used instead. Only for
CMYK input no filter is applied before finally applying flate compression.
Usage
@ -65,6 +67,12 @@ The detailed documentation can be accessed by running:
$ img2pdf --help
With no command line arguments supplied, img2pdf will read a single image from
standard input and write the resulting PDF to standard output. Here is an
example for how to scan directly to PDF using scanimage(1) from SANE:
$ scanimage --mode=Color --resolution=300 | pnmtojpeg -quality 90 | img2pdf > scan.pdf
Bugs
----
@ -72,15 +80,15 @@ Bugs
when embedded into the PDF cannot be read by the Adobe Acrobat Reader,
please contact me.
- I have not yet figured out how to determine the colorspace of JPEG2000
files. Therefore JPEG2000 files use DeviceRGB by default. For JPEG2000
files with other colorspaces, you must explicitly specify it using the
`--colorspace` option.
- Input images with alpha channels are not allowed. PDF doesn't support alpha
channels in images and thus, the alpha channel of the input would have to be
discarded. But img2pdf will always be lossless and thus, input images must
not carry transparency information.
- An error is produced if the input image is broken. This commonly happens if
the input image has an invalid EXIF Orientation value of zero. Even though
only nine different values from 1 to 9 are permitted, Anroid phones and
Canon DSLR cameras produce JPEG images with the invalid value of zero.
Either fix your input images with `exiftool` or similar software before
passing the JPEG to `img2pdf` or run `img2pdf` with `--rotation=ifvalid`
(if you run img2pdf from the commandline) or by passing
`rotation=img2pdf.Rotation.ifvalid` as an argument to `convert()` when using
img2pdf as a library.
- img2pdf uses PIL (or Pillow) to obtain image meta data and to convert the
input if necessary. To prevent decompression bomb denial of service attacks,
@ -117,10 +125,9 @@ You can then test the converter using:
$ ve/bin/img2pdf -o test.pdf src/tests/test.jpg
For Microsoft Windows users, PyInstaller based .exe files are produced by
appveyor. If you don't want to install Python before using img2pdf you can head
to appveyor and click on "Artifacts" to download the latest version:
https://ci.appveyor.com/project/josch/img2pdf
If you don't want to setup Python on Windows, then head to the
[releases](https://gitlab.mister-muffin.de/josch/img2pdf/releases) section and download the latest
`img2pdf.exe`.
GUI
---
@ -147,6 +154,10 @@ The package can also be used as a library:
with open("name.pdf","wb") as f1, open("test.jpg") as f2:
f1.write(img2pdf.convert(f2))
# opening using pathlib
with open("name.pdf","wb") as f:
f.write(img2pdf.convert(pathlib.Path('test.jpg')))
# using in-memory image data
with open("name.pdf","wb") as f:
f.write(img2pdf.convert("\x89PNG...")
@ -161,26 +172,26 @@ The package can also be used as a library:
# convert all files ending in .jpg inside a directory
dirname = "/path/to/images"
imgs = []
for fname in os.listdir(dirname):
if not fname.endswith(".jpg"):
continue
path = os.path.join(dirname, fname)
if os.path.isdir(path):
continue
imgs.append(path)
with open("name.pdf","wb") as f:
imgs = []
for fname in os.listdir(dirname):
if not fname.endswith(".jpg"):
continue
path = os.path.join(dirname, fname)
if os.path.isdir(path):
continue
imgs.append(path)
f.write(img2pdf.convert(imgs))
# convert all files ending in .jpg in a directory and its subdirectories
dirname = "/path/to/images"
imgs = []
for r, _, f in os.walk(dirname):
for fname in f:
if not fname.endswith(".jpg"):
continue
imgs.append(os.path.join(r, fname))
with open("name.pdf","wb") as f:
imgs = []
for r, _, f in os.walk(dirname):
for fname in f:
if not fname.endswith(".jpg"):
continue
imgs.append(os.path.join(r, fname))
f.write(img2pdf.convert(imgs))
@ -189,6 +200,15 @@ The package can also be used as a library:
with open("name.pdf","wb") as f:
f.write(img2pdf.convert(glob.glob("/path/to/*.jpg")))
# convert all files matching a glob using pathlib.Path
from pathlib import Path
with open("name.pdf","wb") as f:
f.write(img2pdf.convert(*Path("/path").glob("**/*.jpg")))
# ignore invalid rotation values in the input images
with open("name.pdf","wb") as f:
f.write(img2pdf.convert('test.jpg'), rotation=img2pdf.Rotation.ifvalid)
# writing to file descriptor
with open("name.pdf","wb") as f1, open("test.jpg") as f2:
img2pdf.convert(f2, outputstream=f1)
@ -199,6 +219,16 @@ The package can also be used as a library:
with open("name.pdf","wb") as f:
f.write(img2pdf.convert('test.jpg', layout_fun=layout_fun))
# use a fixed dpi of 300 instead of reading it from the image
dpix = dpiy = 300
layout_fun = img2pdf.get_fixed_dpi_layout_fun((dpix, dpiy))
with open("name.pdf","wb") as f:
f.write(img2pdf.convert('test.jpg', layout_fun=layout_fun))
# create a PDF/A-1b compliant document by passing an ICC profile
with open("name.pdf","wb") as f:
f.write(img2pdf.convert('test.jpg', pdfa="/usr/share/color/icc/sRGB.icc"))
Comparison to ImageMagick
-------------------------
@ -286,3 +316,14 @@ Tesseract might not do a lossless conversion. For example it converts CMYK
input to RGB and removes the alpha channel from images with transparency. For
multipage TIFF or animated GIF, it will only convert the first frame.
Comparison to econvert from ExactImage
--------------------------------------
Like pdflatex and podofoimg2pf, econvert is able to embed JPEG images into PDF
directly without re-encoding but when given other file formats, it stores them
just using flate compressen, which unnecessarily increases the filesize.
Furthermore, it throws an error with CMYK TIF input. It also doesn't store CMYK
jpeg files as CMYK but converts them to RGB, so it's not lossless. When trying
to feed it 16bit files, it errors out with Unhandled bps/spp combination. It
also seems to choose JPEG encoding when using it on some file types (like
palette images) making it again not lossless for that input as well.

View file

@ -16,16 +16,18 @@ environment:
- PYTHON: "C:\\Python37-x64"
install:
- "%PYTHON%\\python.exe -m pip install wheel pytest pdfrw pillow pyinstaller"
- "%PYTHON%\\python.exe -m pip install tox wheel pyinstaller Pillow"
build: off
test_script:
- "%PYTHON%\\python.exe setup.py test"
# don't run tests on windows because we don't have imagemagick
#test_script:
# - "%PYTHON%\\python.exe -m tox"
after_test:
- "%PYTHON%\\python.exe setup.py bdist_wheel"
- "%PYTHON%\\python.exe -m PyInstaller --clean --onefile --noconsole src/img2pdf.py"
- "%PYTHON%\\python.exe -m PyInstaller --clean --onefile --console --nowindowed --name img2pdf src/img2pdf.py"
#- "%PYTHON%\\python.exe -m PyInstaller --clean --onefile --noconsole --windowed --name img2pdf_windowed src/img2pdf.py"
artifacts:
- path: dist\*

307
magick.py
View file

@ -1,307 +0,0 @@
#!/usr/bin/env python3
import sys
import numpy
import scipy.signal
import zlib
import struct
def find_closest_palette_color(color, palette):
if color.ndim == 0:
idx = (numpy.abs(palette - color)).argmin()
else:
# naive distance function by computing the euclidean distance in RGB space
idx = ((palette - color) ** 2).sum(axis=-1).argmin()
return palette[idx]
def floyd_steinberg(img, palette):
for y in range(img.shape[0]):
for x in range(img.shape[1]):
oldpixel = img[y, x]
newpixel = find_closest_palette_color(oldpixel, palette)
quant_error = oldpixel - newpixel
img[y, x] = newpixel
if x + 1 < img.shape[1]:
img[y, x + 1] += quant_error * 7 / 16
if y + 1 < img.shape[0]:
img[y + 1, x - 1] += quant_error * 3 / 16
img[y + 1, x] += quant_error * 5 / 16
if x + 1 < img.shape[1] and y + 1 < img.shape[0]:
img[y + 1, x + 1] += quant_error * 1 / 16
return img
def convolve_rgba(img, kernel):
return numpy.stack(
(
scipy.signal.convolve2d(img[:, :, 0], kernel, "same"),
scipy.signal.convolve2d(img[:, :, 1], kernel, "same"),
scipy.signal.convolve2d(img[:, :, 2], kernel, "same"),
scipy.signal.convolve2d(img[:, :, 3], kernel, "same"),
),
axis=-1,
)
def rgb2gray(img):
result = numpy.zeros((60, 60), dtype=numpy.dtype("int64"))
for y in range(img.shape[0]):
for x in range(img.shape[1]):
clin = sum(img[y, x] * [0.2126, 0.7152, 0.0722]) / 0xFFFF
if clin <= 0.0031308:
csrgb = 12.92 * clin
else:
csrgb = 1.055 * clin ** (1 / 2.4) - 0.055
result[y, x] = csrgb * 0xFFFF
return result
def palettize(img, pal):
result = numpy.zeros((img.shape[0], img.shape[1]), dtype=numpy.dtype("int64"))
for y in range(img.shape[0]):
for x in range(img.shape[1]):
for i, col in enumerate(pal):
if numpy.array_equal(img[y, x], col):
result[y, x] = i
break
else:
raise Exception()
return result
def write_png(data, path, bitdepth, colortype, palette=None):
with open(path, "wb") as f:
f.write(b"\x89PNG\r\n\x1A\n")
# PNG image type Colour type Allowed bit depths
# Greyscale 0 1, 2, 4, 8, 16
# Truecolour 2 8, 16
# Indexed-colour 3 1, 2, 4, 8
# Greyscale with alpha 4 8, 16
# Truecolour with alpha 6 8, 16
block = b"IHDR" + struct.pack(
">IIBBBBB",
data.shape[1], # width
data.shape[0], # height
bitdepth, # bitdepth
colortype, # colortype
0, # compression
0, # filtertype
0, # interlaced
)
f.write(
struct.pack(">I", len(block) - 4)
+ block
+ struct.pack(">I", zlib.crc32(block))
)
if palette is not None:
block = b"PLTE"
for col in palette:
block += struct.pack(">BBB", col[0], col[1], col[2])
f.write(
struct.pack(">I", len(block) - 4)
+ block
+ struct.pack(">I", zlib.crc32(block))
)
raw = b""
for y in range(data.shape[0]):
raw += b"\0"
if bitdepth == 16:
raw += data[y].astype(">u2").tobytes()
elif bitdepth == 8:
raw += data[y].astype(">u1").tobytes()
elif bitdepth in [4, 2, 1]:
valsperbyte = 8 // bitdepth
for x in range(0, data.shape[1], valsperbyte):
val = 0
for j in range(valsperbyte):
if x + j >= data.shape[1]:
break
val |= (data[y, x + j].astype(">u2") & (2 ** bitdepth - 1)) << (
(valsperbyte - j - 1) * bitdepth
)
raw += struct.pack(">B", val)
else:
raise Exception()
compressed = zlib.compress(raw)
block = b"IDAT" + compressed
f.write(
struct.pack(">I", len(compressed))
+ block
+ struct.pack(">I", zlib.crc32(block))
)
block = b"IEND"
f.write(struct.pack(">I", 0) + block + struct.pack(">I", zlib.crc32(block)))
def main():
outdir = sys.argv[1]
# create a 256 color palette by first writing 16 shades of gray
# and then writing an array of RGB colors with 6, 8 and 5 levels
# for red, green and blue, respectively
pal8 = numpy.zeros((256, 3), dtype=numpy.dtype("int64"))
i = 0
for gray in range(15, 255, 15):
pal8[i] = [gray, gray, gray]
i += 1
for red in 0, 0x33, 0x66, 0x99, 0xCC, 0xFF:
for green in 0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF:
for blue in 0, 0x40, 0x80, 0xBF, 0xFF:
pal8[i] = [red, green, blue]
i += 1
assert i == 256
# windows 16 color palette
pal4 = numpy.array(
[
[0x00, 0x00, 0x00],
[0x80, 0x00, 0x00],
[0x00, 0x80, 0x00],
[0x80, 0x80, 0x00],
[0x00, 0x00, 0x80],
[0x80, 0x00, 0x80],
[0x00, 0x80, 0x80],
[0xC0, 0xC0, 0xC0],
[0x80, 0x80, 0x80],
[0xFF, 0x00, 0x00],
[0x00, 0xFF, 0x00],
[0xFF, 0x00, 0x00],
[0x00, 0xFF, 0x00],
[0xFF, 0x00, 0xFF],
[0x00, 0xFF, 0x00],
[0xFF, 0xFF, 0xFF],
],
dtype=numpy.dtype("int64"),
)
# choose values slightly off red, lime and blue because otherwise
# imagemagick will classify the image as Depth: 8/1-bit
pal2 = numpy.array(
[[0, 0, 0], [0xFE, 0, 0], [0, 0xFE, 0], [0, 0, 0xFE]],
dtype=numpy.dtype("int64"),
)
# don't choose black and white or otherwise imagemagick will classify the
# image as bilevel with 8/1-bit depth instead of palette with 8-bit color
# don't choose gray colors or otherwise imagemagick will classify the
# image as grayscale
pal1 = numpy.array(
[[0x01, 0x02, 0x03], [0xFE, 0xFD, 0xFC]], dtype=numpy.dtype("int64")
)
# gaussian kernel with sigma=3
kernel = numpy.array(
[
[0.011362, 0.014962, 0.017649, 0.018648, 0.017649, 0.014962, 0.011362],
[0.014962, 0.019703, 0.02324, 0.024556, 0.02324, 0.019703, 0.014962],
[0.017649, 0.02324, 0.027413, 0.028964, 0.027413, 0.02324, 0.017649],
[0.018648, 0.024556, 0.028964, 0.030603, 0.028964, 0.024556, 0.018648],
[0.017649, 0.02324, 0.027413, 0.028964, 0.027413, 0.02324, 0.017649],
[0.014962, 0.019703, 0.02324, 0.024556, 0.02324, 0.019703, 0.014962],
[0.011362, 0.014962, 0.017649, 0.018648, 0.017649, 0.014962, 0.011362],
],
numpy.float,
)
# constructs a 2D array of a circle with a width of 36
circle = list()
offsets_36 = [14, 11, 9, 7, 6, 5, 4, 3, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0]
for offs in offsets_36 + offsets_36[::-1]:
circle.append([0] * offs + [1] * (len(offsets_36) - offs) * 2 + [0] * offs)
alpha = numpy.zeros((60, 60, 4), dtype=numpy.dtype("int64"))
# draw three circles
for (xpos, ypos, color) in [
(12, 3, [0xFFFF, 0, 0, 0xFFFF]),
(21, 21, [0, 0xFFFF, 0, 0xFFFF]),
(3, 21, [0, 0, 0xFFFF, 0xFFFF]),
]:
for x, row in enumerate(circle):
for y, pos in enumerate(row):
if pos:
alpha[y + ypos, x + xpos] += color
alpha = numpy.clip(alpha, 0, 0xFFFF)
alpha = convolve_rgba(alpha, kernel)
write_png(alpha, outdir + "/alpha.png", 16, 6)
normal16 = alpha[:, :, 0:3]
write_png(normal16, outdir + "/normal16.png", 16, 2)
write_png(normal16 / 0xFFFF * 0xFF, outdir + "/normal.png", 8, 2)
write_png(0xFF - normal16 / 0xFFFF * 0xFF, outdir + "/inverse.png", 8, 2)
gray16 = rgb2gray(normal16)
write_png(gray16, outdir + "/gray16.png", 16, 0)
write_png(gray16 / 0xFFFF * 0xFF, outdir + "/gray8.png", 8, 0)
write_png(
floyd_steinberg(gray16, numpy.arange(16) / 0xF * 0xFFFF) / 0xFFFF * 0xF,
outdir + "/gray4.png",
4,
0,
)
write_png(
floyd_steinberg(gray16, numpy.arange(4) / 0x3 * 0xFFFF) / 0xFFFF * 0x3,
outdir + "/gray2.png",
2,
0,
)
write_png(
floyd_steinberg(gray16, numpy.arange(2) / 0x1 * 0xFFFF) / 0xFFFF * 0x1,
outdir + "/gray1.png",
1,
0,
)
write_png(
palettize(
floyd_steinberg(normal16, pal8 * 0xFFFF / 0xFF) / 0xFFFF * 0xFF, pal8
),
outdir + "/palette8.png",
8,
3,
pal8,
)
write_png(
palettize(
floyd_steinberg(normal16, pal4 * 0xFFFF / 0xFF) / 0xFFFF * 0xFF, pal4
),
outdir + "/palette4.png",
4,
3,
pal4,
)
write_png(
palettize(
floyd_steinberg(normal16, pal2 * 0xFFFF / 0xFF) / 0xFFFF * 0xFF, pal2
),
outdir + "/palette2.png",
2,
3,
pal2,
)
write_png(
palettize(
floyd_steinberg(normal16, pal1 * 0xFFFF / 0xFF) / 0xFFFF * 0xFF, pal1
),
outdir + "/palette1.png",
1,
3,
pal1,
)
if __name__ == "__main__":
main()

View file

@ -1,2 +0,0 @@
[metadata]
description-file = README.md

View file

@ -1,58 +1,51 @@
import sys
from setuptools import setup
VERSION = "0.3.5"
VERSION = "0.6.0"
INSTALL_REQUIRES = (
'Pillow',
"Pillow",
"pikepdf",
)
TESTS_REQUIRE = (
'pdfrw',
)
setup(
name='img2pdf',
name="img2pdf",
version=VERSION,
author="Johannes 'josch' Schauer",
author_email='josch@mister-muffin.de',
author="Johannes Schauer Marin Rodrigues",
author_email="josch@mister-muffin.de",
description="Convert images to PDF via direct JPEG inclusion.",
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
license="LGPL",
keywords="jpeg pdf converter",
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: Other Audience',
'Environment :: Console',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: Implementation :: CPython',
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Other Audience",
"Environment :: Console",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
'License :: OSI Approved :: GNU Lesser General Public License v3 '
'(LGPLv3)',
'Natural Language :: English',
'Operating System :: OS Independent'],
url='https://gitlab.mister-muffin.de/josch/img2pdf',
download_url='https://gitlab.mister-muffin.de/josch/img2pdf/repository/'
'archive.tar.gz?ref=' + VERSION,
"License :: OSI Approved :: GNU Lesser General Public License v3 " "(LGPLv3)",
"Natural Language :: English",
"Operating System :: OS Independent",
],
url="https://gitlab.mister-muffin.de/josch/img2pdf",
download_url="https://gitlab.mister-muffin.de/josch/img2pdf/repository/"
"archive.tar.gz?ref=" + VERSION,
package_dir={"": "src"},
py_modules=['img2pdf', 'jp2'],
py_modules=["img2pdf", "jp2"],
include_package_data=True,
test_suite='tests.test_suite',
zip_safe=True,
install_requires=INSTALL_REQUIRES,
tests_requires=TESTS_REQUIRE,
extras_require={
'test': TESTS_REQUIRE,
'gui': ('tkinter'),
"gui": ("tkinter"),
},
entry_points={
"setuptools.installation": ["eggsecutable = img2pdf:main"],
"console_scripts": ["img2pdf = img2pdf:main"],
"gui_scripts": ["img2pdf-gui = img2pdf:gui"],
},
)
)

File diff suppressed because it is too large Load diff

7222
src/img2pdf_test.py Executable file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Johannes 'josch' Schauer <j.schauer at email.de>
# Copyright (C) 2013 Johannes Schauer Marin Rodrigues <j.schauer at email.de>
#
# this module is heavily based upon jpylyzer which is
# KB / National Library of the Netherlands, Open Planets Foundation
@ -23,23 +23,22 @@ import struct
def getBox(data, byteStart, noBytes):
boxLengthValue = struct.unpack(">I", data[byteStart:byteStart+4])[0]
boxType = data[byteStart+4:byteStart+8]
boxLengthValue = struct.unpack(">I", data[byteStart : byteStart + 4])[0]
boxType = data[byteStart + 4 : byteStart + 8]
contentsStartOffset = 8
if boxLengthValue == 1:
boxLengthValue = struct.unpack(">Q", data[byteStart+8:byteStart+16])[0]
boxLengthValue = struct.unpack(">Q", data[byteStart + 8 : byteStart + 16])[0]
contentsStartOffset = 16
if boxLengthValue == 0:
boxLengthValue = noBytes-byteStart
boxLengthValue = noBytes - byteStart
byteEnd = byteStart + boxLengthValue
boxContents = data[byteStart+contentsStartOffset:byteEnd]
boxContents = data[byteStart + contentsStartOffset : byteEnd]
return (boxLengthValue, boxType, byteEnd, boxContents)
def parse_ihdr(data):
height = struct.unpack(">I", data[0:4])[0]
width = struct.unpack(">I", data[4:8])[0]
return width, height
height, width, channels, bpp = struct.unpack(">IIHB", data[:11])
return width, height, channels, bpp + 1
def parse_colr(data):
@ -52,14 +51,15 @@ def parse_colr(data):
elif enumCS == 17:
return "L"
else:
raise Exception("only sRGB and greyscale color space is supported, "
"got %d" % enumCS)
raise Exception(
"only sRGB and greyscale color space is supported, " "got %d" % enumCS
)
def parse_resc(data):
hnum, hden, vnum, vden, hexp, vexp = struct.unpack(">HHHHBB", data)
hdpi = ((hnum/hden) * (10**hexp) * 100)/2.54
vdpi = ((vnum/vden) * (10**vexp) * 100)/2.54
hdpi = ((hnum / hden) * (10**hexp) * 100) / 2.54
vdpi = ((vnum / vden) * (10**vexp) * 100) / 2.54
return hdpi, vdpi
@ -69,9 +69,8 @@ def parse_res(data):
byteStart = 0
boxLengthValue = 1 # dummy value for while loop condition
while byteStart < noBytes and boxLengthValue != 0:
boxLengthValue, boxType, byteEnd, boxContents = \
getBox(data, byteStart, noBytes)
if boxType == b'resc':
boxLengthValue, boxType, byteEnd, boxContents = getBox(data, byteStart, noBytes)
if boxType == b"resc":
hdpi, vdpi = parse_resc(boxContents)
break
return hdpi, vdpi
@ -83,16 +82,15 @@ def parse_jp2h(data):
byteStart = 0
boxLengthValue = 1 # dummy value for while loop condition
while byteStart < noBytes and boxLengthValue != 0:
boxLengthValue, boxType, byteEnd, boxContents = \
getBox(data, byteStart, noBytes)
if boxType == b'ihdr':
width, height = parse_ihdr(boxContents)
elif boxType == b'colr':
boxLengthValue, boxType, byteEnd, boxContents = getBox(data, byteStart, noBytes)
if boxType == b"ihdr":
width, height, channels, bpp = parse_ihdr(boxContents)
elif boxType == b"colr":
colorspace = parse_colr(boxContents)
elif boxType == b'res ':
elif boxType == b"res ":
hdpi, vdpi = parse_res(boxContents)
byteStart = byteEnd
return (width, height, colorspace, hdpi, vdpi)
return (width, height, colorspace, hdpi, vdpi, channels, bpp)
def parsejp2(data):
@ -101,10 +99,11 @@ def parsejp2(data):
boxLengthValue = 1 # dummy value for while loop condition
width, height, colorspace, hdpi, vdpi = None, None, None, None, None
while byteStart < noBytes and boxLengthValue != 0:
boxLengthValue, boxType, byteEnd, boxContents = \
getBox(data, byteStart, noBytes)
if boxType == b'jp2h':
width, height, colorspace, hdpi, vdpi = parse_jp2h(boxContents)
boxLengthValue, boxType, byteEnd, boxContents = getBox(data, byteStart, noBytes)
if boxType == b"jp2h":
width, height, colorspace, hdpi, vdpi, channels, bpp = parse_jp2h(
boxContents
)
break
byteStart = byteEnd
if not width:
@ -114,12 +113,41 @@ def parsejp2(data):
if not colorspace:
raise Exception("no colorspace in jp2 header")
# retrieving the dpi is optional so we do not error out if not present
return (width, height, colorspace, hdpi, vdpi)
return (width, height, colorspace, hdpi, vdpi, channels, bpp)
def parsej2k(data):
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack(
">HHIIIIIIIIH", data[4:42]
)
ssiz = [None] * csiz
xrsiz = [None] * csiz
yrsiz = [None] * csiz
for i in range(csiz):
ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack(
"BBB", data[42 + 3 * i : 42 + 3 * (i + 1)]
)
assert ssiz == [7, 7, 7]
return xsiz - xosiz, ysiz - yosiz, None, None, None, csiz, 8
def parse(data):
if data[:4] == b"\xff\x4f\xff\x51":
return parsej2k(data)
else:
return parsejp2(data)
if __name__ == "__main__":
import sys
width, height, colorspace = parsejp2(open(sys.argv[1]).read())
sys.stdout.write("width = %d" % width)
sys.stdout.write("height = %d" % height)
sys.stdout.write("colorspace = %s" % colorspace)
width, height, colorspace, hdpi, vdpi, channels, bpp = parse(
open(sys.argv[1], "rb").read()
)
print("width = %d" % width)
print("height = %d" % height)
print("colorspace = %s" % colorspace)
print("hdpi = %s" % hdpi)
print("vdpi = %s" % vdpi)
print("channels = %s" % channels)
print("bpp = %s" % bpp)

View file

@ -1,718 +0,0 @@
import unittest
import img2pdf
import os
import struct
import sys
import zlib
from PIL import Image
from io import StringIO, BytesIO, TextIOWrapper
HERE = os.path.dirname(__file__)
PdfReaderIO = StringIO
# Recompressing the image stream makes the comparison robust against output
# preserving changes in the zlib compress output bitstream
# (e.g. between different zlib implementations/versions/releases).
# Without this, some img2pdf 0.3.2 tests fail on Fedora 29/aarch64.
# See also:
# https://gitlab.mister-muffin.de/josch/img2pdf/issues/51
# https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/R7GD4L5Z6HELCDAL2RDESWR2F3ZXHWVX/
def recompress_last_stream(bs):
length_pos = bs.rindex(b'/Length')
li = length_pos + 8
lj = bs.index(b' ', li)
n = int(bs[li:lj])
stream_pos = bs.index(b'\nstream\n', lj)
si = stream_pos + 8
sj = si + n
startx_pos = bs.rindex(b'\nstartxref\n')
xi = startx_pos + 11
xj = bs.index(b'\n', xi)
m = int(bs[xi:xj])
unc_t = zlib.decompress(bs[si:sj])
t = zlib.compress(unc_t)
new_len = str(len(t)).encode('ascii')
u = (lj-li) + n
v = len(new_len) + len(t)
off = v - u
rs = (bs[:li] + new_len + bs[lj:si] + t + bs[sj:xi]
+ str(m+off).encode('ascii') + bs[xj:])
return rs
def compare_pdf(outx, outy):
if b'/FlateDecode' in outx:
x = recompress_last_stream(outx)
y = recompress_last_stream(outy)
if x != y:
print('original outx:\n{}\nouty:\n{}\n'.format(outx, outy), file=sys.stderr)
print('recompressed outx:\n{}\nouty:\n{}\n'.format(x, y), file=sys.stderr)
return False
else:
if outx != outy:
print('original outx:\n{}\nouty:\n{}\n'.format(outx, outy), file=sys.stderr)
return True
# convert +set date:create +set date:modify -define png:exclude-chunk=time
# we define some variables so that the table below can be narrower
psl = (972, 504) # --pagesize landscape
psp = (504, 972) # --pagesize portrait
isl = (756, 324) # --imgsize landscape
isp = (324, 756) # --imgsize portrait
border = (162, 270) # --border
poster = (97200, 50400)
# there is no need to have test cases with the same images with inverted
# orientation (landscape/portrait) because --pagesize and --imgsize are
# already inverted
im1 = (864, 288) # imgpx #1 => 648x216
im2 = (1152, 576) # imgpx #2 => 864x432
# shortcuts for fit modes
f_into = img2pdf.FitMode.into
f_fill = img2pdf.FitMode.fill
f_exact = img2pdf.FitMode.exact
f_shrink = img2pdf.FitMode.shrink
f_enlarge = img2pdf.FitMode.enlarge
layout_test_cases = [
# psp=972x504, psl=504x972, isl=756x324, isp=324x756, border=162:270
# --pagesize --border -a pagepdf imgpdf
# --imgsize --fit
(None, None, None, f_into, 0, (648, 216), (648, 216), # 000
(864, 432), (864, 432)),
(None, None, None, f_into, 1, (648, 216), (648, 216), # 001
(864, 432), (864, 432)),
(None, None, None, f_fill, 0, (648, 216), (648, 216), # 002
(864, 432), (864, 432)),
(None, None, None, f_fill, 1, (648, 216), (648, 216), # 003
(864, 432), (864, 432)),
(None, None, None, f_exact, 0, (648, 216), (648, 216), # 004
(864, 432), (864, 432)),
(None, None, None, f_exact, 1, (648, 216), (648, 216), # 005
(864, 432), (864, 432)),
(None, None, None, f_shrink, 0, (648, 216), (648, 216), # 006
(864, 432), (864, 432)),
(None, None, None, f_shrink, 1, (648, 216), (648, 216), # 007
(864, 432), (864, 432)),
(None, None, None, f_enlarge, 0, (648, 216), (648, 216), # 008
(864, 432), (864, 432)),
(None, None, None, f_enlarge, 1, (648, 216), (648, 216), # 009
(864, 432), (864, 432)),
(None, None, border, f_into, 0, (1188, 540), (648, 216), # 010
(1404, 756), (864, 432)),
(None, None, border, f_into, 1, (1188, 540), (648, 216), # 011
(1404, 756), (864, 432)),
(None, None, border, f_fill, 0, (1188, 540), (648, 216), # 012
(1404, 756), (864, 432)),
(None, None, border, f_fill, 1, (1188, 540), (648, 216), # 013
(1404, 756), (864, 432)),
(None, None, border, f_exact, 0, (1188, 540), (648, 216), # 014
(1404, 756), (864, 432)),
(None, None, border, f_exact, 1, (1188, 540), (648, 216), # 015
(1404, 756), (864, 432)),
(None, None, border, f_shrink, 0, (1188, 540), (648, 216), # 016
(1404, 756), (864, 432)),
(None, None, border, f_shrink, 1, (1188, 540), (648, 216), # 017
(1404, 756), (864, 432)),
(None, None, border, f_enlarge, 0, (1188, 540), (648, 216), # 018
(1404, 756), (864, 432)),
(None, None, border, f_enlarge, 1, (1188, 540), (648, 216), # 019
(1404, 756), (864, 432)),
(None, isp, None, f_into, 0, (324, 108), (324, 108), # 020
(324, 162), (324, 162)),
(None, isp, None, f_into, 1, (324, 108), (324, 108), # 021
(324, 162), (324, 162)),
(None, isp, None, f_fill, 0, (2268, 756), (2268, 756), # 022
(1512, 756), (1512, 756)),
(None, isp, None, f_fill, 1, (2268, 756), (2268, 756), # 023
(1512, 756), (1512, 756)),
(None, isp, None, f_exact, 0, (324, 756), (324, 756), # 024
(324, 756), (324, 756)),
(None, isp, None, f_exact, 1, (324, 756), (324, 756), # 025
(324, 756), (324, 756)),
(None, isp, None, f_shrink, 0, (324, 108), (324, 108), # 026
(324, 162), (324, 162)),
(None, isp, None, f_shrink, 1, (324, 108), (324, 108), # 027
(324, 162), (324, 162)),
(None, isp, None, f_enlarge, 0, (648, 216), (648, 216), # 028
(864, 432), (864, 432)),
(None, isp, None, f_enlarge, 1, (648, 216), (648, 216), # 029
(864, 432), (864, 432)),
(None, isp, border, f_into, 0, (864, 432), (324, 108), # 030
(864, 486), (324, 162)),
(None, isp, border, f_into, 1, (864, 432), (324, 108), # 031
(864, 486), (324, 162)),
(None, isp, border, f_fill, 0, (2808, 1080), (2268, 756), # 032
(2052, 1080), (1512, 756)),
(None, isp, border, f_fill, 1, (2808, 1080), (2268, 756), # 033
(2052, 1080), (1512, 756)),
(None, isp, border, f_exact, 0, (864, 1080), (324, 756), # 034
(864, 1080), (324, 756)),
(None, isp, border, f_exact, 1, (864, 1080), (324, 756), # 035
(864, 1080), (324, 756)),
(None, isp, border, f_shrink, 0, (864, 432), (324, 108), # 036
(864, 486), (324, 162)),
(None, isp, border, f_shrink, 1, (864, 432), (324, 108), # 037
(864, 486), (324, 162)),
(None, isp, border, f_enlarge, 0, (1188, 540), (648, 216), # 038
(1404, 756), (864, 432)),
(None, isp, border, f_enlarge, 1, (1188, 540), (648, 216), # 039
(1404, 756), (864, 432)),
(None, isl, None, f_into, 0, (756, 252), (756, 252), # 040
(648, 324), (648, 324)),
(None, isl, None, f_into, 1, (756, 252), (756, 252), # 041
(648, 324), (648, 324)),
(None, isl, None, f_fill, 0, (972, 324), (972, 324), # 042
(756, 378), (756, 378)),
(None, isl, None, f_fill, 1, (972, 324), (972, 324), # 043
(756, 378), (756, 378)),
(None, isl, None, f_exact, 0, (756, 324), (756, 324), # 044
(756, 324), (756, 324)),
(None, isl, None, f_exact, 1, (756, 324), (756, 324), # 045
(756, 324), (756, 324)),
(None, isl, None, f_shrink, 0, (648, 216), (648, 216), # 046
(648, 324), (648, 324)),
(None, isl, None, f_shrink, 1, (648, 216), (648, 216), # 047
(648, 324), (648, 324)),
(None, isl, None, f_enlarge, 0, (756, 252), (756, 252), # 048
(864, 432), (864, 432)),
(None, isl, None, f_enlarge, 1, (756, 252), (756, 252), # 049
(864, 432), (864, 432)),
# psp=972x504, psp=504x972, isl=756x324, isp=324x756, border=162:270
# --pagesize --border -a pagepdf imgpdf
# --imgsize --fit imgpx
(None, isl, border, f_into, 0, (1296, 576), (756, 252), # 050
(1188, 648), (648, 324)),
(None, isl, border, f_into, 1, (1296, 576), (756, 252), # 051
(1188, 648), (648, 324)),
(None, isl, border, f_fill, 0, (1512, 648), (972, 324), # 052
(1296, 702), (756, 378)),
(None, isl, border, f_fill, 1, (1512, 648), (972, 324), # 053
(1296, 702), (756, 378)),
(None, isl, border, f_exact, 0, (1296, 648), (756, 324), # 054
(1296, 648), (756, 324)),
(None, isl, border, f_exact, 1, (1296, 648), (756, 324), # 055
(1296, 648), (756, 324)),
(None, isl, border, f_shrink, 0, (1188, 540), (648, 216), # 056
(1188, 648), (648, 324)),
(None, isl, border, f_shrink, 1, (1188, 540), (648, 216), # 057
(1188, 648), (648, 324)),
(None, isl, border, f_enlarge, 0, (1296, 576), (756, 252), # 058
(1404, 756), (864, 432)),
(None, isl, border, f_enlarge, 1, (1296, 576), (756, 252), # 059
(1404, 756), (864, 432)),
(psp, None, None, f_into, 0, (504, 972), (504, 168), # 060
(504, 972), (504, 252)),
(psp, None, None, f_into, 1, (972, 504), (972, 324), # 061
(972, 504), (972, 486)),
(psp, None, None, f_fill, 0, (504, 972), (2916, 972), # 062
(504, 972), (1944, 972)),
(psp, None, None, f_fill, 1, (972, 504), (1512, 504), # 063
(972, 504), (1008, 504)),
(psp, None, None, f_exact, 0, (504, 972), (504, 972), # 064
(504, 972), (504, 972)),
(psp, None, None, f_exact, 1, (972, 504), (972, 504), # 065
(972, 504), (972, 504)),
(psp, None, None, f_shrink, 0, (504, 972), (504, 168), # 066
(504, 972), (504, 252)),
(psp, None, None, f_shrink, 1, (972, 504), (648, 216), # 067
(972, 504), (864, 432)),
(psp, None, None, f_enlarge, 0, (504, 972), (648, 216), # 068
(504, 972), (864, 432)),
(psp, None, None, f_enlarge, 1, (972, 504), (972, 324), # 069
(972, 504), (972, 486)),
(psp, None, border, f_into, 0, None, None, None, None), # 070
(psp, None, border, f_into, 1, None, None, None, None), # 071
(psp, None, border, f_fill, 0, (504, 972), (1944, 648), # 072
(504, 972), (1296, 648)),
(psp, None, border, f_fill, 1, (972, 504), (648, 216), # 073
(972, 504), (648, 324)),
(psp, None, border, f_exact, 0, None, None, None, None), # 074
(psp, None, border, f_exact, 1, None, None, None, None), # 075
(psp, None, border, f_shrink, 0, None, None, None, None), # 076
(psp, None, border, f_shrink, 1, None, None, None, None), # 077
(psp, None, border, f_enlarge, 0, (504, 972), (648, 216), # 078
(504, 972), (864, 432)),
(psp, None, border, f_enlarge, 1, (972, 504), (648, 216), # 079
(972, 504), (864, 432)),
(psp, isp, None, f_into, 0, (504, 972), (324, 108), # 080
(504, 972), (324, 162)),
(psp, isp, None, f_into, 1, (972, 504), (324, 108), # 081
(972, 504), (324, 162)),
(psp, isp, None, f_fill, 0, (504, 972), (2268, 756), # 082
(504, 972), (1512, 756)),
(psp, isp, None, f_fill, 1, (972, 504), (2268, 756), # 083
(972, 504), (1512, 756)),
(psp, isp, None, f_exact, 0, (504, 972), (324, 756), # 084
(504, 972), (324, 756)),
(psp, isp, None, f_exact, 1, (972, 504), (324, 756), # 085
(972, 504), (324, 756)),
(psp, isp, None, f_shrink, 0, (504, 972), (324, 108), # 086
(504, 972), (324, 162)),
(psp, isp, None, f_shrink, 1, (972, 504), (324, 108), # 087
(972, 504), (324, 162)),
(psp, isp, None, f_enlarge, 0, (504, 972), (648, 216), # 088
(504, 972), (864, 432)),
(psp, isp, None, f_enlarge, 1, (972, 504), (648, 216), # 089
(972, 504), (864, 432)),
(psp, isp, border, f_into, 0, (504, 972), (324, 108), # 090
(504, 972), (324, 162)),
(psp, isp, border, f_into, 1, (972, 504), (324, 108), # 091
(972, 504), (324, 162)),
(psp, isp, border, f_fill, 0, (504, 972), (2268, 756), # 092
(504, 972), (1512, 756)),
(psp, isp, border, f_fill, 1, (972, 504), (2268, 756), # 093
(972, 504), (1512, 756)),
(psp, isp, border, f_exact, 0, (504, 972), (324, 756), # 094
(504, 972), (324, 756)),
(psp, isp, border, f_exact, 1, (972, 504), (324, 756), # 095
(972, 504), (324, 756)),
(psp, isp, border, f_shrink, 0, (504, 972), (324, 108), # 096
(504, 972), (324, 162)),
(psp, isp, border, f_shrink, 1, (972, 504), (324, 108), # 097
(972, 504), (324, 162)),
(psp, isp, border, f_enlarge, 0, (504, 972), (648, 216), # 098
(504, 972), (864, 432)),
(psp, isp, border, f_enlarge, 1, (972, 504), (648, 216), # 099
(972, 504), (864, 432)),
# psp=972x504, psp=504x972, isl=756x324, isp=324x756, border=162:270
# --pagesize --border -a pagepdf imgpdf
# --imgsize --fit imgpx
(psp, isl, None, f_into, 0, (504, 972), (756, 252), # 100
(504, 972), (648, 324)),
(psp, isl, None, f_into, 1, (972, 504), (756, 252), # 101
(972, 504), (648, 324)),
(psp, isl, None, f_fill, 0, (504, 972), (972, 324), # 102
(504, 972), (756, 378)),
(psp, isl, None, f_fill, 1, (972, 504), (972, 324), # 103
(972, 504), (756, 378)),
(psp, isl, None, f_exact, 0, (504, 972), (756, 324), # 104
(504, 972), (756, 324)),
(psp, isl, None, f_exact, 1, (972, 504), (756, 324), # 105
(972, 504), (756, 324)),
(psp, isl, None, f_shrink, 0, (504, 972), (648, 216), # 106
(504, 972), (648, 324)),
(psp, isl, None, f_shrink, 1, (972, 504), (648, 216), # 107
(972, 504), (648, 324)),
(psp, isl, None, f_enlarge, 0, (504, 972), (756, 252), # 108
(504, 972), (864, 432)),
(psp, isl, None, f_enlarge, 1, (972, 504), (756, 252), # 109
(972, 504), (864, 432)),
(psp, isl, border, f_into, 0, (504, 972), (756, 252), # 110
(504, 972), (648, 324)),
(psp, isl, border, f_into, 1, (972, 504), (756, 252), # 111
(972, 504), (648, 324)),
(psp, isl, border, f_fill, 0, (504, 972), (972, 324), # 112
(504, 972), (756, 378)),
(psp, isl, border, f_fill, 1, (972, 504), (972, 324), # 113
(972, 504), (756, 378)),
(psp, isl, border, f_exact, 0, (504, 972), (756, 324), # 114
(504, 972), (756, 324)),
(psp, isl, border, f_exact, 1, (972, 504), (756, 324), # 115
(972, 504), (756, 324)),
(psp, isl, border, f_shrink, 0, (504, 972), (648, 216), # 116
(504, 972), (648, 324)),
(psp, isl, border, f_shrink, 1, (972, 504), (648, 216), # 117
(972, 504), (648, 324)),
(psp, isl, border, f_enlarge, 0, (504, 972), (756, 252), # 118
(504, 972), (864, 432)),
(psp, isl, border, f_enlarge, 1, (972, 504), (756, 252), # 119
(972, 504), (864, 432)),
(psl, None, None, f_into, 0, (972, 504), (972, 324), # 120
(972, 504), (972, 486)),
(psl, None, None, f_into, 1, (972, 504), (972, 324), # 121
(972, 504), (972, 486)),
(psl, None, None, f_fill, 0, (972, 504), (1512, 504), # 122
(972, 504), (1008, 504)),
(psl, None, None, f_fill, 1, (972, 504), (1512, 504), # 123
(972, 504), (1008, 504)),
(psl, None, None, f_exact, 0, (972, 504), (972, 504), # 124
(972, 504), (972, 504)),
(psl, None, None, f_exact, 1, (972, 504), (972, 504), # 125
(972, 504), (972, 504)),
(psl, None, None, f_shrink, 0, (972, 504), (648, 216), # 126
(972, 504), (864, 432)),
(psl, None, None, f_shrink, 1, (972, 504), (648, 216), # 127
(972, 504), (864, 432)),
(psl, None, None, f_enlarge, 0, (972, 504), (972, 324), # 128
(972, 504), (972, 486)),
(psl, None, None, f_enlarge, 1, (972, 504), (972, 324), # 129
(972, 504), (972, 486)),
(psl, None, border, f_into, 0, (972, 504), (432, 144), # 130
(972, 504), (360, 180)),
(psl, None, border, f_into, 1, (972, 504), (432, 144), # 131
(972, 504), (360, 180)),
(psl, None, border, f_fill, 0, (972, 504), (540, 180), # 132
(972, 504), (432, 216)),
(psl, None, border, f_fill, 1, (972, 504), (540, 180), # 133
(972, 504), (432, 216)),
(psl, None, border, f_exact, 0, (972, 504), (432, 180), # 134
(972, 504), (432, 180)),
(psl, None, border, f_exact, 1, (972, 504), (432, 180), # 135
(972, 504), (432, 180)),
(psl, None, border, f_shrink, 0, (972, 504), (432, 144), # 136
(972, 504), (360, 180)),
(psl, None, border, f_shrink, 1, (972, 504), (432, 144), # 137
(972, 504), (360, 180)),
(psl, None, border, f_enlarge, 0, (972, 504), (648, 216), # 138
(972, 504), (864, 432)),
(psl, None, border, f_enlarge, 1, (972, 504), (648, 216), # 139
(972, 504), (864, 432)),
(psl, isp, None, f_into, 0, (972, 504), (324, 108), # 140
(972, 504), (324, 162)),
(psl, isp, None, f_into, 1, (972, 504), (324, 108), # 141
(972, 504), (324, 162)),
(psl, isp, None, f_fill, 0, (972, 504), (2268, 756), # 142
(972, 504), (1512, 756)),
(psl, isp, None, f_fill, 1, (972, 504), (2268, 756), # 143
(972, 504), (1512, 756)),
(psl, isp, None, f_exact, 0, (972, 504), (324, 756), # 144
(972, 504), (324, 756)),
(psl, isp, None, f_exact, 1, (972, 504), (324, 756), # 145
(972, 504), (324, 756)),
(psl, isp, None, f_shrink, 0, (972, 504), (324, 108), # 146
(972, 504), (324, 162)),
(psl, isp, None, f_shrink, 1, (972, 504), (324, 108), # 147
(972, 504), (324, 162)),
(psl, isp, None, f_enlarge, 0, (972, 504), (648, 216), # 148
(972, 504), (864, 432)),
(psl, isp, None, f_enlarge, 1, (972, 504), (648, 216), # 149
(972, 504), (864, 432)),
# psp=972x504, psl=504x972, isl=756x324, isp=324x756, border=162:270
# --pagesize --border -a pagepdf imgpdf
# --imgsize --fit imgpx
(psl, isp, border, f_into, 0, (972, 504), (324, 108), # 150
(972, 504), (324, 162)),
(psl, isp, border, f_into, 1, (972, 504), (324, 108), # 151
(972, 504), (324, 162)),
(psl, isp, border, f_fill, 0, (972, 504), (2268, 756), # 152
(972, 504), (1512, 756)),
(psl, isp, border, f_fill, 1, (972, 504), (2268, 756), # 153
(972, 504), (1512, 756)),
(psl, isp, border, f_exact, 0, (972, 504), (324, 756), # 154
(972, 504), (324, 756)),
(psl, isp, border, f_exact, 1, (972, 504), (324, 756), # 155
(972, 504), (324, 756)),
(psl, isp, border, f_shrink, 0, (972, 504), (324, 108), # 156
(972, 504), (324, 162)),
(psl, isp, border, f_shrink, 1, (972, 504), (324, 108), # 157
(972, 504), (324, 162)),
(psl, isp, border, f_enlarge, 0, (972, 504), (648, 216), # 158
(972, 504), (864, 432)),
(psl, isp, border, f_enlarge, 1, (972, 504), (648, 216), # 159
(972, 504), (864, 432)),
(psl, isl, None, f_into, 0, (972, 504), (756, 252), # 160
(972, 504), (648, 324)),
(psl, isl, None, f_into, 1, (972, 504), (756, 252), # 161
(972, 504), (648, 324)),
(psl, isl, None, f_fill, 0, (972, 504), (972, 324), # 162
(972, 504), (756, 378)),
(psl, isl, None, f_fill, 1, (972, 504), (972, 324), # 163
(972, 504), (756, 378)),
(psl, isl, None, f_exact, 0, (972, 504), (756, 324), # 164
(972, 504), (756, 324)),
(psl, isl, None, f_exact, 1, (972, 504), (756, 324), # 165
(972, 504), (756, 324)),
(psl, isl, None, f_shrink, 0, (972, 504), (648, 216), # 166
(972, 504), (648, 324)),
(psl, isl, None, f_shrink, 1, (972, 504), (648, 216), # 167
(972, 504), (648, 324)),
(psl, isl, None, f_enlarge, 0, (972, 504), (756, 252), # 168
(972, 504), (864, 432)),
(psl, isl, None, f_enlarge, 1, (972, 504), (756, 252), # 169
(972, 504), (864, 432)),
(psl, isl, border, f_into, 0, (972, 504), (756, 252), # 170
(972, 504), (648, 324)),
(psl, isl, border, f_into, 1, (972, 504), (756, 252), # 171
(972, 504), (648, 324)),
(psl, isl, border, f_fill, 0, (972, 504), (972, 324), # 172
(972, 504), (756, 378)),
(psl, isl, border, f_fill, 1, (972, 504), (972, 324), # 173
(972, 504), (756, 378)),
(psl, isl, border, f_exact, 0, (972, 504), (756, 324), # 174
(972, 504), (756, 324)),
(psl, isl, border, f_exact, 1, (972, 504), (756, 324), # 175
(972, 504), (756, 324)),
(psl, isl, border, f_shrink, 0, (972, 504), (648, 216), # 176
(972, 504), (648, 324)),
(psl, isl, border, f_shrink, 1, (972, 504), (648, 216), # 177
(972, 504), (648, 324)),
(psl, isl, border, f_enlarge, 0, (972, 504), (756, 252), # 178
(972, 504), (864, 432)),
(psl, isl, border, f_enlarge, 1, (972, 504), (756, 252), # 179
(972, 504), (864, 432)),
(poster, None, None, f_fill, 0, (97200, 50400), (151200, 50400),
(97200, 50400), (100800, 50400)),
]
def tiff_header_for_ccitt(width, height, img_size, ccitt_group=4):
# Quick and dirty TIFF header builder from
# https://stackoverflow.com/questions/2641770
tiff_header_struct = '<' + '2s' + 'h' + 'l' + 'h' + 'hhll' * 8 + 'h'
return struct.pack(
tiff_header_struct,
b'II', # Byte order indication: Little indian
42, # Version number (always 42)
8, # Offset to first IFD
8, # Number of tags in IFD
256, 4, 1, width, # ImageWidth, LONG, 1, width
257, 4, 1, height, # ImageLength, LONG, 1, lenght
258, 3, 1, 1, # BitsPerSample, SHORT, 1, 1
259, 3, 1, ccitt_group, # Compression, SHORT, 1, 4 = CCITT Group 4
262, 3, 1, 1, # Threshholding, SHORT, 1, 0 = WhiteIsZero
273, 4, 1, struct.calcsize(
tiff_header_struct), # StripOffsets, LONG, 1, len of header
278, 4, 1, height, # RowsPerStrip, LONG, 1, lenght
279, 4, 1, img_size, # StripByteCounts, LONG, 1, size of image
0 # last IFD
)
class CommandLineTests(unittest.TestCase):
def test_main_help(self):
from contextlib import redirect_stdout
f = StringIO()
with redirect_stdout(f):
try:
img2pdf.main(['img2pdf', '--help'])
except SystemExit:
pass
res = f.getvalue()
self.assertIn('img2pdf', res)
def test_suite():
class TestImg2Pdf(unittest.TestCase):
pass
for i, (psopt, isopt, border, fit, ao, pspdf1, ispdf1,
pspdf2, ispdf2) in enumerate(layout_test_cases):
if isopt is not None:
isopt = ((img2pdf.ImgSize.abs, isopt[0]),
(img2pdf.ImgSize.abs, isopt[1]))
def layout_handler(
self, psopt, isopt, border, fit, ao, pspdf, ispdf, im):
layout_fun = img2pdf.get_layout_fun(psopt, isopt, border, fit, ao)
try:
pwpdf, phpdf, iwpdf, ihpdf = \
layout_fun(im[0], im[1], (img2pdf.default_dpi,
img2pdf.default_dpi))
self.assertEqual((pwpdf, phpdf), pspdf)
self.assertEqual((iwpdf, ihpdf), ispdf)
except img2pdf.NegativeDimensionError:
self.assertEqual(None, pspdf)
self.assertEqual(None, ispdf)
def layout_handler_im1(self, psopt=psopt, isopt=isopt, border=border,
fit=fit, ao=ao, pspdf=pspdf1, ispdf=ispdf1):
layout_handler(self, psopt, isopt, border, fit, ao, pspdf, ispdf,
im1)
setattr(TestImg2Pdf, "test_layout_%03d_im1" % i, layout_handler_im1)
def layout_handler_im2(self, psopt=psopt, isopt=isopt, border=border,
fit=fit, ao=ao, pspdf=pspdf2, ispdf=ispdf2):
layout_handler(self, psopt, isopt, border, fit, ao, pspdf, ispdf,
im2)
setattr(TestImg2Pdf, "test_layout_%03d_im2" % i, layout_handler_im2)
files = os.listdir(os.path.join(HERE, "input"))
for with_pdfrw, test_name in [(a, b) for a in [True, False]
for b in files]:
# we do not test animation.gif with pdfrw because it doesn't support
# saving hexadecimal palette data
if test_name == 'animation.gif' and with_pdfrw:
continue
inputf = os.path.join(HERE, "input", test_name)
if not os.path.isfile(inputf):
continue
outputf = os.path.join(HERE, "output", test_name+".pdf")
assert os.path.isfile(outputf)
def handle(self, f=inputf, out=outputf, with_pdfrw=with_pdfrw):
try:
from pdfrw import PdfReader, PdfName, PdfWriter
from pdfrw.py23_diffs import convert_load, convert_store
except ImportError:
# the test requires pdfrw
self.skipTest("this test requires pdfrw")
return
with open(f, "rb") as inf:
orig_imgdata = inf.read()
output = img2pdf.convert(orig_imgdata, nodate=True,
with_pdfrw=with_pdfrw)
x = PdfReader(PdfReaderIO(convert_load(output)))
self.assertEqual(sorted(x.keys()), [PdfName.Info, PdfName.Root,
PdfName.Size])
self.assertIn(x.Root.Pages.Count, ('1', '2'))
if len(x.Root.Pages.Kids) == '1':
self.assertEqual(x.Size, '7')
self.assertEqual(len(x.Root.Pages.Kids), 1)
elif len(x.Root.Pages.Kids) == '2':
self.assertEqual(x.Size, '10')
self.assertEqual(len(x.Root.Pages.Kids), 2)
self.assertEqual(x.Info, {})
self.assertEqual(sorted(x.Root.keys()), [PdfName.Pages,
PdfName.Type])
self.assertEqual(x.Root.Type, PdfName.Catalog)
self.assertEqual(sorted(x.Root.Pages.keys()),
[PdfName.Count, PdfName.Kids, PdfName.Type])
self.assertEqual(x.Root.Pages.Type, PdfName.Pages)
orig_img = Image.open(f)
for pagenum in range(len(x.Root.Pages.Kids)):
# retrieve the original image frame that this page was
# generated from
orig_img.seek(pagenum)
cur_page = x.Root.Pages.Kids[pagenum]
ndpi = orig_img.info.get("dpi", (96.0, 96.0))
# In python3, the returned dpi value for some tiff images will
# not be an integer but a float. To make the behaviour of
# img2pdf the same between python2 and python3, we convert that
# float into an integer by rounding.
# Search online for the 72.009 dpi problem for more info.
ndpi = (int(round(ndpi[0])), int(round(ndpi[1])))
imgwidthpx, imgheightpx = orig_img.size
pagewidth = 72.0*imgwidthpx/ndpi[0]
pageheight = 72.0*imgheightpx/ndpi[1]
def format_float(f):
if int(f) == f:
return str(int(f))
else:
return ("%.4f" % f).rstrip("0")
self.assertEqual(sorted(cur_page.keys()),
[PdfName.Contents, PdfName.MediaBox,
PdfName.Parent, PdfName.Resources,
PdfName.Type])
self.assertEqual(cur_page.MediaBox,
['0', '0', format_float(pagewidth),
format_float(pageheight)])
self.assertEqual(cur_page.Parent, x.Root.Pages)
self.assertEqual(cur_page.Type, PdfName.Page)
self.assertEqual(cur_page.Resources.keys(),
[PdfName.XObject])
self.assertEqual(cur_page.Resources.XObject.keys(),
[PdfName.Im0])
self.assertEqual(cur_page.Contents.keys(),
[PdfName.Length])
self.assertEqual(cur_page.Contents.Length,
str(len(cur_page.Contents.stream)))
self.assertEqual(cur_page.Contents.stream,
"q\n%.4f 0 0 %.4f 0.0000 0.0000 cm\n"
"/Im0 Do\nQ" % (pagewidth, pageheight))
imgprops = cur_page.Resources.XObject.Im0
# test if the filter is valid:
self.assertIn(
imgprops.Filter, [PdfName.DCTDecode, PdfName.JPXDecode,
PdfName.FlateDecode,
[PdfName.CCITTFaxDecode]])
# test if the image has correct size
self.assertEqual(imgprops.Width, str(orig_img.size[0]))
self.assertEqual(imgprops.Height, str(orig_img.size[1]))
# if the input file is a jpeg then it should've been copied
# verbatim into the PDF
if imgprops.Filter in [PdfName.DCTDecode,
PdfName.JPXDecode]:
self.assertEqual(
cur_page.Resources.XObject.Im0.stream,
convert_load(orig_imgdata))
elif imgprops.Filter == [PdfName.CCITTFaxDecode]:
tiff_header = tiff_header_for_ccitt(
int(imgprops.Width), int(imgprops.Height),
int(imgprops.Length), 4)
imgio = BytesIO()
imgio.write(tiff_header)
imgio.write(convert_store(
cur_page.Resources.XObject.Im0.stream))
imgio.seek(0)
im = Image.open(imgio)
self.assertEqual(im.tobytes(), orig_img.tobytes())
try:
im.close()
except AttributeError:
pass
elif imgprops.Filter == PdfName.FlateDecode:
# otherwise, the data is flate encoded and has to be equal
# to the pixel data of the input image
imgdata = zlib.decompress(
convert_store(cur_page.Resources.XObject.Im0.stream))
if imgprops.DecodeParms:
if orig_img.format == 'PNG':
pngidat, palette = img2pdf.parse_png(orig_imgdata)
elif orig_img.format == 'TIFF' \
and orig_img.info['compression'] == "group4":
offset, length = \
img2pdf.ccitt_payload_location_from_pil(
orig_img)
pngidat = orig_imgdata[offset:offset+length]
else:
pngbuffer = BytesIO()
orig_img.save(pngbuffer, format="png")
pngidat, palette = img2pdf.parse_png(
pngbuffer.getvalue())
self.assertEqual(zlib.decompress(pngidat), imgdata)
else:
colorspace = imgprops.ColorSpace
if colorspace == PdfName.DeviceGray:
colorspace = 'L'
elif colorspace == PdfName.DeviceRGB:
colorspace = 'RGB'
elif colorspace == PdfName.DeviceCMYK:
colorspace = 'CMYK'
else:
raise Exception("invalid colorspace")
im = Image.frombytes(colorspace,
(int(imgprops.Width),
int(imgprops.Height)),
imgdata)
if orig_img.mode == '1':
self.assertEqual(im.tobytes(),
orig_img.convert("L").tobytes())
elif orig_img.mode not in ("RGB", "L", "CMYK",
"CMYK;I"):
self.assertEqual(im.tobytes(),
orig_img.convert("RGB").tobytes())
# the python-pil version 2.3.0-1ubuntu3 in Ubuntu does
# not have the close() method
try:
im.close()
except AttributeError:
pass
# now use pdfrw to parse and then write out both pdfs and check the
# result for equality
y = PdfReader(out)
outx = BytesIO()
outy = BytesIO()
xwriter = PdfWriter()
ywriter = PdfWriter()
xwriter.trailer = x
ywriter.trailer = y
xwriter.write(outx)
ywriter.write(outy)
self.assertEqual(compare_pdf(outx.getvalue(), outy.getvalue()), True)
# the python-pil version 2.3.0-1ubuntu3 in Ubuntu does not have the
# close() method
try:
orig_img.close()
except AttributeError:
pass
if with_pdfrw:
setattr(TestImg2Pdf, "test_%s_with_pdfrw" % test_name, handle)
else:
setattr(TestImg2Pdf, "test_%s_without_pdfrw" % test_name, handle)
return unittest.TestSuite((
unittest.makeSuite(TestImg2Pdf),
unittest.makeSuite(CommandLineTests),
))

Binary file not shown.

Before

(image error) Size: 1.9 KiB

After

(image error) Size: 1.9 KiB

BIN
src/tests/input/mono.jb2 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

1449
test.sh

File diff suppressed because it is too large Load diff

17
tox.ini
View file

@ -1,9 +1,18 @@
# tox (https://tox.readthedocs.io/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
[tox]
envlist = py27,py35,pypy
envlist = py37, py38, py39, py310
skip_missing_interpreters = true
[testenv]
deps =
.[test]
pdfrw
pytest
pikepdf
numpy
scipy
commands =
python setup.py test -q
python -m pytest -vv