256 lines
7.7 KiB
Python
256 lines
7.7 KiB
Python
#!/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()) |