Files
PyTaiko/libs/utils.py
Yonokid c1081d255b Add animation restart method and new backgrounds
This commit adds a new `restart()` method to animation classes to properly
reset their state, avoiding the need to recreate animations. It also adds
several new background variations and improves background selection.
2025-06-18 23:11:17 -04:00

830 lines
33 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import hashlib
import math
import os
import tempfile
import time
import zipfile
from dataclasses import dataclass, field
from functools import lru_cache
from pathlib import Path
from typing import Any
import pyray as ray
import tomlkit
#TJA Format creator is unknown. I did not create the format, but I did write the parser though.
def get_zip_filenames(zip_path: Path) -> list[str]:
result = []
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
file_list = zip_ref.namelist()
for file_name in file_list:
result.append(file_name)
return result
def load_image_from_zip(zip_path: Path, filename: str) -> ray.Image:
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
with zip_ref.open(filename) as image_file:
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as temp_file:
temp_file.write(image_file.read())
temp_file_path = temp_file.name
image = ray.load_image(temp_file_path)
os.remove(temp_file_path)
return image
def load_texture_from_zip(zip_path: Path, filename: str) -> ray.Texture:
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
with zip_ref.open(filename) as image_file:
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as temp_file:
temp_file.write(image_file.read())
temp_file_path = temp_file.name
texture = ray.load_texture(temp_file_path)
os.remove(temp_file_path)
return texture
def load_all_textures_from_zip(zip_path: Path) -> dict[str, list[ray.Texture]]:
result_dict = dict()
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
files = zip_ref.namelist()
for file in files:
with zip_ref.open(file) as image_file:
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as temp_file:
temp_file.write(image_file.read())
temp_file_path = temp_file.name
texture = ray.load_texture(temp_file_path)
os.remove(temp_file_path)
true_filename, index = file.split('_img')
index = int(index.split('.')[0])
if true_filename not in result_dict:
result_dict[true_filename] = []
while len(result_dict[true_filename]) <= index:
result_dict[true_filename].append(None)
result_dict[true_filename][index] = texture
return result_dict
def rounded(num: float) -> int:
sign = 1 if (num >= 0) else -1
num = abs(num)
result = int(num)
if (num - result >= 0.5):
result += 1
return sign * result
def get_current_ms() -> int:
return rounded(time.time() * 1000)
def strip_comments(code: str) -> str:
result = ''
index = 0
for line in code.splitlines():
comment_index = line.find('//')
if comment_index == -1:
result += line
elif comment_index != 0 and not line[:comment_index].isspace():
result += line[:comment_index]
index += 1
return result
@lru_cache
def get_pixels_per_frame(bpm: float, time_signature: float, distance: float) -> float:
if bpm == 0:
return 0
beat_duration = 60 / bpm
total_time = time_signature * beat_duration
total_frames = 60 * total_time
return (distance / total_frames)
def get_config() -> dict[str, Any]:
if Path('dev-config.toml').exists():
with open(Path('dev-config.toml'), "r", encoding="utf-8") as f:
config_file = tomlkit.load(f)
return config_file
with open(Path('config.toml'), "r", encoding="utf-8") as f:
config_file = tomlkit.load(f)
return config_file
def save_config(config: dict[str, Any]) -> None:
if Path('dev-config.toml').exists():
with open(Path('dev-config.toml'), "w", encoding="utf-8") as f:
tomlkit.dump(config, f)
return
with open(Path('config.toml'), "w", encoding="utf-8") as f:
tomlkit.dump(config, f)
def is_l_don_pressed() -> bool:
keys = global_data.config["keys"]["left_don"]
gamepad_buttons = global_data.config["gamepad"]["left_don"]
for key in keys:
if ray.is_key_pressed(ord(key)):
return True
if ray.is_gamepad_available(0):
for button in gamepad_buttons:
if ray.is_gamepad_button_pressed(0, button):
return True
mid_x, mid_y = (1280//2, 720)
allowed_gestures = {ray.Gesture.GESTURE_TAP, ray.Gesture.GESTURE_DOUBLETAP}
if ray.get_gesture_detected() in allowed_gestures and ray.is_gesture_detected(ray.get_gesture_detected()):
for i in range(min(ray.get_touch_point_count(), 10)):
tap_pos = (ray.get_touch_position(i).x, ray.get_touch_position(i).y)
if math.dist(tap_pos, (mid_x, mid_y)) < 300 and tap_pos[0] <= mid_x:
return True
return False
def is_r_don_pressed() -> bool:
keys = global_data.config["keys"]["right_don"]
gamepad_buttons = global_data.config["gamepad"]["right_don"]
for key in keys:
if ray.is_key_pressed(ord(key)):
return True
if ray.is_gamepad_available(0):
for button in gamepad_buttons:
if ray.is_gamepad_button_pressed(0, button):
return True
mid_x, mid_y = (1280//2, 720)
allowed_gestures = {ray.Gesture.GESTURE_TAP, ray.Gesture.GESTURE_DOUBLETAP}
if ray.get_gesture_detected() in allowed_gestures and ray.is_gesture_detected(ray.get_gesture_detected()):
for i in range(min(ray.get_touch_point_count(), 10)):
tap_pos = (ray.get_touch_position(i).x, ray.get_touch_position(i).y)
if math.dist(tap_pos, (mid_x, mid_y)) < 300 and tap_pos[0] > mid_x:
return True
return False
def is_l_kat_pressed() -> bool:
keys = global_data.config["keys"]["left_kat"]
gamepad_buttons = global_data.config["gamepad"]["left_kat"]
for key in keys:
if ray.is_key_pressed(ord(key)):
return True
if ray.is_gamepad_available(0):
for button in gamepad_buttons:
if ray.is_gamepad_button_pressed(0, button):
return True
mid_x, mid_y = (1280//2, 720)
allowed_gestures = {ray.Gesture.GESTURE_TAP, ray.Gesture.GESTURE_DOUBLETAP}
if ray.get_gesture_detected() in allowed_gestures and ray.is_gesture_detected(ray.get_gesture_detected()):
for i in range(min(ray.get_touch_point_count(), 10)):
tap_pos = (ray.get_touch_position(i).x, ray.get_touch_position(i).y)
if math.dist(tap_pos, (mid_x, mid_y)) >= 300 and tap_pos[0] <= mid_x:
return True
return False
def is_r_kat_pressed() -> bool:
keys = global_data.config["keys"]["right_kat"]
gamepad_buttons = global_data.config["gamepad"]["right_kat"]
for key in keys:
if ray.is_key_pressed(ord(key)):
return True
if ray.is_gamepad_available(0):
for button in gamepad_buttons:
if ray.is_gamepad_button_pressed(0, button):
return True
mid_x, mid_y = (1280//2, 720)
allowed_gestures = {ray.Gesture.GESTURE_TAP, ray.Gesture.GESTURE_DOUBLETAP}
if ray.get_gesture_detected() in allowed_gestures and ray.is_gesture_detected(ray.get_gesture_detected()):
for i in range(min(ray.get_touch_point_count(), 10)):
tap_pos = (ray.get_touch_position(i).x, ray.get_touch_position(i).y)
if math.dist(tap_pos, (mid_x, mid_y)) >= 300 and tap_pos[0] > mid_x:
return True
return False
def draw_scaled_texture(texture: ray.Texture, x: int, y: int, scale: float, color: ray.Color) -> None:
src_rect = ray.Rectangle(0, 0, texture.width, texture.height)
dst_rect = ray.Rectangle(x, y, texture.width*scale, texture.height*scale)
ray.draw_texture_pro(texture, src_rect, dst_rect, ray.Vector2(0, 0), 0, color)
@dataclass
class SessionData:
selected_difficulty: int = 0
song_title: str = ''
result_score: int = 0
result_good: int = 0
result_ok: int = 0
result_bad: int = 0
result_max_combo: int = 0
result_total_drumroll: int = 0
result_gauge_length: int = 0
session_data = SessionData()
def reset_session():
return SessionData()
@dataclass
class GlobalData:
selected_song: Path = Path()
textures: dict[str, list[ray.Texture]] = field(default_factory=lambda: dict())
songs_played: int = 0
config: dict = field(default_factory=lambda: dict())
global_data = GlobalData()
rotation_cache = dict()
char_size_cache = dict()
horizontal_cache = dict()
text_cache = set()
if not Path('cache/image').exists():
Path('cache').mkdir()
Path('cache/image').mkdir()
for file in Path('cache/image').iterdir():
text_cache.add(file.stem)
@dataclass
class OutlinedText:
text: str
font_size: int
text_color: ray.Color
outline_color: ray.Color
font: ray.Font = ray.Font()
outline_thickness: int = 2
vertical: bool = False
line_spacing: float = 1.0 # Line spacing for vertical text
horizontal_spacing: float = 1.0 # Character spacing for horizontal text
lowercase_spacing_factor: float = 0.85 # Adjust spacing for lowercase letters and whitespace
vertical_chars: set = field(default_factory=lambda: {'-', '', '|', '/', '\\', '', '', '~', '', '', '(', ')',
'', '', '[', ']', '', '', '', '', '', '', '', ':', ''})
no_space_chars: set = field(default_factory=lambda: {
'', '','', '','', '','', '','', '',
'', '','', '','', '','', '','', '',
'', '','','','','','','','','','',
'','','','','','',''
})
# New field for horizontal exception strings
horizontal_exceptions: set = field(default_factory=lambda: {'!!!!', '!!!', '!!', '','','!?', '', '??', '', '†††', '(°∀°)', '(°∀°)'})
# New field for adjacent punctuation characters
adjacent_punctuation: set = field(default_factory=lambda: {'.', ',', '', '', "'", '"', '´', '`'})
def __post_init__(self):
# Cache for rotated characters
self._rotation_cache = rotation_cache
# Cache for character measurements
self._char_size_cache = char_size_cache
# Cache for horizontal exception measurements
self._horizontal_cache = horizontal_cache
self.hash = self._get_hash()
self.texture = self._create_texture()
def _load_font_for_text(self, text: str) -> ray.Font:
codepoint_count = ray.ffi.new('int *', 0)
unique_codepoints = set(text)
codepoints = ray.load_codepoints(''.join(unique_codepoints), codepoint_count)
return ray.load_font_ex(str(Path('Graphics/Modified-DFPKanteiryu-XB.ttf')), self.font_size, codepoints, 0)
def _get_hash(self):
n = hashlib.sha256()
n.update(self.text.encode('utf-8'))
n.update(str(self.vertical).encode('utf-8'))
n.update(str(self.horizontal_spacing).encode('utf-8')) # Include horizontal spacing in hash
n.update(str(self.outline_color.a).encode('utf-8'))
n.update(str(self.outline_color.r).encode('utf-8'))
n.update(str(self.outline_color.g).encode('utf-8'))
n.update(str(self.outline_color.b).encode('utf-8'))
n.update(str(self.text_color.a).encode('utf-8'))
n.update(str(self.text_color.r).encode('utf-8'))
n.update(str(self.text_color.g).encode('utf-8'))
n.update(str(self.text_color.b).encode('utf-8'))
n.update(str(self.font_size).encode('utf-8'))
return n.hexdigest()
def _parse_text_segments(self):
"""Parse text into segments, identifying horizontal exceptions"""
if not self.vertical:
return [{'text': self.text, 'is_horizontal': False}]
segments = []
i = 0
current_segment = ""
while i < len(self.text):
# Check if any horizontal exception starts at current position
found_exception = None
for exception in self.horizontal_exceptions:
if self.text[i:].startswith(exception):
found_exception = exception
break
if found_exception:
# Save current segment if it exists
if current_segment:
segments.append({'text': current_segment, 'is_horizontal': False})
current_segment = ""
# Add horizontal exception as separate segment
segments.append({'text': found_exception, 'is_horizontal': True})
i += len(found_exception)
else:
# Add character to current segment
current_segment += self.text[i]
i += 1
# Add remaining segment
if current_segment:
segments.append({'text': current_segment, 'is_horizontal': False})
return segments
def _group_characters_with_punctuation(self, text):
"""Group characters with their adjacent punctuation"""
groups = []
i = 0
while i < len(text):
current_char = text[i]
group = {'main_char': current_char, 'adjacent_punct': []}
# Look ahead for adjacent punctuation
j = i + 1
while j < len(text) and text[j] in self.adjacent_punctuation:
group['adjacent_punct'].append(text[j])
j += 1
groups.append(group)
i = j # Move to next non-punctuation character
return groups
def _get_horizontal_exception_texture(self, text: str, color):
"""Get or create a texture for horizontal exception text"""
cache_key = (text, color.r, color.g, color.b, color.a, 'horizontal')
if cache_key in self._horizontal_cache:
return self._horizontal_cache[cache_key]
# Measure the text
text_size = ray.measure_text_ex(self.font, text, self.font_size, 1.0)
padding = int(self.outline_thickness * 3)
# Create image with proper dimensions
img_width = int(text_size.x + padding * 2)
img_height = int(text_size.y + padding * 2)
temp_image = ray.gen_image_color(img_width, img_height, ray.Color(0, 0, 0, 0))
# Draw the text centered
ray.image_draw_text_ex(
temp_image,
self.font,
text,
ray.Vector2(padding, padding),
self.font_size,
1.0,
color
)
# Cache the image
self._horizontal_cache[cache_key] = temp_image
return temp_image
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):
"""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())
# Additional check for capitalization transition
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
spacing = self.line_spacing * (self.lowercase_spacing_factor if is_spacing_char else 1.0)
return self.font_size * spacing
def _get_rotated_char(self, char: str, color):
"""Get or create a rotated character texture from cache"""
cache_key = (char, color.r, color.g, color.b, color.a)
if cache_key in self._rotation_cache:
return self._rotation_cache[cache_key]
char_size = self._get_char_size(char)
padding = int(self.outline_thickness * 3) # Increased padding
temp_width = max(int(char_size.y) + padding, self.font_size + padding)
temp_height = max(int(char_size.x) + padding, self.font_size + padding)
temp_image = ray.gen_image_color(temp_width, temp_height, ray.Color(0, 0, 0, 0))
center_x = (temp_width - char_size.y) // 2
center_y = (temp_height - char_size.x) // 2
ray.image_draw_text_ex(
temp_image,
self.font,
char,
ray.Vector2(center_x-5, center_y), # Centered placement with padding
self.font_size,
1.0,
color
)
# 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)
# Cache the rotated image
self._rotation_cache[cache_key] = rotated_image
return rotated_image
def _calculate_horizontal_text_width(self):
"""Calculate the total width of horizontal text with custom spacing"""
if not self.text:
return 0
total_width = 0
for i, char in enumerate(self.text):
char_size = ray.measure_text_ex(self.font, char, self.font_size, 1.0)
total_width += char_size.x
# Add spacing between characters (except for the last character)
if i < len(self.text) - 1:
total_width += (char_size.x * (self.horizontal_spacing - 1.0))
return total_width
def _calculate_dimensions(self):
padding = int(self.outline_thickness * 3)
if not self.vertical:
if self.horizontal_spacing == 1.0:
# Use default raylib measurement for normal spacing
text_size = ray.measure_text_ex(self.font, self.text, self.font_size, 1.0)
return int(text_size.x + padding * 2), int(text_size.y + padding * 2)
else:
# Calculate custom spacing width
text_width = self._calculate_horizontal_text_width()
text_height = ray.measure_text_ex(self.font, "Ag", self.font_size, 1.0).y # Use sample chars for height
return int(text_width + padding * 2), int(text_height + padding * 2)
else:
# Parse text into segments
segments = self._parse_text_segments()
char_heights = []
char_widths = []
for segment in segments:
if segment['is_horizontal']:
# For horizontal exceptions, add their height as spacing
text_size = ray.measure_text_ex(self.font, segment['text'], self.font_size, 1.0)
char_heights.append(text_size.y * self.line_spacing)
char_widths.append(text_size.x)
else:
# Process vertical text with character grouping
char_groups = self._group_characters_with_punctuation(segment['text'])
for i, group in enumerate(char_groups):
main_char = group['main_char']
adjacent_punct = group['adjacent_punct']
# Get next group's main character for spacing calculation
next_char = char_groups[i+1]['main_char'] if i+1 < len(char_groups) else None
char_heights.append(self._calculate_vertical_spacing(main_char, next_char))
# Calculate width considering main char + adjacent punctuation
main_char_size = self._get_char_size(main_char)
group_width = main_char_size.x
# Add width for adjacent punctuation
for punct in adjacent_punct:
punct_size = self._get_char_size(punct)
group_width += punct_size.x
# For vertical characters, consider rotated dimensions
if main_char in self.vertical_chars:
char_widths.append(group_width + padding)
else:
char_widths.append(group_width)
max_char_width = max(char_widths) if char_widths else 0
total_height = sum(char_heights) if char_heights else 0
width = int(max_char_width + padding * 2) # Padding on both sides
height = int(total_height + padding * 2) # Padding on top and bottom
return width, height
def _draw_horizontal_text(self, image):
if self.horizontal_spacing == 1.0:
# Use original method for normal spacing
text_size = ray.measure_text_ex(self.font, self.text, self.font_size, 1.0)
position = ray.Vector2((image.width - text_size.x) / 2, (image.height - text_size.y) / 2)
for dx in range(-self.outline_thickness, self.outline_thickness + 1):
for dy in range(-self.outline_thickness, self.outline_thickness + 1):
# Skip the center position (will be drawn as main text)
if dx == 0 and dy == 0:
continue
# Calculate outline distance
dist = (dx*dx + dy*dy) ** 0.5
# Only draw outline positions that are near the outline thickness
if dist <= self.outline_thickness + 0.5:
ray.image_draw_text_ex(
image,
self.font,
self.text,
ray.Vector2(position.x + dx, position.y + dy),
self.font_size,
1.0,
self.outline_color
)
# Draw main text
ray.image_draw_text_ex(
image,
self.font,
self.text,
position,
self.font_size,
1.0,
self.text_color
)
else:
# Draw text with custom character spacing
text_width = self._calculate_horizontal_text_width()
text_height = ray.measure_text_ex(self.font, "Ag", self.font_size, 1.0).y
start_x = (image.width - text_width) / 2
start_y = (image.height - text_height) / 2
# First draw all outlines
current_x = start_x
for i, char in enumerate(self.text):
char_size = ray.measure_text_ex(self.font, char, self.font_size, 1.0)
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
dist = (dx*dx + dy*dy) ** 0.5
if dist <= self.outline_thickness + 0.5:
ray.image_draw_text_ex(
image,
self.font,
char,
ray.Vector2(current_x + dx, start_y + dy),
self.font_size,
1.0,
self.outline_color
)
# Move to next character position
current_x += char_size.x
if i < len(self.text) - 1: # Add spacing except for last character
current_x += (char_size.x * (self.horizontal_spacing - 1.0))
# Then draw all main text
current_x = start_x
for i, char in enumerate(self.text):
char_size = ray.measure_text_ex(self.font, char, self.font_size, 1.0)
ray.image_draw_text_ex(
image,
self.font,
char,
ray.Vector2(current_x, start_y),
self.font_size,
1.0,
self.text_color
)
# Move to next character position
current_x += char_size.x
if i < len(self.text) - 1: # Add spacing except for last character
current_x += (char_size.x * (self.horizontal_spacing - 1.0))
def _draw_vertical_text(self, image, width):
padding = int(self.outline_thickness * 2)
segments = self._parse_text_segments()
positions = []
current_y = padding # Start with padding at the top
for segment in segments:
if segment['is_horizontal']:
# Handle horizontal exception
text_size = ray.measure_text_ex(self.font, segment['text'], self.font_size, 1.0)
center_offset = (width - text_size.x) // 2
char_height = text_size.y * self.line_spacing
positions.append({
'type': 'horizontal',
'text': segment['text'],
'x': center_offset,
'y': current_y,
'height': char_height
})
current_y += char_height
else:
# Handle vertical text with character grouping
char_groups = self._group_characters_with_punctuation(segment['text'])
for i, group in enumerate(char_groups):
main_char = group['main_char']
adjacent_punct = group['adjacent_punct']
# Get next group for spacing calculation
next_char = char_groups[i+1]['main_char'] if i+1 < len(char_groups) else None
char_height = self._calculate_vertical_spacing(main_char, next_char)
# Calculate positioning for main character
main_char_size = self._get_char_size(main_char)
if main_char in self.vertical_chars:
rotated_img = self._get_rotated_char(main_char, self.text_color)
main_char_width = rotated_img.width
center_offset = (width - main_char_width) // 2
else:
main_char_width = main_char_size.x
center_offset = (width - main_char_width) // 2
# Add main character position
positions.append({
'type': 'vertical',
'char': main_char,
'x': center_offset,
'y': current_y,
'height': char_height,
'is_vertical_char': main_char in self.vertical_chars
})
# Add adjacent punctuation positions
punct_x_offset = center_offset + main_char_width
for punct in adjacent_punct:
punct_size = self._get_char_size(punct)
positions.append({
'type': 'vertical',
'char': punct,
'x': punct_x_offset,
'y': current_y+5,
'height': 0, # No additional height for punctuation
'is_vertical_char': punct in self.vertical_chars,
'is_adjacent': True
})
punct_x_offset += punct_size.x
current_y += char_height
# First draw all outlines
outline_thickness = int(self.outline_thickness)
for pos in positions:
if pos['type'] == 'horizontal':
# Draw horizontal text outline
for dx in range(-outline_thickness, outline_thickness + 1):
for dy in range(-outline_thickness, outline_thickness + 1):
if dx == 0 and dy == 0:
continue
dist = (dx*dx + dy*dy) ** 0.5
if dist <= outline_thickness + 0.5:
ray.image_draw_text_ex(
image,
self.font,
pos['text'],
ray.Vector2(pos['x'] + dx, pos['y'] + dy),
self.font_size,
1.0,
self.outline_color
)
else:
# Draw vertical character outline
for dx in range(-outline_thickness, outline_thickness + 1):
for dy in range(-outline_thickness, outline_thickness + 1):
if dx == 0 and dy == 0:
continue
dist = (dx*dx + dy*dy) ** 0.5
if dist <= outline_thickness + 0.5:
if pos['is_vertical_char']:
rotated_img = self._get_rotated_char(pos['char'], self.outline_color)
ray.image_draw(
image,
rotated_img,
ray.Rectangle(0, 0, rotated_img.width, rotated_img.height),
ray.Rectangle(
int(pos['x'] + dx),
int(pos['y'] + dy),
rotated_img.width,
rotated_img.height
),
ray.WHITE
)
else:
ray.image_draw_text_ex(
image,
self.font,
pos['char'],
ray.Vector2(pos['x'] + dx, pos['y'] + dy),
self.font_size,
1.0,
self.outline_color
)
# Then draw all main text
for pos in positions:
if pos['type'] == 'horizontal':
# Draw horizontal text
ray.image_draw_text_ex(
image,
self.font,
pos['text'],
ray.Vector2(pos['x'], pos['y']),
self.font_size,
1.0,
self.text_color
)
else:
# Draw vertical character
if pos['is_vertical_char']:
rotated_img = self._get_rotated_char(pos['char'], self.text_color)
ray.image_draw(
image,
rotated_img,
ray.Rectangle(0, 0, rotated_img.width, rotated_img.height),
ray.Rectangle(
int(pos['x']),
int(pos['y']),
rotated_img.width,
rotated_img.height
),
ray.WHITE
)
else:
ray.image_draw_text_ex(
image,
self.font,
pos['char'],
ray.Vector2(pos['x'], pos['y']),
self.font_size,
1.0,
self.text_color
)
def _create_texture(self):
if self.hash in text_cache:
texture = ray.load_texture(f'cache/image/{self.hash}.png')
return texture
self.font = self._load_font_for_text(self.text)
width, height = self._calculate_dimensions()
width += int(self.outline_thickness * 1.5)
height += int(self.outline_thickness * 1.5)
image = ray.gen_image_color(width, height, ray.Color(0, 0, 0, 0))
if not self.vertical:
self._draw_horizontal_text(image)
else:
self._draw_vertical_text(image, width)
ray.export_image(image, f'cache/image/{self.hash}.png')
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):
ray.draw_texture_pro(self.texture, src, dest, origin, rotation, color)
def unload(self):
for img in self._rotation_cache.values():
ray.unload_image(img)
self._rotation_cache.clear()
for img in self._horizontal_cache.values():
ray.unload_image(img)
self._horizontal_cache.clear()
ray.unload_texture(self.texture)