Skip to content

Instantly share code, notes, and snippets.

@mralext20
Last active June 23, 2024 00:15
Show Gist options
  • Save mralext20/03fbfbc33885ccb3a1a46629fd82a672 to your computer and use it in GitHub Desktop.
Save mralext20/03fbfbc33885ccb3a1a46629fd82a672 to your computer and use it in GitHub Desktop.
generates QR codes with letter and number pairs, saves to a PDF
import os
from PIL import Image
from PIL import ImageDraw
from qrcode.main import QRCode
from qrcode.constants import ERROR_CORRECT_L
from PIL import ImageFont
from typing import List, Sequence, Generator, Tuple, TypeVar, Union
import math
import shutil
import time
import multiprocessing as mp
t = time.process_time()
_T = TypeVar("_T")
def grouper(iterable: Sequence[_T], n: int) -> Generator[Sequence[_T], None, None]:
"""
given a iterable, yield that iterable back in chunks of size n. last item will be any size.
"""
for i in range(math.ceil(len(iterable) / n)):
yield iterable[i * n : i * n + n]
FONT = ImageFont.truetype("font.ttf", 500)
requests: List[Tuple[str, int]] = []
HEIGHT = 2.5 # inches
# aisles = {"MD1": 25, "GR1": 25, "GM1": 20, "FT1": 17, "FT2": 20, "BK1": 30}
# locators = (LETTER, (RANGE_START,RANGE_END), COUNT)
locators: List[Tuple[str, Tuple[int, int], int]] = [
("A", (1, 4), 2),
("B", 1,12, 1)
]
for entry in locators:
letter = entry[0]
count = entry[1]
if len(entry) != 3:
entry = (letter, count, 1)
for code in range(entry[2]):
for number in range(count[0], count[1] + 1):
requests.append((letter, number))
images = []
cover = Image.new("RGB", (2550, 3300), color=(255, 255, 255))
text = ""
for letter, locatorsRange, count in locators:
text += f"{letter}: {locatorsRange[0]}-{locatorsRange[1]} X {count}\n"
ImageDraw.Draw(cover).text((75, 75), f"{len(requests)} QR Codes\n{text}", font_size=100, fill="black")
images.append(cover)
cover.save("test.png")
# solve how many can i fit on a page?
# page dims are 8.5x11 inches, with a 0.25 inch margin
# 8.5 - 2*0.25 = 8 inches wide
# 11 - 2*0.25 = 10.5 inches tall
# we want each qr code to be HEIGHT inches tall, and 1.6 * HEIGHT inches wide. 1.6 is the aspect ratio of the qr code.
# so we can fit 10.5 / HEIGHT qr codes tall, and 8 / (1.6 * HEIGHT) qr codes wide
per_vert = math.floor(10.5 / HEIGHT)
per_horiz = math.floor(8 / (1.6 * HEIGHT))
# we should check if we can fit more qr codes on the page by rotating the QR codes
extra_sideways = 0
if per_horiz * (1.6 * HEIGHT) < 8 - HEIGHT:
# how many extra?
extra_sideways = math.floor((10.5 - HEIGHT) / HEIGHT)
def generate_qr(data: Tuple[str, int]) -> Image.Image:
single = Image.new("RGB", (1600, 1000), color=(255, 255, 255))
letter = str(data[0])
number = data[1]
qr = QRCode(
version=1,
error_correction=ERROR_CORRECT_L,
box_size=50,
border=0,
)
qr.add_data(f"{letter}-{number}")
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
single.paste(
qr_img.resize((925, 925)),
(37, 37),
)
ImageDraw.Draw(single).rectangle( # Border line
((0, 0, 1600, 1000)),
fill=None,
outline="black",
width=5,
)
# # first letter
ImageDraw.Draw(single).text(
(1000, 0),
letter[-2] if len(letter) > 1 else "",
font=FONT,
fill="black",
)
# second letter
ImageDraw.Draw(single).text(
(1300, 0),
letter[-1],
font=FONT,
fill="black",
) # letter[-1]
# First Number
ImageDraw.Draw(single).text(
(1000, 425),
f"{(number // 10) % 10}",
font=FONT,
fill="black", # f"{(number // 10) % 10}"
)
# Second Number
ImageDraw.Draw(single).text(
(1300, 425),
f"{number % 10}",
font=FONT,
fill="black", # f"{number % 10}"
)
return single
def page_builder(group):
page = Image.new("RGB", (2550, 3300), color=(255, 255, 255))
grid, extra = group[: per_vert * per_horiz], group[per_vert * per_horiz :]
for i, graphic in enumerate(grid):
x = ((i % per_horiz) * int(HEIGHT * 300 * 1.6)) + 75
y = ((i // per_horiz) * int(HEIGHT * 300)) + 75
page.paste(
graphic.resize((int(HEIGHT * 300 * 1.6), int(HEIGHT * 300))),
(x, y),
)
for i, graphic in enumerate(extra):
# starting from the right edge of the grid , go down the page, sideways
x = per_horiz * int(HEIGHT * 300 * 1.6) + 75
y = (i * int(HEIGHT * 1.6 * 300)) + 75
g = graphic.resize((int(HEIGHT * 300 * 1.6), int(HEIGHT * 300)))
g.show()
g = g.rotate(90, expand=True)
page.paste(
g,
(x, y),
)
return page
if __name__ == "__main__":
mp.freeze_support()
# we can fit a total of per_vert * per_horiz + extra_sideways qr codes on the page
print(
f"Height: {HEIGHT}, per_vert: {per_vert}, per_horiz: {per_horiz}, extra_sideways: {extra_sideways}, total: {per_vert * per_horiz + extra_sideways}/page, {math.ceil(len(requests)/(per_vert * (per_horiz + extra_sideways)))} total pages for {len(requests)} qr codes."
)
print(f"Time PRE CALC DONE: {time.process_time() - t}")
requested_graphics = mp.Pool().map(generate_qr, requests)
print(f"Time QR GENERATED: {time.process_time() - t}")
images = mp.Pool().map(
page_builder,
grouper(requested_graphics, per_vert * per_horiz + extra_sideways),
)
print(f"Time START PDF GEN: {time.process_time() - t}")
images[0].save(
"out_all.pdf",
save_all=True,
append_images=images[1:],
resolution=100.0,
)
shutil.make_archive("qrcodes", "zip", ".", "out_all.pdf")
print(f"Time: {time.process_time() - t}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment