Created
June 5, 2025 22:36
-
-
Save will4381/d6d752f2223709e604f9a9785cd09ec7 to your computer and use it in GitHub Desktop.
SVGs to Swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import subprocess | |
import re | |
import shutil | |
def to_camel_case(text): | |
text = text.replace('-', ' ').replace('_', ' ') | |
parts = text.split() | |
if not parts: | |
return "" | |
return "".join(word.capitalize() for word in parts) | |
def generate_icon_code(): | |
assets_dir = "Sources/PlushIconKit/Assets" | |
generated_dir = "Sources/PlushIconKit/GeneratedIcons" | |
if not shutil.which("swiftdraw"): | |
print("Error: swiftdraw command not found. Please ensure it's installed and in your PATH.") | |
print("You can install it via Homebrew: brew install swiftdraw") | |
return | |
os.makedirs(generated_dir, exist_ok=True) | |
icon_details = [] | |
svg_files = sorted([f for f in os.listdir(assets_dir) if f.endswith(".svg")]) | |
if not svg_files: | |
print(f"No SVG files found in {assets_dir}") | |
return | |
print(f"Found {len(svg_files)} SVG files to process.") | |
for svg_filename in svg_files: | |
print(f"Processing {svg_filename}...") | |
if svg_filename.endswith("--Streamline-Plump.svg"): | |
short_name_part = svg_filename.removesuffix("--Streamline-Plump.svg") | |
elif svg_filename.endswith(".svg"): | |
short_name_part = svg_filename.removesuffix(".svg") | |
else: | |
print(f" Unrecognized SVG filename suffix for {svg_filename}. Skipping.") | |
continue | |
base_swift_name = to_camel_case(short_name_part) | |
if not base_swift_name: | |
print(f" Could not determine base_swift_name for {svg_filename} (short_name_part: '{short_name_part}'). Skipping.") | |
continue | |
original_swiftdraw_input_name = svg_filename.removesuffix(".svg") | |
original_swiftdraw_func_suffix = to_camel_case(original_swiftdraw_input_name) | |
if not original_swiftdraw_func_suffix: | |
print(f" Could not determine original_swiftdraw_func_suffix for {svg_filename}. Skipping.") | |
continue | |
input_svg_path = os.path.join(assets_dir, svg_filename) | |
output_swift_filename = f"{base_swift_name}.swift" | |
output_swift_path = os.path.join(generated_dir, output_swift_filename) | |
try: | |
swiftdraw_command = ["swiftdraw", input_svg_path, "--format", "swift", "--output", output_swift_path] | |
result = subprocess.run( | |
swiftdraw_command, | |
check=True, capture_output=True, text=True | |
) | |
except subprocess.CalledProcessError as e: | |
print(f" Error generating Swift code for {svg_filename}:") | |
print(f" Command: {' '.join(e.cmd)}") | |
print(f" Stdout: {e.stdout}") | |
print(f" Stderr: {e.stderr}") | |
continue | |
try: | |
with open(output_swift_path, 'r') as f: | |
content = f.read() | |
# modify the public static function declaration | |
public_func_original_pattern = r"static func svg" + re.escape(original_swiftdraw_func_suffix) + r"\(" | |
public_func_new_replacement = f"static func plush{base_swift_name}(color: UIColor, " | |
content = re.sub(public_func_original_pattern, public_func_new_replacement, content) | |
# nodify the draw call within the UIGraphicsImageRenderer closure | |
draw_call_in_closure_pattern_str = r"(draw" + re.escape(original_swiftdraw_func_suffix) + r")(\(in: \$0\.cgContext, scale: scale\))" | |
draw_call_in_closure_pattern = re.compile(draw_call_in_closure_pattern_str) | |
def build_replacement_for_closure_draw_call(match_obj): | |
original_params_with_parens = match_obj.group(2) | |
if original_params_with_parens.endswith(')'): | |
params_core = original_params_with_parens[:-1] | |
new_call_string = f"drawPlush{base_swift_name}{params_core}, color: color)" | |
else: | |
print(f" Warning: Unexpected parameter format for closure draw call in {svg_filename}. Modification might be incorrect.") | |
new_call_string = f"drawPlush{base_swift_name}{original_params_with_parens}, color: color" | |
return new_call_string | |
content, num_replacements = draw_call_in_closure_pattern.subn(build_replacement_for_closure_draw_call, content) | |
if num_replacements == 0: | |
print(f" Warning: Draw call in closure for '{original_swiftdraw_func_suffix}' not found or not replaced in {svg_filename}. " | |
f"File '{output_swift_path}' might require manual check for dynamic color in the UIImage closure part.") | |
# modify the private static draw function definition | |
private_func_original_pattern = r"private static func draw" + re.escape(original_swiftdraw_func_suffix) + r"\(in ctx: CGContext, scale: CGSize\)" | |
private_func_new_replacement = f"private static func drawPlush{base_swift_name}(in ctx: CGContext, scale: CGSize, color: UIColor)" | |
content = re.sub(private_func_original_pattern, private_func_new_replacement, content) | |
# replace hardcoded fill color with the dynamic color parameter in the private draw function | |
color_setting_pattern = re.compile( | |
r"\s*let rgb = CGColorSpaceCreateDeviceRGB\(\)\s*\n" | |
r"\s*let color1 = CGColor\(colorSpace: rgb, components: \[(?:\d+(?:\.\d*)?), (?:\d+(?:\.\d*)?), (?:\d+(?:\.\d*)?), (?:\d+(?:\.\d*)?)\]\)!\s*\n" | |
r"\s*ctx\.setFillColor\(color1\)" | |
) | |
new_content_after_color_mod, num_color_replacements = color_setting_pattern.subn(r"\n ctx.setFillColor(color.cgColor)", content) | |
if num_color_replacements > 0: | |
content = new_content_after_color_mod | |
else: | |
fallback_fill_pattern = re.compile(r"(ctx\.setFillColor\()([^)]+)(\))") | |
content, num_fallback_replacements = fallback_fill_pattern.subn(r"\1color.cgColor\3", content) | |
if num_fallback_replacements == 0: | |
print(f" Warning: Could not find or replace fill color logic in {svg_filename}. Manual check needed for {output_swift_path}.") | |
with open(output_swift_path, 'w') as f: | |
f.write(content) | |
enum_case_name = base_swift_name[0].lower() + base_swift_name[1:] if base_swift_name else "" | |
public_function_name_for_enum = f"plush{base_swift_name}" | |
icon_details.append((enum_case_name, public_function_name_for_enum, base_swift_name)) | |
except FileNotFoundError: | |
print(f" Error: Generated file {output_swift_path} not found for modification. Skipping.") | |
continue | |
except Exception as e: | |
print(f" Error modifying Swift code for {output_swift_filename}: {e}") | |
continue | |
print("\nFinished processing all SVG files.") | |
if icon_details: | |
print("\nIcon details for enum generation (enum_case_name, UIImage.function_name, OriginalBaseName):") | |
# Sort icon_details by enum_case_name before printing | |
icon_details.sort(key=lambda x: x[0]) | |
for detail in icon_details: | |
print(f'("{detail[0]}", "{detail[1]}", "{detail[2]}")') | |
else: | |
print("\nNo icons were successfully processed to generate enum details.") | |
if __name__ == "__main__": | |
generate_icon_code() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment