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.