Diseño-de-software
Arquitectura
La idea principal del diseño de este software es poder crear innumerables transformaciones de fuentes de la manera que queramos. Para lograr esto, creo que, al menos por ahora, una arquitectura de pipeline va a ser la mejor forma de lograr este proceso.
Fuente Genial -> Parse(bytes) -> [Transformaciones ordenadas] -> font.Write() -> Fuente Más Genial
Para las transformaciones, lo que tengo en mente es una función principal
Modify() que se encargue de todas las modificaciones de la fuente, y funciones
más pequeñas con alcance reducido que solo modifiquen algunos parámetros.
Esto se hace así porque hay un orden necesario para aplicar las transformaciones sobre la fuente. Este es un diseño inicial y comenzará tan ajustado como podamos hacerlo, pero a medida que los conceptos crezcan y todo sea más claro, podré liberar las restricciones de diseño y crear transformaciones y pipelines propios.
Representación de la Fuente
El struct Font en memoria contiene todas las tablas TTF como structs Go
tipados, con los glifos decodificados en contornos y puntos de control
explícitos:
Font
├── head, hhea, maxp, OS/2, name, cmap, post, loca, hmtx, kern
└── Glyphs: []Glyph
└── Contours: []Contour
└── Points: []Point { X, Y, OnCurve }
Los glifos no se almacenan como bytes crudos — cada punto de control está
decodificado y es direccionable. Esto hace que los plugins sean agnósticos al
formato: cuando se agrega soporte para CFF/OTF, el parser produce el mismo
struct Glyph y todos los plugins existentes funcionan sin cambios.
FontPatch
Cada transformación produce un FontPatch — una descripción de qué debe cambiar
— y lo entrega a Modify(), el único punto de mutación. Los plugins nunca tocan
la fuente directamente.
type FontPatch struct {
OS2 *OS2Patch
Hhea *HheaPatch
Hmtx *HmtxPatch
// ... otras tablas
// nil = dejar todos los glifos sin cambios
GlyphTransform func(glyph *Glyph)
}
Los parches de campos usan punteros — nil significa “no tocar esto.” El glyph
transform es un valor de función aplicado a cada glifo de la fuente. Un plugin
que solo cambia métricas no establece GlyphTransform; uno que modifica
contornos sí.
Registro de Plugins
Cada transformación es una función pura. Cada plugin deserializa su propio struct de parámetros tipado desde el payload JSON:
type Plugin func(*Font, json.RawMessage) (FontPatch, error)
var Registry = map[string]Plugin{
"bold": bold.Apply,
"monospace": monospace.Apply,
"width": width.Apply,
"bounce": bounce.Apply,
}
Los plugins se registran por nombre. Agregar una nueva transformación implica escribir una función y agregar una entrada al registro — nada más cambia.
Pipeline
Un pipeline es un archivo JSON: una lista ordenada de objetos
{ nombre, parámetros }. El motor lee el archivo, busca cada nombre en el
registro, entrega los parámetros crudos al plugin (que los deserializa a su
struct tipado) y llama a Modify() en secuencia.
[{ "bold": { "amount": 50 } }, { "monospace": { "target_width": 600 } }]
El estado de la fuente fluye a través de cada transformación en orden. El estado
final es serializado por font.Write().
CLI
handyman run pipeline.json input.ttf output.ttf
Categorías de Transformación
Dos categorías de transformación difieren en complejidad:
Solo métricas (ej. monospace) — establecer campos específicos de tablas.
Solo cambian los anchos de avance en hmtx; no se necesita trabajo en
contornos.
A nivel de contorno (ej. bold, width, bounce) — aplicar una operación
geométrica a los puntos de control de cada glifo. Estos proveen una función
GlyphTransform en su patch.
Ambos pasan por la misma llamada a Modify(). La distinción es interna al
plugin.
Restricciones de Diseño
Cero dependencias externas para el motor. Pura librería estándar de Go.
Build con go build.
Corrección de ida y vuelta primero. El parser y el writer se validan contra
archivos TTF reales antes de escribir cualquier transformación. Si
Parse → Write no produce un resultado byte-idéntico, ninguna transformación
puede ser confiable. En Go esto significa: usar slices (no mapas) para cualquier
tabla ordenada, cuidar la alineación en binary.Write y evitar gob.
Plugins agnósticos al formato. El struct Glyph es el lenguaje común entre
el parser y los plugins. Un parser CFF que produce el mismo struct hace que
todos los plugins existentes funcionen para archivos OTF sin cambios.