def draw_clean_text(draw: ImageDraw, text: str, lang: str, box: Box, fill: str = 'black', spacing: int = 0, pad: int = 0) -> None: '''Place the text at the center of the bounding box. Input: draw: ImageDraw instance text: Content to add lang: Language box: Bounding box fill: Color of text spacing: Vertical spacing between lines pad: Inner padding to add to the bounding box ''' box.pad(p=pad) W, H = (box.R - box.L), (box.B - box.T) font_path = f'data/fonts/indic/{lang}.ttf' F = ImageFont.truetype(font=font_path, size=1) size = 1 w, h = draw.multiline_textsize(text, font=F, spacing=spacing) while w < W and h < H: size += 1 F = ImageFont.truetype(font=font_path, size=size) w, h = draw.multiline_textsize(text, font=F, spacing=spacing) # TODO: fix, uncomment this if you want a static size & not dynamic size for texts # size = 30 if size < 70 else 100 F = ImageFont.truetype(font=font_path, size=size) w, h = F.getsize_multiline(text) cx = (W - w) / 2 + box.L cy = (H - h) / 2 + box.T - 10 draw.multiline_text((cx, cy), text, fill=fill, font=F, align='center', spacing=spacing)
def draw_text_on_image( draw: ImageDraw, text: str, font: ImageFont, height: int, width: int, heigh_offset: int, width_offset: int, ): """Draw given text on the given image. Args: draw (ImageDraw): The ImageDraw object using which text will be drawn text (str): Text to draw on the image font (ImageFont): Font to be used height (int): Height of the image width (int): Width of the image heigh_offset (int): Height offset from the centre width_offset (int): Width offset from the centre """ w, h = draw.multiline_textsize(text, font, spacing=10) left = width_offset + ((width - w) * 0.5) top = heigh_offset + ((height - h) * 0.5) draw.text( (left, top), text, random.choice(COLOR_OPTIONS), font=font, spacing=10, stroke_width=3, stroke_fill="black", )
def centeredText(draw: ImageDraw, font: ImageFont, x: int, y: int, text: str, color: str): textSize = draw.multiline_textsize(text, font) draw.multiline_text((x - textSize[0] / 2, y - textSize[1] / 2), text, color, font, align="center")
def try_better_aspect_ratio(img: ImageDraw, original_text: str, modified_text: str): ''' If the message text is too long, wrapping it in 25 character lines will result in an image with a big height and small width, making it difficult to read when resized to Telegram's limit of 512px. So if the wrapped text has a height more than two times its width, we increase the line width limit and re-wrap it until we get an aspect ratio closer to 1:1. ''' line_width = LINE_WIDTH_LIMIT text_size = img.multiline_textsize(modified_text, font=FONTS['normal']) for _ in range(3): if text_size[1] > 2 * text_size[0]: line_width *= 2 modified_text = wrapped_text(original_text, line_width=line_width, max_lines=MAX_NUMBER_OF_LINES) text_size = img.multiline_textsize(modified_text, font=FONTS['normal']) else: break return modified_text, text_size
def process_message( drawing: ImageDraw, message_text: str, font: ImageFont, line_spacing: int, max_width: int, max_lines: int): message_text = message_text.replace('\r\n', ' ') message_text = message_text.replace('\n', ' ') message_text = message_text.replace(' ', ' ') number_of_lines = 1 lw, lh = drawing.multiline_textsize(text=message_text, font=font, spacing=line_spacing) if lw > max_width: message_words = message_text.split() message_text = '' has_more_text = True while has_more_text: for i in range(0 , len(message_words)): line = ' '.join(message_words[0:i + 1]) lw, lh = drawing.multiline_textsize(text=line, font=font, spacing=line_spacing) if lw > max_width: line = ' '.join(message_words[:i - 1]) message_text += f'{line}\n' number_of_lines += 1 if number_of_lines > max_lines: has_more_text = False message_text = f'{message_text[:len(message_text) - 3]}...' break message_words = message_words[i - 1:] line = ' '.join(message_words) lw, lh = drawing.multiline_textsize(text=line, font=font, spacing=line_spacing) if lw <= max_width: has_more_text = False message_text += f'{line}' break break lw, lh = drawing.multiline_textsize(text=message_text, font=font, spacing=line_spacing) number_of_lines = message_text.count('\n') + 1 return message_text, number_of_lines, lw, lh
def textException(txtDrawer: ImageDraw, txt: str, txtFieldWidth: int, targetFontSize: int, canLiningChange: bool, pathToFont, howMuchCanFontChange: int = None) -> [str, int]: fontSize = targetFontSize _font = ImageFont.truetype(pathToFont, fontSize) txtWidth = txtDrawer.multiline_textsize(txt, font=_font)[0] if howMuchCanFontChange: try: fontSize = tryToMatchTextWidthByFontSize(txt, targetFontSize, txtFieldWidth, howMuchCanFontChange, pathToFont=pathToFont) return txt, fontSize except: pass wordsCount = len(textPreparation(txt).split()) if canLiningChange and wordsCount > 1: temp = minimizingTextWidthByLiningChange(txt, targetFontSize, txtFieldWidth, wordsCount, pathToFont=pathToFont) txt = temp[0] if temp[-1]: return txt, fontSize if howMuchCanFontChange and canLiningChange: try: fontSize = tryToMatchTextWidthByFontSize(txt, targetFontSize, txtFieldWidth, howMuchCanFontChange, pathToFont=pathToFont) return txt, fontSize except: pass if txtWidth > txtFieldWidth: raise ValueError("Слишком длинный текст")
def render_list(self, result: Image, draw: ImageDraw, draw_property: Mapping, bounding_box: Tuple[Tuple[int, int], Tuple[int, int]], current_x: int, current_y: int, backwards: bool) -> Tuple[int, int]: theme_font = None if "text" in draw_property["type"]: theme_font = ImageFont.truetype(draw_property["font"], draw_property["font_size"]) items = self.properties.get(draw_property["property"]) current_x, current_y = self.resolve_position(draw_property["position"], bounding_box, current_x, current_y) size_x, size_y = draw_property["size"] if size_x == "auto": size_x = bounding_box[1][0] - current_x + bounding_box[0][0] if backwards: size_x = current_x - bounding_box[0][0] if size_y == "auto": size_y = bounding_box[1][1] - current_y + bounding_box[0][1] print(current_x, size_x, backwards) if backwards: current_x = current_x - size_x orientation = draw_property["orientation"] internal_current_y = current_y internal_current_x = current_x if items is not None: cached_current_x = current_x cached_current_y = current_y spacing = draw_property["spacing"] x_border = current_x + size_x y_border = current_y + size_y rows = draw_property["rows"] columns = draw_property["columns"] first = True # CodeReview: Specify an order that all will conform to for item in items: resource_name = f"resources/icons/{item}.png" if "icon" in draw_property["type"] and os.path.isfile(resource_name): icon = Image.open(resource_name) icon_width, icon_height = icon.size icon = icon.resize((self.format["icon_height"] * icon_width // icon_height, self.format["icon_height"]), Image.HAMMING) icon_width, icon_height = icon.size x_coord = internal_current_x y_coord = internal_current_y if orientation == "horizontal": if not first: x_coord += spacing first = False if draw_property["centered_height"]: y_coord += (size_y // (rows or 1) - icon_height) // 2 if orientation == "vertical": if not first: y_coord += spacing first = False if draw_property["centered_width"]: x_coord += (size_x // (columns or 1) - icon_width) // 2 if orientation == "horizontal" and x_coord + icon_width > x_border \ and rows is not None and internal_current_y < current_y + (rows - 1) * (size_y // rows): internal_current_y += size_y // rows internal_current_x = cached_current_x x_coord = internal_current_x y_coord += size_y // rows elif orientation == "vertical" and y_coord + icon_height > y_border \ and columns is not None and \ internal_current_x < current_x + (columns - 1) * (size_y // columns): internal_current_x += size_x // rows internal_current_y = cached_current_y result.paste(icon, (x_coord, y_coord)) if orientation == "horizontal": internal_current_x = x_coord + icon_width elif orientation == "vertical": internal_current_y = y_coord + icon_height elif "text" in draw_property["type"]: text_width, text_height = draw.textsize(item, theme_font) if draw_property["wrap"] is not None: item = textwrap.fill(item, draw_property["wrap"]) text_width, text_height = draw.multiline_textsize(item, theme_font) x_coord = internal_current_x y_coord = internal_current_y text_color = self.properties.get(draw_property["text_color_property"]) or \ draw_property["text_color_default"] if draw_property["bulleted"]: draw.ellipse((internal_current_x + spacing, internal_current_y + (text_height - draw_property["font_size"]) // 2, internal_current_x + spacing + draw_property["font_size"], internal_current_y + (text_height + draw_property["spacing"]) // 2), fill=text_color) x_coord += spacing*2 + draw_property["font_size"] if orientation == "horizontal": if not first: x_coord += spacing first = False if draw_property["centered_height"]: y_coord += (size_y // (rows or 1) - text_height) // 2 if orientation == "vertical": if not first: y_coord += spacing first = False if draw_property["centered_width"]: x_coord += (size_x // (columns or 1) - text_width) // 2 print(x_coord, text_width, x_border, cached_current_x, internal_current_y) if orientation == "horizontal" and x_coord + text_width > x_border \ and rows is not None and internal_current_y < current_y + (rows - 1) * (size_y // rows): internal_current_y += size_y // rows internal_current_x = cached_current_x x_coord = internal_current_x y_coord += size_y // rows elif orientation == "vertical" and y_coord + text_height > y_border \ and columns is not None and \ internal_current_x < current_x + (columns - 1) * (size_y // columns): internal_current_x += size_x // rows internal_current_y = cached_current_y if draw_property["wrap"] is None: draw.text((x_coord, y_coord), item, fill=text_color, font=theme_font) else: draw.multiline_text((x_coord, y_coord), item, fill=text_color, font=theme_font) if orientation == "horizontal": internal_current_x = x_coord + text_width elif orientation == "vertical": internal_current_y = y_coord + text_height else: raise Exception(f"Could not find handler for {item} for {draw_property['type']}") if not backwards: current_x += size_x if orientation == "vertical": current_y += size_y else: if draw_property["required"]: raise Exception(f"Missing required property: {draw_property['property']} from card {self.properties}") return current_x, current_y