def __text_wrap(self, text: str, font: PIL_ImgFont.FreeTypeFont, max_width: int) -> List[str]: """ Wrap the into multiple lines """ lines = [] # If the width of the text is smaller than image width # we don't need to split it, just add it to the lines array # and return if font.getsize(text)[0] <= max_width: lines.append(text) else: # split the line by spaces to get words words = text.split(' ') i = 0 while i < len(words): line = '' while i < len(words) and font.getsize( line + words[i])[0] <= max_width: line = line + words[i] + " " i += 1 if not line: line = words[i] i += 1 lines.append(line) return lines
def text_wrap(text: str, font: ImageFont.FreeTypeFont, max_width: int) -> str: """ Text wrapper @param text: Text to wrap @type text: str @param font: Font that the text will be displayed with @type font: ImageFont.FreeTypeFont @param max_width: The maximal width of the text @type max_width: int @return: Wrapped text @rtype: str """ lines: list[str] = [] # TODO: implement emoji # If the text width is smaller than the image width, then no need to split # just add it to the line list and return if font.getsize(text)[0] <= max_width: return text # split the line by spaces to get words words: list[str] = text.split(' ') i = 0 # append every word to a line while its width is shorter than the image width while i < len(words): line = '' while i < len(words) and font.getsize(line + words[i])[0] <= max_width: line = line + words[i] + " " i += 1 if not line: line = words[i] i += 1 lines.append(line) return '\n'.join(lines)
def wrap_new(canv: ImageDraw.Draw, box: Tuple[Tuple[int, int], Tuple[int, int]], text: str, *, font: ImageFont.FreeTypeFont): _, h = font.getsize('yA') max_width = box[1][0] - box[0][0] max_height = box[1][1] def write(x: int, y: int, line: List[str]): text_ = ' '.join(line) font_ = auto_font(font, text_, max_width) w, h = font_.getsize(text_) xy = (x + center(w, max_width), y) canv.text(xy, text_, fill='black', font=font_) x, y = box[0] line = [] for word in text.split(): w, _ = font.getsize(' '.join(line + [word])) if w > max_width: write(x, y, line) y += h if y > max_height: return line = [word] else: line.append(word) if line: write(x, y, line)
def draw_text(self, color: Color, size, font: ImageFont.FreeTypeFont): im = Image.new('RGB', size, color.rgb) draw = ImageDraw.Draw(im) name = str(color) text_size = font.getsize(name) text_color = self.text_color(color) if text_size[0] > size[0]: all_lines = [] lines = split_string(name, maxlen=len(name) // (text_size[0] / size[0])) margin = 2 total_y = 0 for line in lines: line = line.strip() if not line: continue if text_size[1] + total_y > size[1]: break x = (size[0] - font.getsize(line)[0]) // 2 all_lines.append((line, x)) total_y += margin + text_size[1] y = (size[1] - total_y) // 2 for line, x in all_lines: draw.text((x, y), line, font=font, fill=text_color) y += margin + text_size[1] else: x = (size[0] - text_size[0]) // 2 y = (size[1] - text_size[1]) // 2 draw.text((x, y), name, font=font, fill=text_color) return im
def renderPick(self, font: ImageFont.FreeTypeFont) -> Image: """ Render Pick 2, or draw 2/pick 3 """ # Figure out the size of the image pickIm = self.renderCircle(self.blanks, font) w, h = pickIm.size pickT = "PICK " pw, ph = font.getsize(pickT) w += pw if self.blanks > 2: h = round(h * 1.75) drawIm = self.renderCircle(self.blanks - 1, font) drawT = "DRAW " dw, dh = font.getsize(drawT) h += dh w = max(w, drawIm.width + dw) else: drawIm = None im = Image.new("RGB", (w, h), (0, 0, 0)) draw = ImageDraw.Draw(im) # Draw the Pick nw, nh = pickIm.size im.paste(pickIm, (w - nw, h - nh)) draw.text((w - nw - pw, h - nh // 2 - ph // 2), pickT, fill=(255, 255, 255), font=font) if drawIm: # Draw the Draw nw, nh = drawIm.size im.paste(drawIm, (w - nw, 0)) draw.text((w - nw - dw, nh // 2 - dh // 2), drawT, fill=(255, 255, 255), font=font) # Close the temporary images pickIm.close() if drawIm: drawIm.close() newIm = im.resize((round(w * 0.75), round(h * 0.75)), resample=Image.LANCZOS) im.close() return newIm
def _get_metrics_map( text: str, font: ImageFont.FreeTypeFont, with_vertical_metrics: bool = True ) -> typing.Union[typing.Dict[str, typing.Tuple[int, int]], typing.Dict[ str, typing.Tuple[int, int, int, int]], ]: return { char: (*size, size[1] - height, size[1]) if with_vertical_metrics and (height := font.getmask(char).size[1]) is not None else size for char in set(text) if (size := font.getsize(char)) }
def renderLine(self, raw: str, image: Image, y: int, height: int, font: ImageFont.FreeTypeFont) -> ImageDraw: """ Render a single line """ # Get the number of blanks blanks = raw.count("_blank_") no_line = raw.replace("_blank_", "") ascent, descent = font.getmetrics() w, h = font.getsize(no_line) (width, baseline), (offset_x, offset_y) = font.font.getsize(no_line) if height < h: line = ascent else: line = height - descent / 2 y = y - height + h ascent, descent = font.getmetrics() draw = ImageDraw.Draw(image) # draw.line((0, y, w, y), fill=(255, 0, 0)) # draw.line((0, y + height, w, y + height), fill=(0, 255, 0)) # draw.line((0, y + h, w, y + h), fill=(0, 0, 255)) if blanks > 0: # Get the size of each blank. blank_size = (image.size[0] - w) / blanks groups = raw.split("_blank_") # Render each group x = 0 for group in groups: # Draw the text draw.text((x, y), group, self.color, font) w, _ = font.getsize(group) x += w if blanks > 0: # Draw the line draw.line( ((x + 5, y + line), (x + blank_size - 5, y + line)), fill=self.color, width=2) x += blank_size blanks -= 1 return draw.text((0, y), raw, self.color, font)
def draw_right_text(text: str, draw: ImageDraw, font: FreeTypeFont, f_width: int, x: int, y: int, color: Tuple[int, int, int], outline_percentage, outline_color, fontsize) -> Tuple[int, int]: text_width = font.getsize(text)[0] off_x = f_width - text_width draw.text((x + off_x, y), text, color, font, stroke_width=round(outline_percentage * 0.01 * fontsize), stroke_fill=outline_color) return font.getsize(text)
def wrap_line_with_font(inputstr: str, max_width: int, font: ImageFont.FreeTypeFont) -> str: if font.getsize(inputstr)[0] <= max_width: return inputstr tokens = inputstr.split(" ") output = "" segments = [] for token in tokens: combo = output + " " + token if font.getsize(combo)[0] > max_width: segments.append(output + "\n") output = token else: output = combo return "".join(segments + [output]).strip()
def renderText(self, font: ImageFont.FreeTypeFont, width: int = 0, maxwidth: int = 0) -> Image: """ Render the text of the card """ lines = list() # split the words that are too long for match in re.finditer(r"(?:\\n|\s*)(.*?(?=\\n)|.{0,20}(?=\s|$))", self.raw): if match[1]: lines.append(match[1]) ws, hs = zip(*[font.getsize(l) for l in lines]) mult = 1.5 if not width: width = max(ws) padding = (maxwidth - width) // 2 h = round(sum(hs) / len(hs)) height = round(h * (len(hs) - 1) * mult + hs[-1]) im = Image.new("RGB", (width, height + padding), self.background) y = padding for l in lines: self.renderLine(l, im, y, h, font) y += round(h * mult) return im
def _get_multiline_text(text, font: PillowImageFont, width: int) -> List[str]: font_width, _ = font.getsize(text) line_length = int((width / (font_width / len(text)))) text_lines = wrap(text, line_length) return text_lines
def draw_text(self, image: PillowImage, text: List[str], font: PillowImageFont): draw = ImageDraw.Draw(image) for line_index, line in enumerate(text): font_width, _ = font.getsize(line) _, height_offset = font.getoffset(line) x = self._get_x(font_width) y = self._get_y(line_index, self.line_height, height_offset) for c in line: if not is_emoji(c) and c.isprintable(): draw.text((x, y), c, font=font, fill=self.font_color) x += math.ceil(draw.textsize(c, font)[0]) elif self.get_emoji_content: emoji_content = self.get_emoji_content(c) if emoji_content: with tempfile.NamedTemporaryFile(mode='wb') as f: temp_filename = f.name f.write(emoji_content) emoji_img = Image.open(temp_filename) resized_emoji_img = emoji_img.resize( (font.size, font.size)) image.paste(resized_emoji_img, (x, y), resized_emoji_img) x += math.ceil(resized_emoji_img.width) return image
def _get_character_pixel_width(font: ImageFont.FreeTypeFont, char: str) -> int: """Use getlength over using getsize for character pixel width, if available in PIL.""" try: write_pos = int(font.getlength(char)) except AttributeError: write_pos = font.getsize(char)[0] return write_pos
def center_text(text: str, x_pos: int, y_pos: int, font: ImageFont.FreeTypeFont) -> Tuple[int, int]: text_x, text_y = font.getsize(text) x = (x_pos - text_x//2) # / 2 y = (y_pos - text_y//2) # / 2 return x, y
def get_final_text_lines(text: str, text_width: int, font: ImageFont.FreeTypeFont) -> int: lines = text.split("\n") line_count = 0 for line in lines: if not line: line_count += 1 continue line_count += int(math.ceil(float(font.getsize(line)[0]) / float(text_width))) return line_count + 1
def draw_text(self, image: PillowImage, text: List[str], font: PillowImageFont): draw = ImageDraw.Draw(image) for line_index, line in enumerate(text): font_width, _ = font.getsize(line) _, height_offset = font.getoffset(line) x = self._get_x(font_width) y = self._get_y(line_index, self.line_height, height_offset) draw.text((x, y), line, font=font, fill=self.font_color) return image
def wrap_text(text: str, font: FreeTypeFont, max_width: float) -> List[str]: line = "" lines = [] for t in text: if t == "\n": lines.append(line) line = "" elif font.getsize(line + t)[0] > max_width: lines.append(line) line = t else: line += t lines.append(line) return lines
def does_label_needs_to_be_wrapped(font: ImageFont.FreeTypeFont, label_text: str, label_width: int) -> Tuple[bool, int]: """ :param font: :param label_text: single-line text to be checked if it needs to wrapped :param label_width: :return: True if text needs to be wrapped else False """ w, h = font.getsize(label_text) per_char_pixels = w / len(label_text) can_fit_n_chars_in_single_line = label_width // per_char_pixels return (len(label_text) > can_fit_n_chars_in_single_line, can_fit_n_chars_in_single_line)
def wrap_text(text: str, font: FreeTypeFont, max_width: float, stroke_width: int = 0) -> List[str]: line = '' lines = [] for t in text: if t == '\n': lines.append(line) line = '' elif font.getsize(line + t, stroke_width=stroke_width)[0] > max_width: lines.append(line) line = t else: line += t lines.append(line) return lines
def renderCircle(self, txt: str, font: ImageFont.FreeTypeFont) -> Image: """ Render text inside a circle """ txt = str(txt) # Get the size of the circle w, h = font.getsize(txt) diam = round(math.sqrt(w**2 + h**2) * 1.25) im = Image.new("RGB", (diam, diam), (0, 0, 0)) draw = ImageDraw.Draw(im) draw.ellipse((0, 0, diam, diam), fill=(255, 255, 255)) draw.text((diam // 2 - w // 2, diam // 2 - h // 2), txt, fill=(0, 0, 0), font=font) return im
def wrap_text(text: str, width: int, font: ImageFont.FreeTypeFont): """Wrap text to new lines to meet a given width""" text_lines: List[str] = [] text_line: List[str] = [] text = text.replace("\n", " [br] ") words = text.split() for word in words: if word == "[br]": text_lines.append(" ".join(text_line)) text_line = [] continue text_line.append(word) text_width, _ = font.getsize(" ".join(text_line)) if text_width > width: text_line.pop() text_lines.append(" ".join(text_line)) text_line = [word] if text_line: text_lines.append(" ".join(text_line)) return text_lines
def getsize(font: FreeTypeFont, text_str: str) -> Tuple[int, int]: """get the size of text""" if USE_PIL: # original behavior # size = font.getsize(text_str) # new behavior width, _ = font.getsize(text_str) ascent, descent = font.getmetrics() height = ascent + descent size = (width, height) else: info = text_scala.get_info(text_str, _font_to_tuple(font), 0, BORDER_DEFAULT) # Important note! Info height and width may not include some antialiasing pixels! # import numpy as np # column_sums = np.sum(img, axis=0) # present = np.where(column_sums > 0)[0] # calculated_width = (present[-1] - present[0]) + 1 # print("metrics width:", info["width"]) # print("calculated width:", calculated_width) size = (info["width"], info["height"]) return size
def wrap_text(text: str, width: int, font: FreeTypeFont) -> Tuple[str, int, int]: text_lines = [] text_line = [] words = text.split() line_height = 0 line_width = 0 for word in words: text_line.append(word) w, h = font.getsize(' '.join(text_line)) line_height = h line_width = max(line_width, w) if w > width: text_line.pop() text_lines.append(' '.join(text_line)) text_line = [word] if len(text_line) > 0: text_lines.append(' '.join(text_line)) text_height = line_height * len(text_lines) return "\n".join(text_lines), line_width, text_height
def get_font_size(font: ImageFont.FreeTypeFont, text: str) -> (int, int): return font.getsize(text)
def draw_bounding_box_on_image( image: Image.Image, ymin: int, xmin: int, ymax: int, xmax: int, color: Tuple[int], font: FreeTypeFont, thickness: int = 4, display_str_list: Iterable[Text] = (), ): """ Adds a bounding box to an image. Adapted from: https://colab.research.google.com/github/tensorflow/hub/blob/master/examples/colab/object_detection.ipynb """ draw = ImageDraw.Draw(image) # type: ImageDraw.ImageDraw im_width, im_height = image.size (left, right, top, bottom) = ( xmin * im_width, xmax * im_width, ymin * im_height, ymax * im_height, ) draw.line( [(left, top), (left, bottom), (right, bottom), (right, top), (left, top)], width=thickness, fill=color, ) # If the total height of the display strings added to the top of the bounding # box exceeds the top of the image, stack the strings below the bounding box # instead of above. display_str_heights = [font.getsize(ds)[1] for ds in display_str_list] # Each display_str has a top and bottom margin of 0.05x. total_display_str_height = (1 + 2 * 0.05) * sum(display_str_heights) if top > total_display_str_height: text_bottom = top else: text_bottom = bottom + total_display_str_height # Reverse list and print from bottom to top. for display_str in display_str_list[::-1]: text_width, text_height = font.getsize(display_str) margin = np.ceil(0.05 * text_height) draw.rectangle( [ (left, text_bottom - text_height - 2 * margin), (left + text_width, text_bottom), ], fill=color, ) draw.text( (left + margin, text_bottom - text_height - margin), display_str, fill="black", font=font, ) text_bottom -= text_height - 2 * margin
def get_char_box(self, font: ImageFont.FreeTypeFont) -> tuple: """ Com base no tamanho da fonte (que é do tipo `monospace`), calcula a largura e altura em pixels de um caractere.""" return font.getsize("H")