-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathticketGenerator.py
More file actions
235 lines (208 loc) · 9.11 KB
/
ticketGenerator.py
File metadata and controls
235 lines (208 loc) · 9.11 KB
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
from PIL import Image, ImageFont, ImageDraw, ImageOps
from PIL.ImageQt import ImageQt
import math
from mySettings import mySettings
class ticketGenerator:
def __init__(self):
print("ticketGenerator created")
self.x_cards = 1
self.y_cards = 1
# need to correct from the dpi of the template (300) to the dpi assumed by pil (72)
self.dpiCorrection = int(300 / 72)
self.default_fonts = {
"name": {"family": "arial", "size": 14, "file": ""},
"datum": {"family": "arial", "size": 9, "file": ""},
"platz": {"family": "arial", "size": 20, "file": ""},
}
self.font_settings = {k: v.copy() for k, v in self.default_fonts.items()}
self.font = ImageFont.truetype(
self.default_fonts["name"]["family"],
self.default_fonts["name"]["size"] * self.dpiCorrection,
)
self.black = (0, 0, 0)
self.path_to_template = ""
def setCardsPerPage(self, x, y):
self.x_cards = x
self.y_cards = y
self.cards_per_page = self.x_cards * self.y_cards
if self.path_to_template != "":
self.setBaseImage(self.path_to_template)
def setDate(self, date):
self.date = date
def setPositions(self, settings):
# load the positions from the settings
positionSettings = settings.getItemValue("positionen")
# read the x and y positions and the rotation of the name attribute
nameSettings = positionSettings["name"]
self.namePosition = (
int(nameSettings["x"]) * self.dpiCorrection,
int(nameSettings["y"]) * self.dpiCorrection,
)
self.nameRotation = int(nameSettings["r"])
self.nameCentered = bool(nameSettings["c"])
# read the x and y positions and the rotation of the platz attribute
placeSettings = positionSettings["platz"]
self.platzPosition = (
int(placeSettings["x"]) * self.dpiCorrection,
int(placeSettings["y"]) * self.dpiCorrection,
)
self.platzRotation = int(placeSettings["r"])
self.platzCentered = bool(placeSettings["c"])
# read the x and y positions and the rotation of the date attribute
dateSettings = positionSettings["datum"]
self.datePosition = (
int(dateSettings["x"]) * self.dpiCorrection,
int(dateSettings["y"]) * self.dpiCorrection,
)
self.dateRotation = int(dateSettings["r"])
self.dateCentered = bool(dateSettings["c"])
self.__read_fonts(settings)
def setBaseImage(self, path_to_img):
template = Image.open(path_to_img)
self.path_to_template = path_to_img
self.height = int(template.height * self.y_cards)
self.width = int(template.width * self.x_cards)
self.x_spacing = self.width / self.x_cards
self.y_spacing = self.height / self.y_cards
def __add_attribute(
self, card: Image, text: str, position: tuple, rotation: int, centered: bool
):
# write the value to a temp text to rotate it before adding to the base
# 800 x 50 is the max size for text fileds... maybe do this according to text size
# create a dummy drawing canvas to get the needed text box size
dmy = Image.new("L", (1, 1))
dmyDraw = ImageDraw.Draw(dmy)
# get the size of the text to use for centering
_, _, w, h = dmyDraw.textbbox((0, 0), text, font=self.font)
# create a new template matching the text size
textImage = Image.new("L", (w, h))
draw = ImageDraw.Draw(textImage)
# add the text to the template image
draw.text((0, 0), text, font=self.font, fill=255)
# rotate the text if desired
if centered:
# add the centered text
# if the text is centered use only the y information and center around x
position = (
int(((self.width / self.dpiCorrection) / 2) - (w / 2)) + position[0],
position[1],
)
# if the text is centered do not rotate
textImage = textImage.rotate(0, expand=1)
else:
# add the desired rotation to the text. This is only possible if not centered.
textImage = textImage.rotate(rotation, expand=1)
# if the text is not centered don't alter the position
# add the text
card.paste(
ImageOps.colorize(textImage, (0, 0, 0), (0, 0, 0)), position, textImage
)
def createCard(self, customer):
# load base image
print(customer)
card = Image.open(self.path_to_template)
# add the data to the ticket and return it
# set the font size for the base attributes
self.font = self.__load_font("name")
self.__add_attribute(
card=card,
text=str(customer["name"]),
position=self.namePosition,
rotation=self.nameRotation,
centered=self.nameCentered,
)
# set the font size for the date
self.font = self.__load_font("datum")
self.__add_attribute(
card=card,
text=self.date,
position=self.datePosition,
rotation=self.dateRotation,
centered=self.dateCentered,
)
# increase the font size for the place to make it better readable
self.font = self.__load_font("platz")
self.__add_attribute(
card=card,
text=str(customer["place"]),
position=self.platzPosition,
rotation=self.platzRotation,
centered=self.platzCentered,
)
return card
def createBlanco(self):
# load base image and return as blanco card
card = Image.open(self.path_to_template)
editable = ImageDraw.Draw(card)
return card
# change this function, so that all tickets get placed in stacks instead of on one sheet.
# therefore place one should be on page one , place 2 on page 2 and so on.
# if the sorting should be by page, only switch up that the page counter is the outermost loop
def createOutput(self, tickets, path):
pages = []
# get page cnt
pages_total = len(tickets) / (self.x_cards * self.y_cards)
pages_total = math.ceil(pages_total)
# create all pages
for p in range(pages_total):
pages.append(Image.new("RGB", (self.width, self.height), (255, 255, 255)))
current_card = 0
for y in range(self.y_cards):
for x in range(self.x_cards):
current_x = int(x * self.x_spacing)
current_y = int(y * self.y_spacing)
for p in range(pages_total):
# save to pages
if current_card < len(tickets):
print(
"cardNo: ", current_card, " p: ", p, " x: ", x, " y: ", y
)
pages[p].paste(
tickets[current_card],
(current_x, current_y),
tickets[current_card],
)
current_card += 1
# save output to pdf
pages[0].save(path, save_all=True, append_images=pages[1:], dpi=(300, 300))
# close memory intensive objects
for p in pages:
p.close()
del pages[:]
for t in tickets:
t.close()
del tickets[:]
def __read_fonts(self, settings):
font_settings = settings.getItemValue("fonts")
# reset to defaults then overwrite with user settings
self.font_settings = {k: v.copy() for k, v in self.default_fonts.items()}
if font_settings is None:
return
for key, default in self.default_fonts.items():
if key in font_settings:
entry = font_settings[key]
self.font_settings[key]["family"] = entry.get("family", default["family"])
try:
self.font_settings[key]["size"] = int(
entry.get("size", default["size"])
)
except (TypeError, ValueError):
self.font_settings[key]["size"] = default["size"]
self.font_settings[key]["file"] = entry.get("file", default["file"])
def __load_font(self, attribute):
font_entry = self.font_settings.get(attribute, self.default_fonts["name"])
family = font_entry.get("family", self.default_fonts["name"]["family"])
size = int(font_entry.get("size", self.default_fonts["name"]["size"]))
font_file = font_entry.get("file", "")
# prefer explicit file path if provided
if font_file:
try:
return ImageFont.truetype(font_file, size * self.dpiCorrection)
except OSError:
# if provided file fails, continue with family fallback
pass
try:
return ImageFont.truetype(family, size * self.dpiCorrection)
except OSError:
# fall back to arial if custom font is missing
return ImageFont.truetype("arial", size * self.dpiCorrection)