diff --git a/create_preview.py b/create_preview.py index c55f67b..b598c91 100644 --- a/create_preview.py +++ b/create_preview.py @@ -3,6 +3,9 @@ from store.utils import create_preview for i in range(1, 5): order_id = f'result{i}' - image = create_preview(f'source{i}.jpeg', size=40, price_rub=100, title='Adidas Originals') + image = create_preview(f'source{i}.jpeg', + size=40, + price_rub=100.5, + title_lines=['New Balance', 'NB 9060']) image.save(f'{order_id}.jpg') diff --git a/store/models.py b/store/models.py index 4343795..d5e413a 100644 --- a/store/models.py +++ b/store/models.py @@ -407,10 +407,6 @@ class Checklist(models.Model): return self.images.filter(type=Image.DEFAULT) - @property - def title(self): - return concat_not_null_values(self.brand, self.model) - def __str__(self): return f'{self.id}' @@ -419,11 +415,12 @@ class Checklist(models.Model): if not source_img: return + title_lines = [v for v in (self.brand, self.model) if v is not None] # Render preview image preview = create_preview(source_img.image.path, size=self.size, price_rub=self.full_price, - title=self.title) + title_lines=title_lines) # Prepare bytes image_io = BytesIO() diff --git a/store/utils.py b/store/utils.py index 9427731..d4d2f98 100644 --- a/store/utils.py +++ b/store/utils.py @@ -1,16 +1,48 @@ import os import textwrap +from typing import Tuple from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError from poizonstore.settings import BASE_DIR -def create_preview(source_image: str, size=None, price_rub=None, title=None): +def create_preview(source_image: str, size=None, price_rub=None, title_lines=None): + # Create image + preview_width, preview_height = 800, 600 + hor_padding = 15 + vert_padding = 50 + left_block_width = 270 # minimal width + + canvas_img = Image.new('RGBA', (preview_width, preview_height), color='white') + draw = ImageDraw.Draw(canvas_img) + def get_font(font_size): font_path = os.path.join(BASE_DIR, 'static', 'preview_image_font.ttf') return ImageFont.truetype(font_path, size=font_size) + def draw_text(xy: Tuple[float, float], text: str, font: ImageFont, multiline_width: int = None, fill: str = None): + x, y = xy + + block_width = 0 + block_height = 0 + + if multiline_width is None: + text = [text] + else: + text = textwrap.wrap(text, width=multiline_width) + + for line in text: + _, _, line_width, line_height = font.getbbox(line) + + draw.text((x, y), line, font=font, fill=fill) + y += line_height + + block_width = max(block_width, line_width) + block_height += line_height + + return block_width, block_height + def resize_with_ar(image, size): max_w, max_h = size @@ -24,57 +56,63 @@ def create_preview(source_image: str, size=None, price_rub=None, title=None): if not os.path.isfile(source_image): return None - # Create image - preview_width, preview_height = 800, 600 - hor_padding = 15 - vert_padding = 50 - - canvas_img = Image.new('RGBA', (preview_width, preview_height), color='white') - draw = ImageDraw.Draw(canvas_img) - # Draw top text top_font = get_font(20) text = 'Заказ в Poizon Store' - draw.text((hor_padding, vert_padding), text, font=top_font, fill='black') + block_size = draw_text((hor_padding, vert_padding), text, font=top_font, fill='black') + left_block_width = max(left_block_width, block_size[0] + hor_padding) # Draw title - if title: + title_x, title_y = hor_padding, vert_padding + 30 + if title_lines: title_font = get_font(40) - text = title - wrapped_text = textwrap.wrap(text, width=10) - x, start_y = hor_padding, vert_padding + 30 - for line in wrapped_text: - draw.text((x, start_y), line, font=title_font, fill='black') - start_y += title_font.getbbox(line)[3] + for title_line in title_lines: + width, height = draw_text((title_x, title_y), title_line, font=title_font, multiline_width=12, fill='black') + title_y += height + + left_block_width = max(left_block_width, width + title_x) # Draw size if size: size_text = str(size) size_font = get_font(20) - x1, y1 = hor_padding, vert_padding + 30 + 100 - x2, y2 = x1 + 40, y1 + 40 - draw.rectangle((x1, y1, x2, y2), fill='black', width=2) - draw.text((x1 + 7, y1 + 7), size_text, font=size_font, fill='white') + + rect_x, rect_y = hor_padding, title_y + 30 + rect_width, rect_height = 40, 40 + + # size text is slightly inside a rect + size_x, size_y = rect_x + 7, rect_y + 7 + + draw.rectangle((rect_x, rect_y, rect_x + rect_width, rect_y + rect_height), fill='black', width=2) + block_size = draw_text((size_x, size_y), size_text, font=size_font, fill='white') + left_block_width = max(left_block_width, max(block_size[0], rect_width)) # Draw price if price_rub: price_text = f"{price_rub:.2f} ₽" price_font = get_font(50) - draw.text((hor_padding + 15, preview_height - 100), price_text, font=price_font, fill='black') + price_x, price_y = hor_padding + 15, preview_height - 100 + + block_size = draw_text((price_x, price_y), price_text, font=price_font, fill='black') + left_block_width = max(left_block_width, block_size[0] + price_x) # Draw goods image - img2_box_w = preview_width - 270 - hor_padding * 2 + img2_box_w = preview_width - left_block_width - hor_padding * 2 img2_box_y = preview_height - vert_padding * 2 try: with Image.open(source_image).convert("RGBA") as img2: img2 = resize_with_ar(img2, (img2_box_w, img2_box_y)) - img2_x = 270 + int(img2_box_w / 2) - int(img2.width / 2) + img2_x = left_block_width + int(img2_box_w / 2) - int(img2.width / 2) img2_y = 50 + int(img2_box_y / 2) - int(img2.height / 2) canvas_img.paste(img2, (img2_x, img2_y), mask=img2) + + # Debug only + # img2_box = (img2_x, img2_y, img2_x + img2.width, img2_y + img2.height) + # draw.rectangle(img2_box, outline='red', width=2) return canvas_img.convert('RGB') except UnidentifiedImageError: return None