#!/usr/bin/env python3 """ Deep SVG Group Analysis - Find what's being missed """ import re import sys def analyze_groups(svg_content): """Analyze group structure and transforms""" # Find all groups group_pattern = r'<(?:\w+:)?g\s+([^>]*)>' groups = [] for match in re.finditer(group_pattern, svg_content, re.IGNORECASE): attrs = match.group(1) pos = match.start() # Extract transform transform_match = re.search(r'transform="([^"]*)"', attrs) transform = transform_match.group(1) if transform_match else None groups.append({ 'position': pos, 'attrs': attrs, 'transform': transform }) return groups def find_elements_in_groups(svg_content): """Find which elements are inside groups vs top-level""" # Count total elements all_paths = len(re.findall(r'<(?:\w+:)?path\s+', svg_content, re.IGNORECASE)) all_images = len(re.findall(r'<(?:\w+:)?image\s+', svg_content, re.IGNORECASE)) # Remove group content and count remaining no_groups = re.sub(r'<(?:\w+:)?g[^>]*>[\s\S]*?', '', svg_content) top_level_paths = len(re.findall(r'<(?:\w+:)?path\s+', no_groups, re.IGNORECASE)) top_level_images = len(re.findall(r'<(?:\w+:)?image\s+', no_groups, re.IGNORECASE)) return { 'total_paths': all_paths, 'paths_in_groups': all_paths - top_level_paths, 'top_level_paths': top_level_paths, 'total_images': all_images, 'images_in_groups': all_images - top_level_images, 'top_level_images': top_level_images } def find_bounding_boxes(svg_content): """Find coordinate ranges to detect if lower half is missing""" # Extract all Y coordinates from paths y_coords = [] # From path d attributes - look for M, L, V commands with Y values path_pattern = r'd="([^"]*)"' for match in re.finditer(path_pattern, svg_content): d = match.group(1) # Extract numbers after M, L commands (every second number is Y) coords = re.findall(r'[ML]\s*([-\d.]+)\s+([-\d.]+)', d, re.IGNORECASE) for x, y in coords: try: y_coords.append(float(y)) except: pass if y_coords: return { 'min_y': min(y_coords), 'max_y': max(y_coords), 'y_range': max(y_coords) - min(y_coords) } return None def analyze_nested_groups(svg_content): """Find deeply nested groups""" # Find group opening/closing tags opens = [(m.start(), 'open') for m in re.finditer(r'<(?:\w+:)?g\s+[^>]*>', svg_content, re.IGNORECASE)] closes = [(m.start(), 'close') for m in re.finditer(r'', svg_content, re.IGNORECASE)] all_tags = sorted(opens + closes, key=lambda x: x[0]) max_depth = 0 current_depth = 0 depth_counts = {} for pos, tag_type in all_tags: if tag_type == 'open': current_depth += 1 max_depth = max(max_depth, current_depth) depth_counts[current_depth] = depth_counts.get(current_depth, 0) + 1 else: current_depth -= 1 return { 'max_nesting_depth': max_depth, 'groups_at_each_depth': depth_counts } def check_transform_types(svg_content): """Check what transform types are used""" transforms = re.findall(r'transform="([^"]*)"', svg_content) types = { 'matrix': 0, 'translate': 0, 'scale': 0, 'rotate': 0, 'skew': 0 } for t in transforms: if 'matrix' in t: types['matrix'] += 1 if 'translate' in t: types['translate'] += 1 if 'scale' in t: types['scale'] += 1 if 'rotate' in t: types['rotate'] += 1 if 'skew' in t: types['skew'] += 1 return types def main(): if len(sys.argv) < 2: print("Usage: python svg-group-analysis.py ") sys.exit(1) filepath = sys.argv[1] with open(filepath, 'r') as f: content = f.read() print(f"\n{'='*60}") print(f"SVG GROUP ANALYSIS: {filepath}") print('='*60) # Basic counts elem_counts = find_elements_in_groups(content) print(f"\nšŸ“Š ELEMENT DISTRIBUTION:") print(f" Total paths: {elem_counts['total_paths']}") print(f" - In groups: {elem_counts['paths_in_groups']}") print(f" - Top level: {elem_counts['top_level_paths']}") print(f" Total images: {elem_counts['total_images']}") print(f" - In groups: {elem_counts['images_in_groups']}") print(f" - Top level: {elem_counts['top_level_images']}") # Group nesting nesting = analyze_nested_groups(content) print(f"\nšŸ“ GROUP NESTING:") print(f" Max depth: {nesting['max_nesting_depth']}") print(f" Groups at each depth: {nesting['groups_at_each_depth']}") # Transforms transforms = check_transform_types(content) print(f"\nšŸ”„ TRANSFORMS USED:") for t, count in transforms.items(): if count > 0: print(f" {t}: {count}") # Groups with transforms groups = analyze_groups(content) groups_with_transforms = [g for g in groups if g['transform']] print(f"\nšŸ”§ GROUPS WITH TRANSFORMS: {len(groups_with_transforms)}") for g in groups_with_transforms[:5]: print(f" transform: {g['transform'][:60]}...") # Bounding box bbox = find_bounding_boxes(content) if bbox: print(f"\nšŸ“ Y-COORDINATE RANGE:") print(f" Min Y: {bbox['min_y']:.2f}") print(f" Max Y: {bbox['max_y']:.2f}") print(f" Range: {bbox['y_range']:.2f}") # ViewBox viewbox_match = re.search(r'viewBox="([^"]*)"', content) if viewbox_match: vb = viewbox_match.group(1).split() if len(vb) >= 4: vb_height = float(vb[3]) print(f" ViewBox height: {vb_height}") if bbox: coverage = (bbox['max_y'] - bbox['min_y']) / vb_height * 100 print(f" Content covers: {coverage:.1f}% of viewBox height") if __name__ == '__main__': main()