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
328 lines
12 KiB
|
1 year ago
|
/* ——————————————————————————————————————————————————————————————————————————
|
||
|
|
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
|
||
|
|
}
|