-
Notifications
You must be signed in to change notification settings - Fork 1
/
invertPDF.py
155 lines (128 loc) · 4.91 KB
/
invertPDF.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/python3
'''
A simple script for inverting the color of PDF files.
'''
__author__ = 'Christoff van Zyl'
__copyright__ = '"Copyright 2021, invertPDF'
__license__ = 'GPL'
from pathlib import Path
import pikepdf
from PyPDF2 import PdfReader
from pikepdf import Page, Name
def invertPDF(in_file, out_file, inv_ratio: float = 1.0):
'''
Inverts a PDF and saves it elsewhere.
Parameters:
in_file: The file to invert.
out_file: The destination of the inverted copy.
'''
# Setup
blend_dict = pikepdf.Dictionary(
Type=Name('/ExtGState'), BM=Name('/Exclusion'), ca=1, CA=1)
non_blend_dict = pikepdf.Dictionary(Type=Name('/ExtGState'), ca=1, CA=1)
xobj_dict = pikepdf.Dictionary({'/Type': Name('/XObject'), '/SubType': Name(
'/Form'), '/Group': {'/S': Name('/Transparency'), '/CS': Name('/DeviceRGB')}})
num_pages = 0
boxes = []
# Read approximate page coordinate sizes using different library (pikepdf's mediabox functionality appears broken (pikepdf 2.12))
pdf = PdfReader(in_file)
num_pages = len(pdf.pages)
for i in range(num_pages):
box = pdf.pages[i].mediabox
min_c = float(min(box))
max_c = float(max(box))
add = (max_c - min_c) * 10
min_c -= add
max_c += add
boxes.append([min_c, min_c, max_c - min_c, max_c - min_c])
with pikepdf.open(in_file) as pdf:
for i in range(num_pages):
page = Page(pdf.pages[i])
# Add Dictionaries to Page
name = page.add_resource(blend_dict, Name('/ExtGState'))
name2 = page.add_resource(non_blend_dict, Name('/ExtGState'))
# Create rectangles
front_rect = pdf.make_stream(bytes(
'q \n{0:.2} {0:.2} {0:.2} rg\n{1} gs\n{2} {3} {4} {5} re\n f\n Q'.format(inv_ratio, name, *boxes[i]), 'utf8'), xobj_dict)
back_rect = pdf.make_stream(bytes(
'q \n1.0 1.0 1.0 rg\n{} gs\n{} {} {} {} re\n f\n Q'.format(name2, *boxes[i]), 'utf8'))
# Add Rectangles to page
page.contents_add(back_rect, prepend=True)
page.contents_add(front_rect)
pdf.save(out_file)
def invert_files_to_folder(files, folder, inv_ratio: float = 1.0):
'''
Converts all given files and places their inverted copies in the given folder under the same file names.
Parameters:
files: Array of file names to be converted.
folder: The folder in which to put the inverted copies.
'''
folder.mkdir(parents=True, exist_ok=True)
for ifile in files:
try:
# add _inverted to the file name
inverted_pdf_file = folder / (ifile.stem + '_inverted.pdf')
invertPDF(str(ifile), str(inverted_pdf_file), inv_ratio)
print('Success: {}'.format(ifile.name))
except Exception as e:
print('Failed: {}'.format(ifile.name))
print(e)
if __name__ == '__main__':
import argparse
cli = argparse.ArgumentParser(
prog='InvertPDF',
description='Invert the color of PDFs at a file level.'
)
cli.add_argument(
'in_paths',
nargs='*',
help=(
'Input files or folders to convert for. If no paths are specified, defaults'
' to the current working directory.'
),
type=Path,
default=[Path.cwd()]
)
cli.add_argument(
'--global-out-path',
help=(
'Output path to place the inverted PDFs in. If not present, all'
' inverted PDFs will be stored in the same location as their respective original'
' file with and appended with `_inverted`.'),
type=Path
)
cli.add_argument(
'--local-out-path',
help='Use a dedicated relative inverted path when `global-out-path` is not specified.',
type=Path
)
cli.add_argument(
'--inv-ratio',
help=(
'Inversion ratio between 0 and 1. 0 corresponds no inversion and 1 corresponds to a true color inversion.'
' By default this is 0.9 to produce inversions that are easier on the eyes.'
),
type=float,
default=0.9
)
cli_args = cli.parse_args()
gop: Path = cli_args.global_out_path
lop: Path = cli_args.local_out_path
if lop and lop.is_absolute():
print(f'Error: Local output path: {lop} is not a relative directory')
exit(1)
inv_ratio = max(min(cli_args.inv_ratio, 1.0), 0.0)
for path in cli_args.in_paths:
if path.is_dir():
files_to_invert = path.glob('*.pdf')
out_path = path
elif path.is_file():
files_to_invert = [path]
out_path = path.parent
else:
print(f'Warning: {path} is not a directory or a file, skipping')
if gop:
out_path = gop
elif lop:
out_path = out_path.joinpath(lop)
invert_files_to_folder(files_to_invert, out_path, cli_args.inv_ratio)