Skip to content

Instantly share code, notes, and snippets.

@zvodd
Created January 22, 2025 21:04
Show Gist options
  • Save zvodd/a4691964443463bf8a131006106387a3 to your computer and use it in GitHub Desktop.
Save zvodd/a4691964443463bf8a131006106387a3 to your computer and use it in GitHub Desktop.
Resize SVG folder in/out; via viewbox and corresponding transform element wrap
import xml.etree.ElementTree as ET
import argparse
import sys
import os
from pathlib import Path
def resize_svg(input_file, width=None, height=None):
"""
Resize an SVG by wrapping its contents in a transform that scales to new dimensions
while preserving aspect ratio.
Args:
input_file (str): Path to input SVG file
width (float, optional): Desired width
height (float, optional): Desired height
Returns:
str: Modified SVG content as string
"""
# Parse the SVG file
tree = ET.parse(input_file)
root = tree.getroot()
# Get the viewBox attribute
viewbox = root.get('viewBox')
if not viewbox:
raise ValueError("SVG must have a viewBox attribute")
# Parse viewBox values
vb_min_x, vb_min_y, vb_width, vb_height = map(float, viewbox.split())
# Calculate actual width and height accounting for negative origins
orig_width = abs(vb_width)
orig_height = abs(vb_height)
# If only one dimension is provided, use it for both
if width is None and height is None:
raise ValueError("At least one of width or height must be specified")
elif width is None:
width = height
elif height is None:
height = width
# Calculate scale factors
scale_x = width / orig_width
scale_y = height / orig_height
# Create a new root element with the new dimensions
new_root = ET.Element('svg')
new_root.set('xmlns', 'http://www.w3.org/2000/svg')
new_root.set('width', str(width))
new_root.set('height', str(height))
# Calculate new viewBox values
new_vb_width = width
new_vb_height = height
new_vb_min_x = vb_min_x * scale_x
new_vb_min_y = vb_min_y * scale_y
new_root.set('viewBox', f"{new_vb_min_x} {new_vb_min_y} {new_vb_width} {new_vb_height}")
# Create a group to hold the transformed content
group = ET.SubElement(new_root, 'g')
group.set('transform', f'scale({scale_x} {scale_y})')
# Move all original content to the transformed group
for child in list(root):
group.append(child)
# Convert to string
return ET.tostring(new_root, encoding='unicode')
def process_folder(input_folder, output_folder, width=None, height=None):
"""
Process all SVG files in the input folder and save resized versions to the output folder.
Args:
input_folder (str): Path to input folder containing SVG files
output_folder (str): Path to output folder for resized SVG files
width (float, optional): Desired width
height (float, optional): Desired height
"""
# Create output folder if it doesn't exist
Path(output_folder).mkdir(parents=True, exist_ok=True)
# Get all SVG files in input folder
svg_files = Path(input_folder).glob('**/*.svg')
processed_count = 0
error_count = 0
for svg_file in svg_files:
# Create relative path for output file
rel_path = svg_file.relative_to(input_folder)
output_file = Path(output_folder) / rel_path
# Create necessary subdirectories
output_file.parent.mkdir(parents=True, exist_ok=True)
try:
resized_svg = resize_svg(svg_file, width, height)
with open(output_file, 'w') as f:
f.write(resized_svg)
processed_count += 1
print(f"Processed: {rel_path}")
except Exception as e:
print(f"Error processing {rel_path}: {str(e)}", file=sys.stderr)
error_count += 1
return processed_count, error_count
def main():
parser = argparse.ArgumentParser(description="Batch resize SVG files in a folder.", add_help=False)
parser.add_argument("input_folder", help="Input folder containing SVG files")
parser.add_argument("-w", "--width", type=float, help="New width")
parser.add_argument("-t", "--height", type=float, help="New height") # Using -t instead of -h
parser.add_argument("-o", "--output", required=True, help="Output folder for resized SVGs")
parser.add_argument("--help", action="help", help="Show this help message")
args = parser.parse_args()
try:
if not os.path.isdir(args.input_folder):
raise ValueError(f"Input path '{args.input_folder}' is not a directory")
processed, errors = process_folder(
args.input_folder,
args.output,
args.width,
args.height
)
print(f"\nProcessing complete:")
print(f"Successfully processed: {processed} files")
if errors > 0:
print(f"Errors encountered: {errors} files", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment