Phase 1 - Refactoring: - Modular architecture: fonts/, graphics/, objects/, output/ - Fixed Zig 0.15 API changes (ArrayListUnmanaged) - Fixed memory issues in render() Phase 2 - Text System: - cell() with borders, fill, alignment - cellAdvanced() with position control - multiCell() with automatic word wrap - ln() for line breaks - getStringWidth() for text width calculation - Page margins (setMargins, setCellMargin) - Align enum (left, center, right) - Border packed struct New features: - New Pdf API (cleaner than legacy Document) - Document metadata (setTitle, setAuthor, setSubject) - Color: RGB, CMYK, Grayscale support - 52 unit tests passing - New example: text_demo.zig 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
326 lines
8.7 KiB
Markdown
326 lines
8.7 KiB
Markdown
# PLAN MAESTRO: zpdf - La Mejor Librería PDF en Zig
|
|
|
|
**Fecha inicio:** 2025-12-08
|
|
**Objetivo:** Crear la mejor librería PDF para Zig, basada en fpdf2 (Python)
|
|
**Filosofía:** Sin prisa, hacerlo perfecto
|
|
|
|
---
|
|
|
|
## DECISIÓN ARQUITECTÓNICA
|
|
|
|
### Fuente Principal: fpdf2 (Python)
|
|
|
|
**Repositorio:** https://github.com/py-pdf/fpdf2
|
|
**Documentación:** https://py-pdf.github.io/fpdf2/
|
|
|
|
**Por qué fpdf2:**
|
|
1. Arquitectura moderna y refinada (evolución de 20+ años de FPDF)
|
|
2. 1300+ tests = especificación ejecutable
|
|
3. UTF-8 nativo desde el diseño
|
|
4. Código Python limpio, fácil de traducir a Zig
|
|
5. Documentación exhaustiva
|
|
6. Features completos para documentos comerciales
|
|
|
|
**Descartados:**
|
|
- go-pdf/fpdf: Arrastra diseño antiguo del PHP original
|
|
- UniPDF: Comercial, no podemos estudiar el código
|
|
- lopdf (Rust): Muy bajo nivel, más para manipular que crear
|
|
|
|
---
|
|
|
|
## FASES DE IMPLEMENTACIÓN
|
|
|
|
### FASE 0: Estudio y Documentación (ACTUAL)
|
|
- [ ] Clonar fpdf2
|
|
- [ ] Leer y analizar fpdf.py completo
|
|
- [ ] Documentar arquitectura interna
|
|
- [ ] Identificar clases y métodos principales
|
|
- [ ] Mapear tipos Python → Zig
|
|
- [ ] Documentar el "core mínimo" necesario
|
|
|
|
### FASE 1: Core PDF Engine
|
|
**Objetivo:** Generar PDF válido mínimo
|
|
|
|
Componentes:
|
|
- [ ] PDFObject: Representación de objetos PDF (dict, array, stream, etc.)
|
|
- [ ] Document: Contenedor principal
|
|
- [ ] Page: Páginas individuales
|
|
- [ ] ContentStream: Comandos de dibujo
|
|
- [ ] Writer: Serialización a bytes PDF
|
|
- [ ] CrossReference: Tabla xref correcta
|
|
|
|
Entregable: PDF vacío válido que abre en cualquier lector
|
|
|
|
### FASE 2: Sistema de Texto
|
|
**Objetivo:** Texto con fuentes Type1 y posicionamiento
|
|
|
|
Componentes:
|
|
- [ ] Font: Gestión de fuentes Type1 (14 estándar)
|
|
- [ ] TextState: Estado actual (fuente, tamaño, color)
|
|
- [ ] Cell(): Celda rectangular con texto
|
|
- [ ] MultiCell(): Texto con saltos de línea automáticos
|
|
- [ ] Write(): Texto fluido
|
|
- [ ] Text(): Texto en posición absoluta
|
|
- [ ] Alineación: left, center, right, justify
|
|
|
|
Entregable: PDF con texto formateado, múltiples fuentes
|
|
|
|
### FASE 3: Sistema de Gráficos
|
|
**Objetivo:** Líneas, formas, colores
|
|
|
|
Componentes:
|
|
- [ ] Color: RGB, grayscale, CMYK
|
|
- [ ] Line(): Líneas
|
|
- [ ] Rect(): Rectángulos (stroke, fill, both)
|
|
- [ ] Circle/Ellipse(): Círculos y elipses
|
|
- [ ] Polygon(): Polígonos
|
|
- [ ] Bezier curves
|
|
- [ ] SetLineWidth, SetLineCap, SetLineJoin
|
|
- [ ] Transformaciones: translate, rotate, scale
|
|
|
|
Entregable: PDF con gráficos vectoriales
|
|
|
|
### FASE 4: Sistema de Imágenes
|
|
**Objetivo:** Embeber imágenes en PDF
|
|
|
|
Componentes:
|
|
- [ ] JPEG: Embebido directo (DCTDecode)
|
|
- [ ] PNG: Con y sin alpha (FlateDecode)
|
|
- [ ] Image(): Posicionar y escalar imágenes
|
|
- [ ] Aspect ratio automático
|
|
- [ ] Caché de imágenes (no duplicar)
|
|
|
|
Entregable: PDF con imágenes embebidas
|
|
|
|
### FASE 5: Layout y Tablas
|
|
**Objetivo:** Helpers de alto nivel para documentos
|
|
|
|
Componentes:
|
|
- [ ] Table: Helper para crear tablas
|
|
- [ ] Columns: Sistema de columnas
|
|
- [ ] SetMargins, SetAutoPageBreak
|
|
- [ ] Header/Footer callbacks
|
|
- [ ] Numeración de páginas
|
|
- [ ] Word wrap inteligente
|
|
|
|
Entregable: Facturas completas con tablas
|
|
|
|
### FASE 6: Features Avanzados
|
|
**Objetivo:** Completar la librería
|
|
|
|
Componentes:
|
|
- [ ] Links internos y externos
|
|
- [ ] Bookmarks/Outline
|
|
- [ ] Metadata (título, autor, etc.)
|
|
- [ ] Compresión streams (zlib)
|
|
- [ ] UTF-8 con fuentes TrueType embebidas
|
|
- [ ] Encriptación básica (opcional)
|
|
|
|
Entregable: Librería completa nivel producción
|
|
|
|
---
|
|
|
|
## ARQUITECTURA ZPDF (Diseño Preliminar)
|
|
|
|
```
|
|
zpdf/
|
|
├── src/
|
|
│ ├── root.zig # Exports públicos
|
|
│ ├── document.zig # Document principal
|
|
│ ├── page.zig # Página individual
|
|
│ ├── objects.zig # Tipos PDF (dict, array, stream, etc.)
|
|
│ ├── writer.zig # Serialización PDF
|
|
│ ├── content_stream.zig # Comandos gráficos
|
|
│ ├── fonts/
|
|
│ │ ├── font.zig # Interfaz Font
|
|
│ │ ├── type1.zig # Fuentes Type1 estándar
|
|
│ │ └── metrics.zig # Métricas de caracteres
|
|
│ ├── graphics/
|
|
│ │ ├── color.zig # Colores RGB/CMYK/Gray
|
|
│ │ ├── path.zig # Paths vectoriales
|
|
│ │ └── transform.zig # Transformaciones
|
|
│ ├── text/
|
|
│ │ ├── state.zig # Estado de texto
|
|
│ │ ├── layout.zig # Word wrap, alineación
|
|
│ │ └── cell.zig # Cell, MultiCell, Write
|
|
│ ├── image/
|
|
│ │ ├── jpeg.zig # Parser/embebido JPEG
|
|
│ │ └── png.zig # Parser/embebido PNG
|
|
│ └── util/
|
|
│ ├── buffer.zig # Buffer de bytes
|
|
│ └── encoding.zig # Encoding texto PDF
|
|
├── examples/
|
|
│ ├── hello.zig
|
|
│ ├── invoice.zig
|
|
│ ├── table.zig
|
|
│ └── images.zig
|
|
├── tests/
|
|
│ └── ... (muchos tests)
|
|
└── docs/
|
|
├── PLAN_MAESTRO_ZPDF.md # Este archivo
|
|
├── ARQUITECTURA_FPDF2.md # Análisis de fpdf2
|
|
└── API.md # Documentación API
|
|
```
|
|
|
|
---
|
|
|
|
## API OBJETIVO (Inspirada en fpdf2)
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
const pdf = @import("zpdf");
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
// Crear documento
|
|
var doc = try pdf.Document.init(allocator, .{
|
|
.orientation = .portrait,
|
|
.unit = .mm,
|
|
.format = .a4,
|
|
});
|
|
defer doc.deinit();
|
|
|
|
// Metadata
|
|
doc.setTitle("Factura #001");
|
|
doc.setAuthor("ACME Corp");
|
|
|
|
// Nueva página
|
|
var page = try doc.addPage();
|
|
|
|
// Márgenes
|
|
page.setMargins(10, 10, 10);
|
|
|
|
// Fuente
|
|
try page.setFont(.helvetica_bold, 24);
|
|
|
|
// Colores
|
|
page.setTextColor(pdf.Color.rgb(0, 100, 200));
|
|
|
|
// Texto
|
|
try page.cell(.{
|
|
.width = 0, // Hasta el margen
|
|
.height = 10,
|
|
.text = "FACTURA",
|
|
.align = .right,
|
|
});
|
|
|
|
// Salto de línea
|
|
page.ln(10);
|
|
|
|
// Línea
|
|
page.setDrawColor(pdf.Color.gray);
|
|
try page.line(10, page.getY(), 200, page.getY());
|
|
|
|
// Imagen
|
|
try page.image("logo.png", .{
|
|
.x = 10,
|
|
.y = 10,
|
|
.width = 40,
|
|
});
|
|
|
|
// Tabla
|
|
try page.table(.{
|
|
.x = 10,
|
|
.y = 100,
|
|
.columns = &.{
|
|
.{ .header = "Descripción", .width = 80, .align = .left },
|
|
.{ .header = "Cant.", .width = 20, .align = .center },
|
|
.{ .header = "Precio", .width = 30, .align = .right },
|
|
.{ .header = "Total", .width = 30, .align = .right },
|
|
},
|
|
.rows = &.{
|
|
&.{ "Producto A", "2", "10.00", "20.00" },
|
|
&.{ "Producto B", "1", "25.00", "25.00" },
|
|
},
|
|
});
|
|
|
|
// Guardar
|
|
try doc.save("factura.pdf");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## MAPEO TIPOS PYTHON → ZIG
|
|
|
|
| Python (fpdf2) | Zig (zpdf) |
|
|
|----------------|------------|
|
|
| `class FPDF` | `pub const Document = struct` |
|
|
| `str` | `[]const u8` |
|
|
| `float` | `f32` |
|
|
| `int` | `i32` o `u32` |
|
|
| `list` | `std.ArrayList` o slice |
|
|
| `dict` | `std.StringHashMap` o struct |
|
|
| `bytes` | `[]u8` |
|
|
| `Optional[T]` | `?T` |
|
|
| `Union[A, B]` | `union(enum)` |
|
|
| Exception | `error` union |
|
|
| `with open()` | `std.fs.File` |
|
|
| `io.BytesIO` | `std.ArrayList(u8)` |
|
|
|
|
---
|
|
|
|
## COMANDOS
|
|
|
|
```bash
|
|
# Zig
|
|
ZIG=/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2/zig
|
|
|
|
# Build
|
|
$ZIG build
|
|
|
|
# Tests
|
|
$ZIG build test
|
|
|
|
# Ejemplos
|
|
$ZIG build hello
|
|
$ZIG build invoice
|
|
|
|
# Ver PDF generado
|
|
evince hello.pdf
|
|
```
|
|
|
|
---
|
|
|
|
## REFERENCIAS
|
|
|
|
### Especificación PDF
|
|
- PDF 1.4: https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.4.pdf
|
|
- PDF 1.7 (ISO 32000): https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf
|
|
|
|
### Librerías de Referencia
|
|
- fpdf2 (Python): https://github.com/py-pdf/fpdf2
|
|
- go-pdf/fpdf (Go): https://codeberg.org/go-pdf/fpdf
|
|
- FPDF original (PHP): https://github.com/Setasign/FPDF
|
|
|
|
### Documentación fpdf2
|
|
- Tutorial: https://py-pdf.github.io/fpdf2/Tutorial.html
|
|
- API Reference: https://py-pdf.github.io/fpdf2/fpdf/
|
|
|
|
---
|
|
|
|
## NOTAS DE DESARROLLO
|
|
|
|
*Se irán añadiendo conforme avance el proyecto*
|
|
|
|
### 2025-12-08 - Inicio del proyecto
|
|
- Decisión: usar fpdf2 como fuente principal
|
|
- Razón: arquitectura más moderna, mejor documentación, más tests
|
|
- Primer paso: clonar y estudiar fpdf2
|
|
|
|
---
|
|
|
|
## SI ESTA CONVERSACIÓN SE CORTA
|
|
|
|
1. Leer este documento completo
|
|
2. Leer CLAUDE.md del proyecto
|
|
3. Continuar desde donde se quedó según las fases
|
|
4. El código de fpdf2 estará clonado en: `/mnt/cello2/arno/re/recode/zig/zpdf/reference/fpdf2/`
|
|
5. El análisis de arquitectura estará en: `docs/ARQUITECTURA_FPDF2.md`
|
|
|
|
---
|
|
|
|
**Recuerda:** Sin prisa, lo importante es hacerlo perfecto.
|