169 lines
5.3 KiB
Python
169 lines
5.3 KiB
Python
import qrcode
|
|
import textwrap
|
|
from string import ascii_letters
|
|
|
|
from PIL import ImageFont, ImageDraw, Image as PILImage
|
|
|
|
from labeler.domain.objects import Image, LabelDefinition
|
|
from labeler.interfaces import Renderer
|
|
|
|
|
|
DPI = 360.0
|
|
|
|
|
|
def points_to_pixels(point_size: float):
|
|
return int(point_size * (72 / DPI))
|
|
|
|
|
|
class PILRenderer(Renderer):
|
|
def __init__(self):
|
|
self.font_path = "/Library/Fonts/Arial Unicode.ttf"
|
|
|
|
def render_label(self, label_definition: LabelDefinition) -> Image:
|
|
if label_definition.length is None:
|
|
pil_image = self.__render_no_fixed_lenth(label_definition)
|
|
else:
|
|
pil_image = self.__render_fixed_length(label_definition)
|
|
|
|
return Image.from_pil(pil_image)
|
|
|
|
def __render_fixed_length(self, label_definition: LabelDefinition):
|
|
width = label_definition.pixel_width
|
|
length = label_definition.pixel_length
|
|
font, text = self.__get_font(
|
|
label_definition.text,
|
|
width,
|
|
length,
|
|
)
|
|
im = PILImage.new("1", (length, width), 1)
|
|
draw = ImageDraw.Draw(im)
|
|
draw.text(
|
|
(length / 2, width / 2),
|
|
text,
|
|
font=font,
|
|
fill=0,
|
|
anchor="mm",
|
|
align="center",
|
|
)
|
|
return im
|
|
|
|
def __render_no_fixed_lenth(self, label_definition: LabelDefinition):
|
|
lines_to_print = label_definition.text.count("\n") + 1
|
|
text = "\n".join([line.strip() for line in label_definition.text.split("\n")])
|
|
|
|
text_height = label_definition.pixel_width // lines_to_print
|
|
|
|
while text_height > 0:
|
|
font = ImageFont.truetype(
|
|
"/Library/Fonts/Arial Unicode.ttf",
|
|
text_height,
|
|
)
|
|
if lines_to_print > 1:
|
|
occupied_height = font.getsize_multiline(text)[1]
|
|
else:
|
|
occupied_height = font.getsize(text)[1]
|
|
if occupied_height <= label_definition.pixel_width:
|
|
break
|
|
text_height -= 1
|
|
|
|
sizes = [font.getsize(line) for line in text.split("\n")]
|
|
|
|
length = max(length for length, height in sizes)
|
|
|
|
im = PILImage.new("1", (length, label_definition.pixel_width), 1)
|
|
draw = ImageDraw.Draw(im)
|
|
draw.text(
|
|
(length / 2, label_definition.pixel_width / 2),
|
|
text,
|
|
font=font,
|
|
fill=0,
|
|
anchor="mm",
|
|
align="center",
|
|
)
|
|
return im
|
|
|
|
def render_qrcode(self, label_definition: LabelDefinition) -> Image:
|
|
width = label_definition.pixel_width
|
|
length = label_definition.pixel_length
|
|
text = label_definition.text
|
|
font = self.__get_font_for_qr()
|
|
qr = qrcode.QRCode(box_size=7)
|
|
qr.add_data(f"https://hs3.pl/db/{text}")
|
|
qr.make(fit=True)
|
|
qr_img = qr.make_image()
|
|
print(width, length)
|
|
pil_image = PILImage.new("1", (width, length), 1)
|
|
draw = ImageDraw.Draw(pil_image)
|
|
pil_image.paste(qr_img)
|
|
draw.text(
|
|
(width / 2, 232),
|
|
"HS3-DB",
|
|
font=font,
|
|
fill=0,
|
|
anchor="mm",
|
|
align="center",
|
|
)
|
|
draw.text(
|
|
(20, 232 + 34),
|
|
f"ID: {text}",
|
|
font=font,
|
|
fill=0,
|
|
align="left",
|
|
)
|
|
|
|
return Image.from_pil(pil_image.transpose(PILImage.Transpose.ROTATE_90))
|
|
|
|
def __get_font_for_qr(self):
|
|
font_size = int(360 / 9)
|
|
|
|
return ImageFont.truetype("fonts/SourceCodePro-SemiBold.ttf", font_size)
|
|
|
|
def __get_font(self, text: str, max_width: int, max_length: int):
|
|
font_size = max_width
|
|
step = max_width // 2
|
|
last_good = None
|
|
last_corrected = None
|
|
while step > 1:
|
|
fits, corrected = self.__will_font_fit(
|
|
text, self.font_path, font_size, max_width, max_length
|
|
)
|
|
if fits:
|
|
last_good = font_size
|
|
last_corrected = corrected
|
|
font_size += step
|
|
else:
|
|
font_size -= step
|
|
step //= 2
|
|
|
|
return ImageFont.truetype(self.font_path, last_good), last_corrected
|
|
|
|
def __will_font_fit(
|
|
self, text: str, font_path: str, font_size: int, max_width: int, max_length: int
|
|
):
|
|
font = ImageFont.truetype(font_path, font_size)
|
|
if "\n" in text:
|
|
text_width, text_height = font.getsize_multiline(text)
|
|
else:
|
|
text_width, text_height = font.getsize(text)
|
|
|
|
if text_height > max_width:
|
|
return False, None
|
|
|
|
if text_width <= max_length:
|
|
# Now we know that the text fits. We can stop trying
|
|
return True, text
|
|
|
|
avg_char_width = sum(font.getsize(char)[0] for char in ascii_letters) / len(
|
|
ascii_letters
|
|
)
|
|
charachters_per_line = max_length // avg_char_width
|
|
if charachters_per_line < max(len(line) for line in text.split(" ")):
|
|
return False, None
|
|
|
|
wrapped = textwrap.fill(text, charachters_per_line)
|
|
wrapped_width, wrapped_height = font.getsize_multiline(wrapped)
|
|
if wrapped_height <= max_width and wrapped_width <= max_length:
|
|
# Now we know that the text fits. We can stop trying
|
|
return True, wrapped
|
|
|
|
return False, None
|