mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 03:30:13 +01:00
Update utils.py
This commit is contained in:
399
libs/utils.py
399
libs/utils.py
@@ -144,232 +144,269 @@ class OutlinedText:
|
||||
})
|
||||
|
||||
def __post_init__(self):
|
||||
# Cache for rotated characters
|
||||
self._rotation_cache = {}
|
||||
# Cache for character measurements
|
||||
self._char_size_cache = {}
|
||||
self.texture = self._create_texture()
|
||||
|
||||
def _get_char_size(self, char):
|
||||
"""Cache character size measurements"""
|
||||
if char not in self._char_size_cache:
|
||||
if char in self.vertical_chars:
|
||||
# For vertical chars, width and height are swapped
|
||||
self._char_size_cache[char] = ray.Vector2(self.font_size, self.font_size)
|
||||
else:
|
||||
self._char_size_cache[char] = ray.measure_text_ex(self.font, char, self.font_size, 1.0)
|
||||
return self._char_size_cache[char]
|
||||
|
||||
def _calculate_vertical_spacing(self, current_char, next_char=None):
|
||||
# Check if current char is lowercase or whitespace
|
||||
is_spacing_char = current_char.islower() or current_char.isspace() or current_char in self.no_space_chars
|
||||
"""Calculate vertical spacing between characters"""
|
||||
# Check if current char is lowercase, whitespace or a special character
|
||||
is_spacing_char = (current_char.islower() or
|
||||
current_char.isspace() or
|
||||
current_char in self.no_space_chars)
|
||||
|
||||
# Additional check for capitalization transition
|
||||
if next_char and current_char.isupper() and next_char.islower() or next_char in self.no_space_chars:
|
||||
if next_char and ((current_char.isupper() and next_char.islower()) or
|
||||
next_char in self.no_space_chars):
|
||||
is_spacing_char = True
|
||||
|
||||
# Apply spacing factor if it's a spacing character
|
||||
if is_spacing_char:
|
||||
return self.font_size * (self.line_spacing * self.lowercase_spacing_factor)
|
||||
return self.font_size * self.line_spacing
|
||||
spacing = self.line_spacing * (self.lowercase_spacing_factor if is_spacing_char else 1.0)
|
||||
return self.font_size * spacing
|
||||
|
||||
def _draw_rotated_char(self, image, font, char, pos, font_size, color, is_outline=False):
|
||||
# Calculate character size
|
||||
char_size = ray.measure_text_ex(font, char, font_size, 1.0)
|
||||
def _get_rotated_char(self, char, color):
|
||||
"""Get or create a rotated character texture from cache"""
|
||||
cache_key = (char, color[0], color[1], color[2], color[3])
|
||||
|
||||
# Create a temporary image for the rotated character
|
||||
temp_image = ray.gen_image_color(int(char_size.y), int(char_size.x), ray.Color(0, 0, 0, 0))
|
||||
if cache_key in self._rotation_cache:
|
||||
return self._rotation_cache[cache_key]
|
||||
|
||||
# Draw the character on the temporary image
|
||||
char_size = self._get_char_size(char)
|
||||
|
||||
# For rotated text, we need extra padding to prevent cutoff
|
||||
padding = max(int(self.font_size * 0.2), 2) # Add padding proportional to font size
|
||||
temp_width = int(char_size.y) + padding * 2
|
||||
temp_height = int(char_size.x) + padding * 2
|
||||
|
||||
# Create a temporary image with padding to ensure characters aren't cut off
|
||||
temp_image = ray.gen_image_color(temp_width, temp_height, ray.Color(0, 0, 0, 0))
|
||||
|
||||
# Calculate centering offsets
|
||||
x_offset = padding
|
||||
y_offset = padding
|
||||
|
||||
# Draw the character centered in the temporary image
|
||||
ray.image_draw_text_ex(
|
||||
temp_image,
|
||||
font,
|
||||
self.font,
|
||||
char,
|
||||
ray.Vector2(0, 0),
|
||||
font_size,
|
||||
ray.Vector2(x_offset-5, y_offset),
|
||||
self.font_size,
|
||||
1.0,
|
||||
color
|
||||
)
|
||||
|
||||
# Rotate the temporary image 90 degrees
|
||||
rotated_image = ray.gen_image_color(int(char_size.x), int(char_size.y), ray.Color(0, 0, 0, 0))
|
||||
for x in range(int(char_size.y)):
|
||||
for y in range(int(char_size.x)):
|
||||
pixel = ray.get_image_color(temp_image, y, int(char_size.y) - x - 1)
|
||||
ray.image_draw_pixel(
|
||||
rotated_image,
|
||||
x,
|
||||
y,
|
||||
pixel
|
||||
)
|
||||
# Rotate the temporary image 90 degrees counterclockwise
|
||||
rotated_image = ray.gen_image_color(temp_height, temp_width, ray.Color(0, 0, 0, 0))
|
||||
for x in range(temp_width):
|
||||
for y in range(temp_height):
|
||||
pixel = ray.get_image_color(temp_image, x, temp_height - y - 1)
|
||||
ray.image_draw_pixel(rotated_image, y, x, pixel)
|
||||
|
||||
# Unload temporary image
|
||||
ray.unload_image(temp_image)
|
||||
|
||||
# Draw the rotated image
|
||||
ray.image_draw(
|
||||
image,
|
||||
rotated_image,
|
||||
ray.Rectangle(0, 0, rotated_image.width, rotated_image.height),
|
||||
ray.Rectangle(int(pos.x), int(pos.y), rotated_image.width, rotated_image.height),
|
||||
ray.WHITE
|
||||
)
|
||||
# Cache the rotated image
|
||||
self._rotation_cache[cache_key] = rotated_image
|
||||
return rotated_image
|
||||
|
||||
# Unload rotated image
|
||||
ray.unload_image(rotated_image)
|
||||
|
||||
def _create_texture(self):
|
||||
# Measure text size
|
||||
text_size = ray.measure_text_ex(self.font, self.text, self.font_size, 1.0)
|
||||
|
||||
# Determine dimensions based on orientation
|
||||
def _calculate_dimensions(self):
|
||||
"""Calculate dimensions based on orientation"""
|
||||
if not self.vertical:
|
||||
width = int(text_size.x + self.outline_thickness * 4)
|
||||
height = int(text_size.y + self.outline_thickness * 4)
|
||||
padding_x, padding_y = self.outline_thickness * 2, self.outline_thickness * 2
|
||||
else:
|
||||
# For vertical text, calculate total height and max character width
|
||||
char_heights = [
|
||||
self._calculate_vertical_spacing(
|
||||
self.text[i],
|
||||
self.text[i+1] if i+1 < len(self.text) else None
|
||||
)
|
||||
for i in range(len(self.text))
|
||||
]
|
||||
# Horizontal text
|
||||
text_size = ray.measure_text_ex(self.font, self.text, self.font_size, 1.0)
|
||||
|
||||
# Calculate the maximum character width (including outline)
|
||||
# Add extra padding to prevent cutoff
|
||||
extra_padding = max(int(self.font_size * 0.15), 2)
|
||||
width = int(text_size.x + self.outline_thickness * 4 + extra_padding * 2)
|
||||
height = int(text_size.y + self.outline_thickness * 4 + extra_padding * 2)
|
||||
padding_x = self.outline_thickness * 2 + extra_padding
|
||||
padding_y = self.outline_thickness * 2 + extra_padding
|
||||
|
||||
return width, height, padding_x, padding_y
|
||||
else:
|
||||
# For vertical text, pre-calculate all character heights and widths
|
||||
char_heights = []
|
||||
char_widths = []
|
||||
for char in self.text:
|
||||
|
||||
for i, char in enumerate(self.text):
|
||||
next_char = self.text[i+1] if i+1 < len(self.text) else None
|
||||
char_heights.append(self._calculate_vertical_spacing(char, next_char))
|
||||
|
||||
# For vertical characters, consider rotated dimensions
|
||||
if char in self.vertical_chars:
|
||||
# For vertically drawn characters, use font size as width
|
||||
char_width = self.font_size
|
||||
# Use padded width for rotated characters
|
||||
padding = max(int(self.font_size * 0.2), 2) * 2
|
||||
char_widths.append(self._get_char_size(char).x + padding)
|
||||
else:
|
||||
# Normal character width
|
||||
char_width = ray.measure_text_ex(self.font, char, self.font_size, 1.0).x
|
||||
char_widths.append(char_width)
|
||||
char_widths.append(self._get_char_size(char).x)
|
||||
|
||||
max_char_width = max(char_widths) if char_widths else 0
|
||||
total_height = sum(char_heights) if char_heights else 0
|
||||
|
||||
# Adjust dimensions to be tighter around the text
|
||||
width = int(max_char_width + self.outline_thickness * 2) # Reduced padding
|
||||
height = int(total_height + self.outline_thickness * 2) # Reduced padding
|
||||
padding_x = self.outline_thickness
|
||||
padding_y = self.outline_thickness
|
||||
# Add extra padding for vertical text
|
||||
extra_padding = max(int(self.font_size * 0.15), 2)
|
||||
width = int(max_char_width + self.outline_thickness * 4 + extra_padding * 2)
|
||||
height = int(total_height + self.outline_thickness * 4 + extra_padding * 2)
|
||||
padding_x = self.outline_thickness * 2 + extra_padding
|
||||
padding_y = self.outline_thickness * 2 + extra_padding
|
||||
|
||||
return width, height, padding_x, padding_y
|
||||
|
||||
def _draw_horizontal_text(self, image, padding_x, padding_y):
|
||||
"""Draw horizontal text with outline"""
|
||||
# Draw outline
|
||||
for dx in range(-self.outline_thickness, self.outline_thickness + 1):
|
||||
for dy in range(-self.outline_thickness, self.outline_thickness + 1):
|
||||
if dx == 0 and dy == 0:
|
||||
continue
|
||||
ray.image_draw_text_ex(
|
||||
image,
|
||||
self.font,
|
||||
self.text,
|
||||
ray.Vector2(padding_x + dx, padding_y + dy),
|
||||
self.font_size,
|
||||
1.0,
|
||||
self.outline_color
|
||||
)
|
||||
|
||||
# Draw main text
|
||||
ray.image_draw_text_ex(
|
||||
image,
|
||||
self.font,
|
||||
self.text,
|
||||
ray.Vector2(padding_x, padding_y),
|
||||
self.font_size,
|
||||
1.0,
|
||||
self.text_color
|
||||
)
|
||||
|
||||
def _draw_vertical_text(self, image, width, padding_x, padding_y):
|
||||
"""Draw vertical text with outline"""
|
||||
# Precalculate positions and spacings to avoid redundant calculations
|
||||
positions = []
|
||||
current_y = padding_y
|
||||
|
||||
for i, char in enumerate(self.text):
|
||||
char_size = self._get_char_size(char)
|
||||
char_height = self._calculate_vertical_spacing(
|
||||
char,
|
||||
self.text[i+1] if i+1 < len(self.text) else None
|
||||
)
|
||||
|
||||
# Calculate center position for each character
|
||||
if char in self.vertical_chars:
|
||||
# For vertical characters, we need to use the rotated image dimensions
|
||||
rotated_img = self._get_rotated_char(char, self.text_color)
|
||||
char_width = rotated_img.width
|
||||
center_offset = (width - char_width) // 2
|
||||
else:
|
||||
char_width = char_size.x
|
||||
center_offset = (width - char_width) // 2
|
||||
|
||||
positions.append((char, center_offset, current_y, char_height, char in self.vertical_chars))
|
||||
current_y += char_height
|
||||
|
||||
# First draw all outlines
|
||||
for dx in range(-self.outline_thickness, self.outline_thickness + 1):
|
||||
for dy in range(-self.outline_thickness, self.outline_thickness + 1):
|
||||
if dx == 0 and dy == 0:
|
||||
continue
|
||||
|
||||
for char, center_offset, y_pos, _, is_vertical in positions:
|
||||
if is_vertical:
|
||||
rotated_img = self._get_rotated_char(char, self.outline_color)
|
||||
ray.image_draw(
|
||||
image,
|
||||
rotated_img,
|
||||
ray.Rectangle(0, 0, rotated_img.width, rotated_img.height),
|
||||
ray.Rectangle(
|
||||
int(center_offset + dx),
|
||||
int(y_pos + dy),
|
||||
rotated_img.width,
|
||||
rotated_img.height
|
||||
),
|
||||
ray.WHITE
|
||||
)
|
||||
else:
|
||||
ray.image_draw_text_ex(
|
||||
image,
|
||||
self.font,
|
||||
char,
|
||||
ray.Vector2(center_offset + dx, y_pos + dy),
|
||||
self.font_size,
|
||||
1.0,
|
||||
self.outline_color
|
||||
)
|
||||
|
||||
# Then draw all main text
|
||||
for char, center_offset, y_pos, _, is_vertical in positions:
|
||||
if is_vertical:
|
||||
rotated_img = self._get_rotated_char(char, self.text_color)
|
||||
ray.image_draw(
|
||||
image,
|
||||
rotated_img,
|
||||
ray.Rectangle(0, 0, rotated_img.width, rotated_img.height),
|
||||
ray.Rectangle(
|
||||
int(center_offset),
|
||||
int(y_pos),
|
||||
rotated_img.width,
|
||||
rotated_img.height
|
||||
),
|
||||
ray.WHITE
|
||||
)
|
||||
else:
|
||||
ray.image_draw_text_ex(
|
||||
image,
|
||||
self.font,
|
||||
char,
|
||||
ray.Vector2(center_offset, y_pos),
|
||||
self.font_size,
|
||||
1.0,
|
||||
self.text_color
|
||||
)
|
||||
|
||||
def _create_texture(self):
|
||||
"""Create a texture with outlined text"""
|
||||
# Calculate dimensions
|
||||
width, height, padding_x, padding_y = self._calculate_dimensions()
|
||||
|
||||
# Create transparent image
|
||||
image = ray.gen_image_color(width, height, ray.Color(0, 0, 0, 0))
|
||||
|
||||
# Draw outline
|
||||
# Draw text based on orientation
|
||||
if not self.vertical:
|
||||
# Horizontal text outline
|
||||
for dx in range(-self.outline_thickness, self.outline_thickness + 1):
|
||||
for dy in range(-self.outline_thickness, self.outline_thickness + 1):
|
||||
if dx == 0 and dy == 0:
|
||||
continue
|
||||
ray.image_draw_text_ex(
|
||||
image,
|
||||
self.font,
|
||||
self.text,
|
||||
ray.Vector2(padding_x + dx, padding_y + dy),
|
||||
self.font_size,
|
||||
1.0,
|
||||
self.outline_color
|
||||
)
|
||||
self._draw_horizontal_text(image, padding_x, padding_y)
|
||||
else:
|
||||
# Vertical text outline
|
||||
current_y = padding_y
|
||||
for dx in range(-self.outline_thickness, self.outline_thickness + 1):
|
||||
for dy in range(-self.outline_thickness, self.outline_thickness + 1):
|
||||
if dx == 0 and dy == 0:
|
||||
continue
|
||||
self._draw_vertical_text(image, width, padding_x, padding_y)
|
||||
|
||||
current_y = padding_y
|
||||
for i, char in enumerate(self.text):
|
||||
if char in self.vertical_chars:
|
||||
char_width = self.font_size
|
||||
else:
|
||||
char_width = ray.measure_text_ex(self.font, char, self.font_size, 1.0).x
|
||||
|
||||
# Calculate centered position
|
||||
center_offset = (width - char_width) // 2
|
||||
char_height = self._calculate_vertical_spacing(
|
||||
char,
|
||||
self.text[i+1] if i+1 < len(self.text) else None
|
||||
)
|
||||
|
||||
if char in self.vertical_chars:
|
||||
self._draw_rotated_char(
|
||||
image,
|
||||
self.font,
|
||||
char,
|
||||
ray.Vector2(
|
||||
center_offset + dx,
|
||||
current_y + dy
|
||||
),
|
||||
self.font_size,
|
||||
self.outline_color,
|
||||
is_outline=True
|
||||
)
|
||||
else:
|
||||
ray.image_draw_text_ex(
|
||||
image,
|
||||
self.font,
|
||||
char,
|
||||
ray.Vector2(center_offset + dx, current_y + dy),
|
||||
self.font_size,
|
||||
1.0,
|
||||
self.outline_color
|
||||
)
|
||||
|
||||
current_y += char_height
|
||||
|
||||
# Draw main text
|
||||
if not self.vertical:
|
||||
# Horizontal text
|
||||
ray.image_draw_text_ex(
|
||||
image,
|
||||
self.font,
|
||||
self.text,
|
||||
ray.Vector2(padding_x, padding_y),
|
||||
self.font_size,
|
||||
1.0,
|
||||
self.text_color
|
||||
)
|
||||
else:
|
||||
# Vertical text
|
||||
current_y = padding_y
|
||||
for i, char in enumerate(self.text):
|
||||
if char in self.vertical_chars:
|
||||
char_width = self.font_size
|
||||
else:
|
||||
char_width = ray.measure_text_ex(self.font, char, self.font_size, 1.0).x
|
||||
|
||||
# Calculate centered position
|
||||
center_offset = (width - char_width) // 2
|
||||
char_height = self._calculate_vertical_spacing(
|
||||
char,
|
||||
self.text[i+1] if i+1 < len(self.text) else None
|
||||
)
|
||||
|
||||
if char in self.vertical_chars:
|
||||
self._draw_rotated_char(
|
||||
image,
|
||||
self.font,
|
||||
char,
|
||||
ray.Vector2(
|
||||
center_offset,
|
||||
current_y
|
||||
),
|
||||
self.font_size,
|
||||
self.text_color
|
||||
)
|
||||
else:
|
||||
ray.image_draw_text_ex(
|
||||
image,
|
||||
self.font,
|
||||
char,
|
||||
ray.Vector2(center_offset, current_y),
|
||||
self.font_size,
|
||||
1.0,
|
||||
self.text_color
|
||||
)
|
||||
|
||||
current_y += char_height
|
||||
|
||||
# Create texture and clean up
|
||||
# Create texture from image
|
||||
texture = ray.load_texture_from_image(image)
|
||||
ray.unload_image(image)
|
||||
return texture
|
||||
|
||||
def draw(self, src: ray.Rectangle, dest: ray.Rectangle, origin: ray.Vector2, rotation: float, color: ray.Color):
|
||||
"""Draw the outlined text"""
|
||||
ray.draw_texture_pro(self.texture, src, dest, origin, rotation, color)
|
||||
|
||||
def unload(self):
|
||||
"""Clean up resources"""
|
||||
# Unload all cached rotated images
|
||||
for img in self._rotation_cache.values():
|
||||
ray.unload_image(img)
|
||||
self._rotation_cache.clear()
|
||||
|
||||
# Unload texture
|
||||
ray.unload_texture(self.texture)
|
||||
|
||||
Reference in New Issue
Block a user