You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

328 lines
12 KiB

/* ——————————————————————————————————————————————————————————————————————————
Proyecto fontToB64
—————————————————————————————————————————————————————————————————————————————
Fichero main.go
Package fontToB64
Autor Juan V. Navarro juanvnl@activething.com
Creado 12/10/2025
—————————————————————————————————————————————————————————————————————————————
LICENSES AND TERMS OF USE
-------------------------
This software is licensed under the Elastic License v2.0 (the "License").
For full terms and additional information regarding permitted and prohibited
uses, please visit:
https://activething.com/wiawiaw/licenses
You may use, copy, modify, and redistribute this software internally within
your organization for any purpose, including research, development, and
testing, subject to the terms of this License.
You may NOT, however, use, provide, distribute, or make this software
available to any third party as part of a hosted service, SaaS offering, or
commercial product without first obtaining a commercial license from
Active Thing.
You may combine this software with other code, provided that such
combination does not circumvent the restrictions of this License.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. In no event shall
the authors or copyright holders be liable for any claim, damages, or other
liability arising from the use of this software.
—————————————————————————————————————————————————————————————————————————————
Web : activething.com | wiawiaw.com
git : g.activething.com | github.com/activething
Correo : dev@activething.com
—————————————————————————————————————————————————————————————————————————————
No deseo caminar sobre el agua", dijo Siddhartha.
Que los antiguos chamanes se contenten con tales habilidades.
—— Hermann Hesse, Siddhartha
—————————————————————————————————————————————————————————————————————————————
Copyright (c) 2025 Active Thing
————————————————————————————————————————————————————————————————————————————— */
package main
import (
"encoding/base64"
"flag"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
)
// Config contiene los parámetros de la línea de comandos
type Config struct {
dir string
outputName string
varName string
}
// FontData representa la estructura de un objeto de fuente en TypeScript
type FontData struct {
Name string `json:"name"`
Format string `json:"format"`
Filename string `json:"filename"`
Base64 string `json:"base64"`
Weight int `json:"weight"`
Style string `json:"style"`
}
func main() {
cfg := parseFlags()
// Verificar que el directorio existe
if _, err := os.Stat(cfg.dir); os.IsNotExist(err) {
fmt.Printf("Error: El directorio '%s' no existe\n", cfg.dir)
os.Exit(1)
}
// Leer archivos del directorio
entries, err := os.ReadDir(cfg.dir)
if err != nil {
fmt.Printf("Error al leer el directorio: %v\n", err)
os.Exit(1)
}
var fontCollection []FontData
processed := 0
fmt.Println("🔎 Buscando fuentes en el directorio...")
for _, entry := range entries {
if entry.IsDir() {
continue
}
// Procesar solo archivos de fuentes soportados
if isSupportedFont(entry.Name()) {
inputPath := filepath.Join(cfg.dir, entry.Name())
fmt.Printf("Procesando: %s\n", entry.Name())
fontData, err := processFontFile(inputPath)
if err != nil {
fmt.Printf(" Error: %v\n", err)
continue
}
fontCollection = append(fontCollection, fontData)
fmt.Printf(" ✓ Añadido: %s (Weight: %d, Style: %s)\n", fontData.Name, fontData.Weight, fontData.Style)
processed++
}
}
if processed == 0 {
fmt.Println("\nNo se encontraron fuentes soportadas (.ttf, .otf, .woff, .woff2)")
return
}
// Generar el archivo TypeScript
if err := generateTsFile(fontCollection, cfg); err != nil {
fmt.Printf("\nError al generar el archivo TypeScript: %v\n", err)
os.Exit(1)
}
fmt.Printf("\n✅ %d fuentes procesadas correctamente\n", processed)
}
func parseFlags() Config {
cfg := Config{}
flag.StringVar(&cfg.dir, "dir", "", "Directorio con las fuentes (requerido)")
flag.StringVar(&cfg.outputName, "output", "", "Nombre del archivo de salida (sin extensión)")
flag.StringVar(&cfg.varName, "varname", "projectFonts", "Nombre para la variable exportada en TypeScript")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "\n╔════════════════════════════════════════════════════════════════════╗\n")
fmt.Fprintf(os.Stderr, "║ Procesador de Fuentes a Base64 para TypeScript ║\n")
fmt.Fprintf(os.Stderr, "╚════════════════════════════════════════════════════════════════════╝\n\n")
fmt.Fprintf(os.Stderr, "DESCRIPCIÓN:\n")
fmt.Fprintf(os.Stderr, " Convierte archivos de fuentes a Base64 y los agrupa en un único\n")
fmt.Fprintf(os.Stderr, " archivo TypeScript como un array de objetos, ideal para CSS-in-JS.\n\n")
fmt.Fprintf(os.Stderr, "USO:\n")
fmt.Fprintf(os.Stderr, " %s -dir <directorio> [opciones]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "PARÁMETROS:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nEJEMPLOS:\n")
fmt.Fprintf(os.Stderr, " # Uso básico (genera 'fuentes.fonts.ts' con la variable 'projectFonts')\n")
fmt.Fprintf(os.Stderr, " %s -dir ./mis-fuentes\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, " # Especificando nombre de archivo y variable\n")
fmt.Fprintf(os.Stderr, " %s -dir ./mis-fuentes -output AppFonts -varname allFonts\n\n", os.Args[0])
}
flag.Parse()
if cfg.dir == "" {
flag.Usage()
os.Exit(1)
}
if cfg.outputName == "" {
cfg.outputName = filepath.Base(cfg.dir)
}
return cfg
}
// processFontFile lee una fuente, la convierte y extrae sus metadatos.
func processFontFile(inputPath string) (FontData, error) {
fileBytes, err := os.ReadFile(inputPath)
if err != nil {
return FontData{}, fmt.Errorf("error al leer el archivo: %w", err)
}
base64Str := base64.StdEncoding.EncodeToString(fileBytes)
filename := filepath.Base(inputPath)
ext := strings.ToLower(filepath.Ext(filename))
fontFormat, mimeType := getFontFormat(ext)
// Analizar nombre de archivo para extraer metadatos
name, weight, style := parseFontFilename(filename)
return FontData{
Name: name,
Format: fontFormat,
Filename: filename,
Base64: fmt.Sprintf("data:%s;base64,%s", mimeType, base64Str),
Weight: weight,
Style: style,
}, nil
}
// generateTsFile crea el archivo .ts final con la colección de fuentes.
func generateTsFile(fonts []FontData, cfg Config) error {
outputPath := filepath.Join(cfg.dir, cfg.outputName+".fonts.ts")
var content strings.Builder
content.WriteString("/**\n * Este archivo ha sido generado automáticamente.\n")
content.WriteString(" * Contiene las fuentes del proyecto en formato Base64.\n */\n\n")
// Escribir la interfaz para tipado
content.WriteString("export interface Font {\n")
content.WriteString(" name: string;\n")
content.WriteString(" format: string;\n")
content.WriteString(" filename: string;\n")
content.WriteString(" base64: string;\n")
content.WriteString(" weight: number;\n")
content.WriteString(" style: 'normal' | 'italic' | 'oblique';\n")
content.WriteString("}\n\n")
// Iniciar el array
content.WriteString(fmt.Sprintf("export const %s: Font[] = [\n", cfg.varName))
for _, font := range fonts {
content.WriteString(" {\n")
content.WriteString(fmt.Sprintf(" name: \"%s\",\n", font.Name))
content.WriteString(fmt.Sprintf(" format: \"%s\",\n", font.Format))
content.WriteString(fmt.Sprintf(" filename: \"%s\",\n", font.Filename))
content.WriteString(fmt.Sprintf(" base64: \"%s\",\n", font.Base64))
content.WriteString(fmt.Sprintf(" weight: %d,\n", font.Weight))
content.WriteString(fmt.Sprintf(" style: \"%s\",\n", font.Style))
content.WriteString(" },\n")
}
content.WriteString("];\n")
err := os.WriteFile(outputPath, []byte(content.String()), 0644)
if err != nil {
return err
}
fmt.Printf("\n🎉 Archivo TypeScript generado: %s\n", filepath.Base(outputPath))
return nil
}
// --- Funciones de Ayuda ---
func isSupportedFont(filename string) bool {
ext := strings.ToLower(filepath.Ext(filename))
switch ext {
case ".ttf", ".otf", ".woff", ".woff2":
return true
default:
return false
}
}
func getFontFormat(ext string) (format string, mimeType string) {
switch ext {
case ".ttf":
return "truetype", "font/ttf"
case ".otf":
return "opentype", "font/otf"
case ".woff":
return "woff", "font/woff"
case ".woff2":
return "woff2", "font/woff2"
default:
return "unknown", "application/octet-stream"
}
}
// parseFontFilename intenta deducir el nombre, peso y estilo de la fuente desde el nombre del archivo.
// Es una heurística y puede necesitar ajustes según la convención de nombres.
func parseFontFilename(filename string) (name string, weight int, style string) {
// Valores por defecto
weight = 400 // normal
style = "normal"
// Quitar la extensión
cleanName := strings.TrimSuffix(filename, filepath.Ext(filename))
// Mapeo de pesos y estilos
styleMap := map[string]string{
"Italic": "italic",
"Oblique": "oblique",
}
weightMap := map[string]int{
"Thin": 100,
"ExtraLight": 200,
"Light": 300,
"Regular": 400,
"Medium": 500,
"SemiBold": 600,
"Bold": 700,
"ExtraBold": 800,
"Black": 900,
"Heavy": 900,
}
// Buscar y reemplazar para extraer metadatos
for key, val := range styleMap {
if strings.Contains(cleanName, key) {
style = val
cleanName = strings.ReplaceAll(cleanName, key, "")
}
}
for key, val := range weightMap {
if strings.Contains(cleanName, key) {
weight = val
cleanName = strings.ReplaceAll(cleanName, key, "")
}
}
// Probar si el peso es numérico (e.g., "Font-700.ttf")
parts := strings.Split(cleanName, "-")
lastPart := parts[len(parts)-1]
if w, err := strconv.Atoi(lastPart); err == nil && w >= 100 && w <= 900 {
weight = w
cleanName = strings.Join(parts[:len(parts)-1], "-")
}
// Limpiar el nombre resultante
cleanName = strings.ReplaceAll(cleanName, "-", " ")
cleanName = strings.ReplaceAll(cleanName, "_", " ")
// Expresión regular para quitar espacios duplicados
cleanName = strings.Join(strings.Fields(cleanName), " ")
return cleanName, weight, style
}

Powered by TurnKey Linux.