import os SUPPORTED_COLORS = {"blue", "purple", "red", "orange", "green", "pink"} COLOR_RGB_MAP = { "blue": (47, 108, 229), "purple": (138, 47, 229), "red": (229, 47, 47), "orange": (229, 138, 47), "green": (47, 229, 47), "pink": (229, 47, 168), } DEFAULT_COLOR = "blue" def get_assets_dir(): """Returns the absolute path to the assets directory""" current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) return os.path.join(current_dir, "assets") def get_valid_color(slide_colour: str) -> str: """ Returns the validated color name. Falls back to DEFAULT_COLOR if unsupported. """ color = slide_colour.lower() return color if color in SUPPORTED_COLORS else DEFAULT_COLOR def get_color_rgb(slide_colour: str) -> tuple: """ Returns the RGB color tuple based on the given slide color. """ color = get_valid_color(slide_colour) return COLOR_RGB_MAP.get(color, COLOR_RGB_MAP[DEFAULT_COLOR]) def get_background_image_path(slide_colour: str, first_slide: bool = False) -> str: """ Returns the path to the background image based on the slide color and type. """ color = get_valid_color(slide_colour) suffix = "bg1" if first_slide else "bg2" assets_dir = get_assets_dir() image_path = os.path.join(assets_dir, f"{color}_{suffix}.png") if not os.path.exists(image_path): print(f"Warning: Background image not found at {image_path}") # Fallback to basic background if color-specific one doesn't exist image_path = os.path.join(assets_dir, f"{suffix}.png") if not os.path.exists(image_path): print(f"Warning: Fallback background image not found at {image_path}") return image_path def get_code_rgb(slide_colour: str) -> tuple: """ Returns the RGB color used for code syntax highlighting. """ return get_color_rgb(slide_colour) def get_title_rgb(slide_colour: str) -> tuple: """ Returns the RGB color used for the topic title text. """ return get_color_rgb(slide_colour) def sanitize_filename(name: str, replacement: str = "_", max_length: int = 200) -> str: """ Sanitize a string to be safe for use as a filesystem name. Focuses on replacing problematic special characters with a safe replacement. Behavior: - Normalize unicode (NFKC) - Remove control characters - Replace path separators and shell-sensitive punctuation with `replacement` - Replace whitespace sequences with `replacement` - Replace hyphens and dots with `replacement` (per config) - Collapse repeated replacement characters - Trim leading/trailing replacement characters - Truncate to `max_length` characters - Preserve non-Latin characters (Unicode) by default """ if not name: return "" import re import unicodedata # Normalize s = unicodedata.normalize("NFKC", str(name)) # Remove control chars (C0 controls) s = "".join(ch for ch in s if ord(ch) >= 32) # Replace a set of problematic punctuation with replacement # This includes path separators, shell-sensitive chars, punctuation and symbols. # Also replace hyphen and dot as requested. # Include '+' so languages like 'C++' are sanitized cleanly s = re.sub(r"[\\/\\:\\*\\?\"<>\\|,;@#%&\\$'`~()\[\]{}\\\\\+]", replacement, s) s = re.sub(r"[-\\.]", replacement, s) # Replace whitespace sequences with single replacement s = re.sub(r"\s+", replacement, s) # Collapse multiple replacements if replacement: esc = re.escape(replacement) s = re.sub(rf"{esc}+", replacement, s) # Trim leading/trailing replacements and spaces s = s.strip(replacement + " ") # Truncate if max_length and len(s) > max_length: s = s[:max_length].rstrip(replacement) # Avoid empty result if not s: return "topic" return s