'lm', 'dark-mode' => 'dm', 'light-accent' => 'la', 'dark-accent' => 'da', ]; // Payment shapes $paymentShapes = ['icon', 'square', 'rectangle', 'text-only']; // Social shapes $socialShapes = ['icon', 'full-logo', 'text-only']; /** * Convert class-based styles to inline styles and clean up SVG * Returns array with 'defs', 'symbols' (internal symbols), and 'content' */ function processIcon(string $content, string $iconId): array { // Extract style definitions $styles = []; if (preg_match('/]*>(.*?)<\/style>/s', $content, $styleMatch)) { preg_match_all('/\.([a-zA-Z0-9_-]+)\s*\{([^}]+)\}/', $styleMatch[1], $rules, PREG_SET_ORDER); foreach ($rules as $rule) { $styles[$rule[1]] = trim($rule[2]); } } // Remove style block, XML declaration, comments $content = preg_replace('/]*>.*?<\/style>/s', '', $content); $content = preg_replace('/<\?xml[^>]+\?>/', '', $content); $content = preg_replace('//s', '', $content); // Replace class attributes with inline styles BEFORE prefixing IDs foreach ($styles as $className => $cssProps) { $content = preg_replace_callback( '/class="([^"]*\b' . preg_quote($className, '/') . '\b[^"]*)"/', function ($match) use ($cssProps) { return 'style="' . $cssProps . '"'; }, $content ); } // Prefix ALL id attributes with icon ID to avoid conflicts $content = preg_replace('/\bid="([^"]+)"/', 'id="' . $iconId . '_$1"', $content); // Update ALL url(#...) references (fill, clip-path, mask, etc.) $content = preg_replace('/url\(#([^)]+)\)/', 'url(#' . $iconId . '_$1)', $content); // Update href="#..." references (matches both xlink:href and modern href) $content = preg_replace('/href="#([^"]+)"/', 'href="#' . $iconId . '_$1"', $content); // Extract inner content (everything between and ) if (!preg_match('/]*>(.*)<\/svg>/s', $content, $match)) { return ['defs' => '', 'symbols' => '', 'content' => '']; } $innerContent = trim($match[1]); // Extract ALL defs content (may be multiple defs blocks or nested) $allDefs = ''; $innerContent = preg_replace_callback( '/]*>(.*?)<\/defs>/s', function ($match) use (&$allDefs) { $allDefs .= $match[1]; return ''; }, $innerContent ); // Also extract referenceable elements that are outside tags // These need to be in the root defs for to work properly $refElements = ['clipPath', 'linearGradient', 'radialGradient', 'mask', 'filter', 'pattern']; foreach ($refElements as $tag) { $innerContent = preg_replace_callback( '/<' . $tag . '[^>]*>.*?<\/' . $tag . '>/s', function ($match) use (&$allDefs) { $allDefs .= $match[0]; return ''; }, $innerContent ); } // Extract internal elements (like in SEPA icon) - these go at root level, not in defs $internalSymbols = ''; $innerContent = preg_replace_callback( '/]*>.*?<\/symbol>/s', function ($match) use (&$internalSymbols) { $internalSymbols .= $match[0]; return ''; }, $innerContent ); return ['defs' => $allDefs, 'symbols' => $internalSymbols, 'content' => trim($innerContent)]; } /** * Build a sprite from a directory of SVG files */ function buildSprite(string $sourceDir, string $outputFile, string $modeSuffix, string $shape): void { if (!is_dir($sourceDir)) { echo " Skipping: $sourceDir (not found)\n"; return; } $files = glob($sourceDir . '/*.svg'); if (empty($files)) { echo " Skipping: $sourceDir (no SVG files)\n"; return; } $symbols = []; $allDefs = ''; $internalSymbols = ''; foreach ($files as $file) { $filename = strtolower(basename($file, '.svg')); // Symbol ID includes shape for uniqueness: {shape}_{brand}_{mode} // e.g., text-only_paypal_lm, rectangle_visa_la, icon_mastercard_dm $symbolId = $shape . '_' . $filename; $content = file_get_contents($file); // Extract viewBox from original SVG preg_match('/viewBox="([^"]+)"/', $content, $viewBoxMatch); $viewBox = $viewBoxMatch[1] ?? '0 0 45 45'; // Process the icon (convert styles, prefix IDs, extract defs) $result = processIcon($content, $symbolId); if (empty($result['content'])) { echo " Warning: Could not parse $filename\n"; continue; } // Collect defs at sprite root level (so can resolve references) if (!empty($result['defs'])) { $allDefs .= $result['defs']; } // Collect internal symbols at root level (not in defs) if (!empty($result['symbols'])) { $internalSymbols .= $result['symbols']; } $symbols[] = " {$result['content']}"; } if (empty($symbols)) { echo " Skipping: $outputFile (no valid symbols)\n"; return; } $sprite = "\n"; if (!empty($allDefs)) { $sprite .= "$allDefs\n"; } if (!empty($internalSymbols)) { $sprite .= $internalSymbols . "\n"; } $sprite .= implode("\n", $symbols) . "\n"; $sprite .= "\n"; file_put_contents($outputFile, $sprite); echo " Created: $outputFile (" . count($symbols) . " icons)\n"; } // Build payment sprites echo "Building payment sprites...\n"; foreach ($paymentShapes as $shape) { foreach ($modes as $modeDir => $modeSuffix) { $sourceDir = "$materialsDir/payment-icons/$shape/$modeDir"; $outputFile = "$outputDir/payments/$shape-$modeSuffix.svg"; buildSprite($sourceDir, $outputFile, $modeSuffix, $shape); } } // Build social sprites echo "\nBuilding social sprites...\n"; foreach ($socialShapes as $shape) { foreach ($modes as $modeDir => $modeSuffix) { $sourceDir = "$materialsDir/socials-icons/$shape/$modeDir"; $outputFile = "$outputDir/socials/$shape-$modeSuffix.svg"; buildSprite($sourceDir, $outputFile, $modeSuffix, $shape); } } echo "\nDone!\n";