# --- BEGIN TEXT FUNCTIONS # # # import PIL.Image import PIL.ImageDraw import PIL.ImageFont from arcade.sprite import Sprite from arcade.arcade_types import Color from arcade.draw_commands import Texture class Text: """ Class used for managing text. """ def __init__(self): self.size = (0, 0) self.text_sprite_list = None class CreateText: """ Class used for managing text """ def __init__(self, text: str, color: Color, font_size: float = 12, width: int = 20, align="left", font_name=('Calibri', 'Arial'), bold: bool = False, italic: bool = False, anchor_x="left", anchor_y="baseline", rotation=0): self.text = text self.color = color self.font_size = font_size self.width = width self.align = align self.font_name = font_name self.bold = bold self.italic = italic self.anchor_x = anchor_x self.anchor_y = anchor_y self.rotation = rotation def create_text(text: str, color: Color, font_size: float = 12, width: int = 0, align="left", font_name=('Calibri', 'Arial'), bold: bool = False, italic: bool = False, anchor_x: str = "left", anchor_y: str = "baseline", rotation=0): """ Deprecated. Two step text drawing for backwards compatibility. """ import warnings warnings.warn("create_text has been deprecated, please use draw_text instead.", DeprecationWarning) my_text = CreateText(text, color, font_size, width, align, font_name, bold, italic, anchor_x, anchor_y, rotation) return my_text def render_text(text: CreateText, start_x: float, start_y: float): """ Deprecated. Two step text drawing for backwards compatibility. """ import warnings warnings.warn("render_text has been deprecated, please use draw_text instead.", DeprecationWarning) draw_text(text.text, start_x, start_y, color=text.color, font_size=text.font_size, width=text.width, align=text.align, font_name=text.font_name, bold=text.bold, italic=text.italic, anchor_x=text.anchor_x, anchor_y=text.anchor_y, rotation=text.rotation) def draw_text(text: str, start_x: float, start_y: float, color: Color, font_size: float = 12, width: int = 0, align: str = "left", font_name=('calibri', 'arial'), bold: bool = False, italic: bool = False, anchor_x: str = "left", anchor_y: str = "baseline", rotation: float = 0 ): """ :param str text: Text to draw :param float start_x: :param float start_y: :param Color color: Color of the text :param float font_size: Size of the text :param float width: :param str align: :param str font_name: :param bool bold: :param bool italic: :param str anchor_x: :param str anchor_y: :param float rotation: """ # Scale the font up, so it matches with the sizes of the old code back # when Pyglet drew the text. font_size *= 1.25 # Text isn't anti-aliased, so we'll draw big, and then shrink scale_up = 1 scale_down = 1 # font_size *= scale_up # If the cache gets too large, dump it and start over. if len(draw_text.cache) > 5000: draw_text.cache = {} key = f"{text}{color}{font_size}{width}{align}{font_name}{bold}{italic}" if key in draw_text.cache: label = draw_text.cache[key] text_sprite = label.text_sprite_list[0] if anchor_x == "left": text_sprite.center_x = start_x + text_sprite.width / 2 elif anchor_x == "center": text_sprite.center_x = start_x elif anchor_x == "right": text_sprite.right = start_x else: raise ValueError(f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'") if anchor_y == "top": text_sprite.center_y = start_y - text_sprite.height / 2 elif anchor_y == "center": text_sprite.center_y = start_y elif anchor_y == "bottom" or anchor_y == "baseline": text_sprite.bottom = start_y else: raise ValueError(f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_y}'") text_sprite.angle = rotation else: label = Text() # Figure out the font to use font = None # Font was specified with a string if isinstance(font_name, str): try: font = PIL.ImageFont.truetype(font_name, int(font_size)) except OSError: # print(f"1 Can't find font: {font_name}") pass if font is None: try: temp_font_name = f"{font_name}.ttf" font = PIL.ImageFont.truetype(temp_font_name, int(font_size)) except OSError: # print(f"2 Can't find font: {temp_font_name}") pass # We were instead given a list of font names, in order of preference else: for font_string_name in font_name: try: font = PIL.ImageFont.truetype(font_string_name, int(font_size)) # print(f"3 Found font: {font_string_name}") except OSError: # print(f"3 Can't find font: {font_string_name}") pass if font is None: try: temp_font_name = f"{font_name}.ttf" font = PIL.ImageFont.truetype(temp_font_name, int(font_size)) except OSError: # print(f"4 Can't find font: {temp_font_name}") pass if font is not None: break # Default font if no font if font is None: font_names = ("arial.ttf", 'NotoSans-Regular.ttf', "/usr/share/fonts/truetype/freefont/FreeMono.ttf", '/System/Library/Fonts/SFNSDisplay.ttf') for font_string_name in font_names: try: font = PIL.ImageFont.truetype(font_string_name, int(font_size)) break except OSError: # print(f"5 Can't find font: {font_string_name}") pass # This is stupid. We have to have an image to figure out what size # the text will be when we draw it. Of course, we don't know how big # to make the image. Catch-22. So we just make a small image we'll trash text_image_size = (10, 10) image = PIL.Image.new("RGBA", text_image_size) draw = PIL.ImageDraw.Draw(image) # Get size the text will be text_image_size = draw.multiline_textsize(text, font=font) # Create image of proper size text_height = text_image_size[1] text_width = text_image_size[0] image_start_x = 0 if width == 0: width = text_image_size[0] else: # Wait! We were given a field width. if align == "center": # Center text on given field width field_width = width * scale_up text_image_size = field_width, text_height image_start_x = (field_width - text_width) // 2 width = field_width else: image_start_x = 0 # If we draw a y at 0, then the text is drawn with a baseline of 0, # cutting off letters that drop below the baseline. This shoves it # up a bit. image_start_y = - font_size * scale_up * 0.02 image = PIL.Image.new("RGBA", text_image_size) draw = PIL.ImageDraw.Draw(image) # Convert to tuple if needed, because the multiline_text does not take a # list for a color if isinstance(color, list): color = tuple(color) draw.multiline_text((image_start_x, image_start_y), text, color, align=align, font=font) # image = image.resize((width // scale_down, text_height // scale_down), resample=PIL.Image.LANCZOS) text_sprite = Sprite() text_sprite._texture = Texture(key) text_sprite._texture.image = image text_sprite.image = image text_sprite.texture_name = key text_sprite.width = image.width text_sprite.height = image.height if anchor_x == "left": text_sprite.center_x = start_x + text_sprite.width / 2 elif anchor_x == "center": text_sprite.center_x = start_x elif anchor_x == "right": text_sprite.right = start_x else: raise ValueError(f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'") if anchor_y == "top": text_sprite.center_y = start_y + text_sprite.height / 2 elif anchor_y == "center": text_sprite.center_y = start_y elif anchor_y == "bottom" or anchor_y == "baseline": text_sprite.bottom = start_y else: raise ValueError(f"anchor_x should be 'top', 'center', 'bottom', or 'baseline'. Not '{anchor_y}'") text_sprite.angle = rotation from arcade.sprite_list import SpriteList label.text_sprite_list = SpriteList() label.text_sprite_list.append(text_sprite) draw_text.cache[key] = label label.text_sprite_list.draw() draw_text.cache = {}