#!/usr/bin/env python3 """ AI Moji CSS Generator フォントメタデータからCSS/SCSSファイルを自動生成 """ import json from pathlib import Path from typing import Dict, List from datetime import datetime class AIMojiCSSGenerator: def __init__(self, output_dir: str = "../../dist"): self.output_dir = Path(output_dir) self.css_dir = self.output_dir / "css" self.scss_dir = self.output_dir / "scss" self.metadata_file = self.output_dir / "metadata.json" # ディレクトリ作成 self.css_dir.mkdir(parents=True, exist_ok=True) self.scss_dir.mkdir(parents=True, exist_ok=True) def load_metadata(self) -> Dict: """メタデータファイルを読み込み""" if not self.metadata_file.exists(): raise FileNotFoundError(f"メタデータファイルが見つかりません: {self.metadata_file}") with open(self.metadata_file, 'r', encoding='utf-8') as f: return json.load(f) def generate_css_template(self, metadata: Dict) -> str: """FontAwesome風CSSテンプレート生成""" font_family = metadata["font_family"] css_prefix = metadata["css_prefix"] css_content = f"""/*! * AI Moji Icons v{metadata["version"]} * Generated on {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} * {metadata["total_icons"]} icons in the set */ @font-face {{ font-family: '{font_family}'; src: url('../fonts/{font_family}.eot'); src: url('../fonts/{font_family}.eot?#iefix') format('embedded-opentype'), url('../fonts/{font_family}.woff2') format('woff2'), url('../fonts/{font_family}.woff') format('woff'), url('../fonts/{font_family}.ttf') format('truetype'), url('../fonts/{font_family}.svg#{font_family}') format('svg'); font-weight: normal; font-style: normal; font-display: block; }} .{css_prefix} {{ /* use !important to prevent issues with browser extensions that change fonts */ font-family: '{font_family}' !important; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }} /* Individual Icon Classes */ """ # 各アイコンのCSSクラス生成 for icon in metadata["icons"]: css_content += f'.{icon["css_class"]}::before {{\n' css_content += f' content: "\\{icon["unicode"][2:]}";\n' css_content += '}\n\n' return css_content def generate_scss_variables(self, metadata: Dict) -> str: """SCSS変数ファイル生成""" scss_content = f"""// // AI Moji Icons Variables // Generated on {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} // // Font Configuration $aimoji-font-family: '{metadata["font_family"]}' !default; $aimoji-font-path: '../fonts' !default; $aimoji-css-prefix: '{metadata["css_prefix"]}' !default; $aimoji-version: '{metadata["version"]}' !default; // Unicode Variables """ # 各アイコンのUnicode変数 for icon in metadata["icons"]: var_name = icon["name"].replace("-", "_").replace(".", "_") scss_content += f'$aimoji-{var_name}: "\\{icon["unicode"][2:]}";\n' scss_content += "\n// Icon Map for iteration\n" scss_content += "$aimoji-icons: (\n" for i, icon in enumerate(metadata["icons"]): var_name = icon["name"].replace("-", "_").replace(".", "_") comma = "," if i < len(metadata["icons"]) - 1 else "" scss_content += f' "{icon["name"]}": $aimoji-{var_name}{comma}\n' scss_content += ");\n" return scss_content def generate_scss_mixins(self) -> str: """SCSS mixinファイル生成""" return """// // AI Moji Icons Mixins // // Base icon mixin @mixin aimoji-base() { font-family: $aimoji-font-family; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } // Icon content mixin @mixin aimoji-icon($unicode) { @include aimoji-base(); &::before { content: $unicode; } } // Size mixins @mixin aimoji-size($size) { font-size: $size; line-height: 1; } // Rotation mixins @mixin aimoji-rotate($degrees) { transform: rotate(#{$degrees}deg); } @mixin aimoji-flip-horizontal() { transform: scaleX(-1); } @mixin aimoji-flip-vertical() { transform: scaleY(-1); } // Animation mixins @mixin aimoji-spin() { animation: aimoji-spin 2s infinite linear; } @keyframes aimoji-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(359deg); } } @mixin aimoji-pulse() { animation: aimoji-pulse 1s infinite; } @keyframes aimoji-pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } """ def minify_css(self, css_content: str) -> str: """CSS最小化(簡易版)""" import re # コメント削除 css_content = re.sub(r'/\*.*?\*/', '', css_content, flags=re.DOTALL) # 余分な空白削除 css_content = re.sub(r'\s+', ' ', css_content) # セミコロン前後の空白削除 css_content = re.sub(r'\s*;\s*', ';', css_content) # ブレース前後の空白削除 css_content = re.sub(r'\s*{\s*', '{', css_content) css_content = re.sub(r'\s*}\s*', '}', css_content) return css_content.strip() def generate_all(self) -> Dict: """全CSSファイル生成""" print("📝 CSS/SCSSファイル生成開始...") # メタデータ読み込み metadata = self.load_metadata() # CSS生成 css_content = self.generate_css_template(metadata) css_file = self.css_dir / "aimoji.css" with open(css_file, 'w', encoding='utf-8') as f: f.write(css_content) print(f"✅ CSS生成完了: {css_file}") # CSS最小化版生成 css_min_content = self.minify_css(css_content) css_min_file = self.css_dir / "aimoji.min.css" with open(css_min_file, 'w', encoding='utf-8') as f: f.write(css_min_content) print(f"✅ CSS最小化版生成完了: {css_min_file}") # SCSS変数生成 scss_vars = self.generate_scss_variables(metadata) scss_vars_file = self.scss_dir / "_variables.scss" with open(scss_vars_file, 'w', encoding='utf-8') as f: f.write(scss_vars) print(f"✅ SCSS変数生成完了: {scss_vars_file}") # SCSS mixins生成 scss_mixins = self.generate_scss_mixins() scss_mixins_file = self.scss_dir / "_mixins.scss" with open(scss_mixins_file, 'w', encoding='utf-8') as f: f.write(scss_mixins) print(f"✅ SCSS mixins生成完了: {scss_mixins_file}") return { "css_file": str(css_file), "css_min_file": str(css_min_file), "scss_vars_file": str(scss_vars_file), "scss_mixins_file": str(scss_mixins_file), "total_icons": metadata["total_icons"] } def main(): """メイン実行関数""" try: generator = AIMojiCSSGenerator() result = generator.generate_all() print("\n🎨 CSS/SCSS生成完了!") print(f"📊 総アイコン数: {result['total_icons']}") except Exception as e: print(f"❌ エラーが発生しました: {e}") return 1 return 0 if __name__ == "__main__": exit(main())