c165f2f788
Agrega 19 issues técnicas documentando funcionalidades implementadas y pendientes. Issues completadas (movidas a dev/issues/completed/): - 001-conversor-web-markdown.md - 002-accessibility-tree.md - 003-gestion-cookies-perfil.md - 004-gestion-extensiones-chrome.md - 005-eliminar-timeouts-innecesarios.md Issues implementadas: - 006-manejo-tabs-ventanas.md - 016-manejo-iframes.md - 017-actions-api.md - 018-file-uploads.md - 019-expected-conditions-mejoradas.md Issues pendientes (media prioridad): - 007-alert-prompt-confirm-handling.md - 008-screenshot-elementos-especificos.md - 009-pdf-generation.md - 010-device-emulation-completo.md - 011-downloads-handling.md Issues pendientes (baja prioridad / avanzado): - 012-browser-contexts-multi-sesion.md - 013-video-recording.md - 014-network-mocking-avanzado.md - 015-geolocation-permissions.md Incluye también dev/NUEVAS_FUNCIONALIDADES.md con resumen completo. Directorio: dev/
11 KiB
11 KiB
Issue #009: PDF Generation
Tipo: Enhancement Prioridad: Media Estado: Pendiente
Descripción
Implementar generación de PDFs de páginas web, similar a "Imprimir a PDF" del navegador.
Funcionalidad deseada
Operaciones básicas
- Generar PDF de página completa
- Generar PDF de página actual (viewport)
- Control de formato de página (A4, Letter, etc.)
- Orientación (portrait/landscape)
- Márgenes personalizables
- Headers y footers personalizados
- Background graphics (imágenes de fondo)
- Scale/zoom del contenido
Operaciones avanzadas
- Rangos de páginas específicos
- Números de página
- Fecha/hora en header/footer
- CSS para medios de impresión
- Protección de PDF (opcional)
Implementación técnica
Archivo sugerido
pkg/browser/pdf.go
CDP Method
Page.printToPDF - Genera PDF de la página
API propuesta
// PDFFormat formato de papel
type PDFFormat string
const (
PDFFormatA4 PDFFormat = "A4"
PDFFormatLetter PDFFormat = "Letter"
PDFFormatLegal PDFFormat = "Legal"
PDFFormatA3 PDFFormat = "A3"
PDFFormatTabloid PDFFormat = "Tabloid"
)
// PDFOrientation orientación de página
type PDFOrientation string
const (
PDFOrientationPortrait PDFOrientation = "portrait"
PDFOrientationLandscape PDFOrientation = "landscape"
)
// PDFMargins márgenes del PDF
type PDFMargins struct {
Top float64 // En pulgadas
Right float64
Bottom float64
Left float64
}
// PDFOptions opciones para generación de PDF
type PDFOptions struct {
// Formato de papel
Format PDFFormat // Default: A4
// Orientación
Orientation PDFOrientation // Default: portrait
// Dimensiones personalizadas (en pulgadas)
// Si se especifica, ignora Format
Width float64
Height float64
// Márgenes (en pulgadas)
Margins PDFMargins // Default: 1cm todos
// Scale del contenido (0.1 - 2.0)
Scale float64 // Default: 1.0
// Incluir colores y gráficos de fondo
PrintBackground bool // Default: false
// Rango de páginas (ej: "1-5, 8, 11-13")
PageRanges string
// Header template (HTML)
HeaderTemplate string
// Footer template (HTML)
FooterTemplate string
// Mostrar header y footer
DisplayHeaderFooter bool
// Preferir CSS para @media print
PreferCSSPageSize bool
// Generar PDFs etiquetados (accesibilidad)
GenerateTaggedPDF bool
}
// DefaultPDFOptions retorna opciones por defecto
func DefaultPDFOptions() *PDFOptions
// GeneratePDF genera un PDF de la página actual
func (b *Browser) GeneratePDF(ctx context.Context, opts *PDFOptions) ([]byte, error)
// SavePDF genera y guarda PDF a archivo
func (b *Browser) SavePDF(ctx context.Context, filepath string, opts *PDFOptions) error
// PrintToPDF genera PDF (alias de GeneratePDF)
func (b *Browser) PrintToPDF(ctx context.Context, opts *PDFOptions) ([]byte, error)
Casos de uso
Caso 1: PDF simple
// PDF con opciones por defecto (A4, portrait)
pdf, _ := b.GeneratePDF(ctx, nil)
os.WriteFile("page.pdf", pdf, 0644)
Caso 2: PDF con configuración personalizada
opts := &browser.PDFOptions{
Format: browser.PDFFormatLetter,
Orientation: browser.PDFOrientationLandscape,
PrintBackground: true, // Incluir colores de fondo
Scale: 0.8, // 80% del tamaño
Margins: browser.PDFMargins{
Top: 0.5,
Right: 0.5,
Bottom: 0.5,
Left: 0.5,
},
}
pdf, _ := b.GeneratePDF(ctx, opts)
Caso 3: PDF con header y footer
opts := &browser.PDFOptions{
DisplayHeaderFooter: true,
HeaderTemplate: `
<div style="font-size: 10px; text-align: center; width: 100%;">
<span class="title"></span>
</div>
`,
FooterTemplate: `
<div style="font-size: 10px; text-align: center; width: 100%;">
Página <span class="pageNumber"></span> de <span class="totalPages"></span>
</div>
`,
}
pdf, _ := b.GeneratePDF(ctx, opts)
Caso 4: PDF de rango específico
opts := &browser.PDFOptions{
PageRanges: "1-3, 5", // Solo páginas 1, 2, 3 y 5
}
pdf, _ := b.GeneratePDF(ctx, opts)
Caso 5: Guardar directamente a archivo
opts := browser.DefaultPDFOptions()
opts.Format = browser.PDFFormatA4
opts.PrintBackground = true
b.SavePDF(ctx, "report.pdf", opts)
Implementación interna
func (b *Browser) GeneratePDF(ctx context.Context, opts *PDFOptions) ([]byte, error) {
if opts == nil {
opts = DefaultPDFOptions()
}
// Construir parámetros CDP
params := map[string]interface{}{
"printBackground": opts.PrintBackground,
"displayHeaderFooter": opts.DisplayHeaderFooter,
"preferCSSPageSize": opts.PreferCSSPageSize,
"generateTaggedPDF": opts.GenerateTaggedPDF,
}
// Formato o dimensiones custom
if opts.Width > 0 && opts.Height > 0 {
params["paperWidth"] = opts.Width
params["paperHeight"] = opts.Height
} else {
// Usar formato predefinido
params["format"] = string(opts.Format)
}
// Orientación
if opts.Orientation != "" {
params["landscape"] = opts.Orientation == PDFOrientationLandscape
}
// Márgenes
params["marginTop"] = opts.Margins.Top
params["marginRight"] = opts.Margins.Right
params["marginBottom"] = opts.Margins.Bottom
params["marginLeft"] = opts.Margins.Left
// Scale
if opts.Scale > 0 {
params["scale"] = opts.Scale
}
// Page ranges
if opts.PageRanges != "" {
params["pageRanges"] = opts.PageRanges
}
// Templates
if opts.HeaderTemplate != "" {
params["headerTemplate"] = opts.HeaderTemplate
}
if opts.FooterTemplate != "" {
params["footerTemplate"] = opts.FooterTemplate
}
// Ejecutar comando
var result struct {
Data string `json:"data"` // Base64
Stream string `json:"stream"` // Stream handle (para PDFs grandes)
}
if err := b.cdpClient.Execute(ctx, "Page.printToPDF", params, &result); err != nil {
return nil, fmt.Errorf("failed to generate PDF: %w", err)
}
// Decodificar base64
data, err := base64.StdEncoding.DecodeString(result.Data)
if err != nil {
return nil, fmt.Errorf("failed to decode PDF: %w", err)
}
return data, nil
}
func DefaultPDFOptions() *PDFOptions {
return &PDFOptions{
Format: PDFFormatA4,
Orientation: PDFOrientationPortrait,
Scale: 1.0,
Margins: PDFMargins{
Top: 0.4, // ~1cm
Right: 0.4,
Bottom: 0.4,
Left: 0.4,
},
PrintBackground: false,
}
}
Comandos CDP
{
"method": "Page.printToPDF",
"params": {
"landscape": false,
"displayHeaderFooter": true,
"printBackground": true,
"scale": 1,
"paperWidth": 8.5,
"paperHeight": 11,
"marginTop": 0.4,
"marginBottom": 0.4,
"marginLeft": 0.4,
"marginRight": 0.4,
"pageRanges": "1-5",
"headerTemplate": "<div>Header</div>",
"footerTemplate": "<div>Footer</div>",
"preferCSSPageSize": false,
"generateTaggedPDF": false
}
}
// Response:
{
"data": "base64_encoded_pdf_data..."
}
Variables en templates
Header/Footer templates soportan:
<span class="date"></span>- Fecha actual<span class="title"></span>- Título de la página<span class="url"></span>- URL de la página<span class="pageNumber"></span>- Número de página actual<span class="totalPages"></span>- Total de páginas
Ejemplo de template completo
<div style="font-size: 10px; width: 100%; padding: 0 1cm;">
<div style="float: left;">
<span class="title"></span>
</div>
<div style="float: right;">
<span class="date"></span>
</div>
</div>
CSS para impresión
Aplicar estilos específicos para PDF
@media print {
.no-print {
display: none !important;
}
.page-break {
page-break-after: always;
}
body {
font-size: 12pt;
}
}
Inyectar CSS antes de generar PDF
// Inyectar estilos de impresión
b.Evaluate(ctx, `
const style = document.createElement('style');
style.textContent = '@media print { .sidebar { display: none; } }';
document.head.appendChild(style);
`)
// Generar PDF
pdf, _ := b.GeneratePDF(ctx, opts)
Casos de uso avanzados
Generar reporte con múltiples páginas
// Navegar a página de reporte
b.Navigate(ctx, "https://example.com/report", nil)
// Esperar a que cargue completamente
b.WaitForSelector(ctx, ".report-ready", nil)
// Generar PDF
opts := &browser.PDFOptions{
Format: browser.PDFFormatA4,
PrintBackground: true,
DisplayHeaderFooter: true,
HeaderTemplate: `<div style="font-size: 10px; text-align: right; width: 100%; padding-right: 1cm;">
Reporte generado: <span class="date"></span>
</div>`,
FooterTemplate: `<div style="font-size: 10px; text-align: center; width: 100%;">
<span class="pageNumber"></span> / <span class="totalPages"></span>
</div>`,
}
b.SavePDF(ctx, "reporte.pdf", opts)
PDF con contenido dinámico
// Generar contenido dinámico
b.Evaluate(ctx, `
document.body.innerHTML = '<h1>Reporte Dinámico</h1>';
for (let i = 1; i <= 10; i++) {
document.body.innerHTML += '<p>Elemento ' + i + '</p>';
}
`)
// Generar PDF
pdf, _ := b.GeneratePDF(ctx, nil)
Batch PDF generation
urls := []string{
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3",
}
for i, url := range urls {
b.Navigate(ctx, url, nil)
b.WaitForNavigation(ctx, nil)
filename := fmt.Sprintf("page_%d.pdf", i+1)
b.SavePDF(ctx, filename, nil)
}
Consideraciones
Tamaño del PDF
- PDFs grandes pueden exceder límite de respuesta CDP
- Usar streaming para PDFs > 10MB (no implementado en v1)
Performance
- Generación de PDF es bloqueante
- Puede tomar varios segundos para páginas grandes
- Considerar timeout apropiado
Calidad
- Images embebidas mantienen su resolución
- Fonts pueden no incluirse (usar web fonts)
- JavaScript no se ejecuta durante generación
Headless mode
- PDF generation funciona mejor en headless
- Algunas páginas pueden requerir modo visible
Referencias
- CDP Page.printToPDF: https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
- Chrome printing: https://developer.chrome.com/docs/chromium/print-previews
- Playwright PDF: https://playwright.dev/docs/api/class-page#page-pdf
- Puppeteer PDF: https://pptr.dev/api/puppeteer.page.pdf