Impresión y salida en PDF en aplicaciones empresariales de Windows ── cómo elegir entre System.Drawing.Printing, WPF y bibliotecas de informes

· Actualizado el: · · CSharp, .NET, WinForms, WPF, Impresión, PDF, Informes, Desarrollo de Windows, Consultoría técnica

Historial de revisiones (1 actualizaciones, última el 22 Aug 2026)

Registro de los cambios realizados en este artículo. Cuando se archivó una versión previa, sigue siendo legible mediante un enlace permanente con DOI.

Se ha sustituido la traducción, que estaba abreviada, por una traducción completa del artículo japonés en su versión actual: el texto crece un 62 %. Se han incorporado 3 apartados, 22 filas de tabla, 2 notas al pie, 3 bloques de código y 2 diagramas que no estaban en la edición anterior. El contenido no cambia respecto al original japonés; esta edición simplemente ya no lo resume. Además, los enlaces a otros artículos que ya tienen edición en español apuntan ahora a esa edición en lugar de a la japonesa. Leer la versión anterior a esta actualización (DOI: 10.5281/zenodo.21638277)
Primera publicación
Citar este artículo(DOI: 10.5281/zenodo.21638276)

Este artículo está archivado en Zenodo. A continuación se muestran tanto el DOI que siempre resuelve a la última versión como el DOI fijado a la versión que está leyendo.

Go Komura (2026). Impresión y salida en PDF en aplicaciones empresariales de Windows ── cómo elegir entre System.Drawing.Printing, WPF y bibliotecas de informes. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21638276 https://comcomponent.com/es/blog/windows-app-printing-pdf-guide/

DOI (última versión)
10.5281/zenodo.21638276
DOI (esta versión)
10.5281/zenodo.22053438

La impresión en una aplicación empresarial de Windows no se resuelve con «dibujar de cualquier manera con PrintDocument y ya está». Que el salto de página no funcione, que el margen se desplace, que la vista previa no coincida con la salida real, o que se interponga el cuadro de diálogo de impresión cuando solo se quería un PDF: estos problemas suelen deberse a haber empezado a implementar sin entender correctamente el propio mecanismo de impresión.

Este artículo organiza las siguientes cuatro opciones desde el punto de vista de cómo elegir entre ellas según los requisitos. En adelante se hará referencia a ellas por este número.

# Opción Capítulo principal
System.Drawing.Printing de WinForms (PrintDocument) Capítulo 3
FlowDocument / FixedDocument de WPF Capítulo 4
Generación directa con una biblioteca de salida PDF Capítulo 5
Informes vía Excel/Word (Open XML SDK, automatización COM) Capítulo 6

1. Conclusión primero

  • Si se trata de un listado sencillo o de una extensión de recursos WinForms existentes, PrintDocument es suficiente. El salto de página funciona invocando repetidamente el evento PrintPage hasta que HasMorePages pasa a false; si se olvida este mecanismo, no se genera la segunda página ni las siguientes.12
  • El DPI de impresión es distinto del DPI de pantalla. PageSettings.PrinterResolution representa la resolución de la impresora, y el margen tiene una estructura en dos niveles: PageSettings.HardMarginX/HardMarginY (la zona física no imprimible que tiene la impresora) y Margins (el margen lógico que especifica la aplicación). Si se reutilizan directamente las coordenadas de pantalla para la impresión, el diseño se desplaza debido a esta diferencia de DPI y de sistema de coordenadas.345
  • En WPF, la ruta de impresión estándar de Windows se divide en dos sistemas: la ruta de impresión GDI y la ruta de impresión XPS. Las aplicaciones WPF usan originalmente la ruta de impresión XPS, y cuando se envían a una impresora sin compatibilidad XPSDrv, se convierten automáticamente al formato GDI.67
  • Los documentos de WPF tienen una filosofía de diseño distinta según se trate de FlowDocument (contenido fluido) o FixedDocument (diseño fijo). FlowDocument prioriza la legibilidad y se recompone según el tamaño de ventana y la resolución, mientras que FixedDocument prioriza la fidelidad respecto al dispositivo de visualización o impresión.89
  • El tamaño de papel y la bandeja se especifican con PrintDialog y PrintTicket/PrintQueue de WPF. Antes de imprimir, lo habitual es validar y combinar la configuración con PrintQueue.MergeAndValidatePrintTicket frente a las capacidades reales de la impresora.1011
  • Microsoft Print to PDF es un controlador de impresión pensado para el uso interactivo. Se presenta como una cola de impresión y un paquete de controladores incluidos de serie en el sistema operativo, con un diseño básico en el que cada ejecución muestra un cuadro de diálogo para elegir el destino de guardado, por lo que no es adecuado para el procesamiento por lotes desatendido.12
  • La automatización COM de Office desde un servidor o un servicio no está oficialmente admitida ni recomendada por Microsoft. Office está diseñado partiendo de un escritorio interactivo y un perfil de usuario, por lo que en entornos desatendidos y no interactivos puede provocar un comportamiento inestable o interbloqueos (deadlocks).13
  • Los servicios de Windows se ejecutan en la sesión 0, por lo que no pueden mostrar cuadros de diálogo ni depender de la impresora predeterminada asociada a la sesión de un usuario. Al diseñar un proceso en segundo plano que implique impresión, tenga presente este límite desde el principio.14
  • Si en una ejecución prolongada se olvida liberar los objetos GDI (identificadores), se alcanza el límite por proceso y la aplicación se bloquea. Ese límite es un valor finito, ajustable mediante el valor de registro GDIProcessHandleQuota, y no se pueden reservar identificadores de forma ilimitada.15

Tabla de decisión ── requisitos × medios

Requisito Medio adecuado Opción Motivo
Imprimir un listado sencillo o unas pocas hojas de comprobante (WinForms) PrintDocument Se resuelve solo con clases estándar y permite reutilizar directamente los recursos de dibujo GDI+ existentes
Informe con muchas líneas de cuadrícula, varios tamaños de papel o diseño complejo Biblioteca de informes, o diseño fijo con FixedDocument ② o ③ El coste de programar a mano el cálculo de coordenadas, el control de saltos de página y la composición tipográfica japonesa es alto
Impresión masiva por lotes (ejecución nocturna desatendida) Llamada programática a PrintDocument.Print() o envío directo a XpsDocumentWriter ① o ② Debe completarse sin mostrar ningún cuadro de diálogo, y conviene evitar la dependencia de la impresora predeterminada
Solo se necesita guardar en PDF (sin usar la impresión) Generar el PDF directamente con una biblioteca de generación de PDF No depende del cuadro de diálogo de impresión, de la impresora predeterminada ni del estado del spooler
La vista previa es obligatoria y el flujo de revisión interno es importante PrintPreviewDialog (WinForms), DocumentViewer (WPF) ① o ② Se puede usar directamente la interfaz estándar de confirmación previa a la impresión
Se quiere reutilizar una plantilla de Excel existente Generación directa con Open XML SDK u otra herramienta similar, o automatización COM limitada Permite reutilizar los recursos existentes, pero debe evitarse la automatización COM en un servicio en segundo plano

A continuación se detalla cada uno de estos puntos.

2. Lo mínimo imprescindible sobre el mecanismo de impresión de Windows

Antes de continuar, se definen los términos que se usarán a partir de este capítulo.

Término Significado
DPI (dots per inch) Número de puntos por pulgada. Es la unidad de resolución. La pantalla suele rondar los 96 DPI, mientras que la impresora ronda los 600 DPI: un orden de magnitud distinto
Spooler Servicio de Windows que retiene temporalmente los trabajos de impresión recibidos de la aplicación y los va entregando en orden al controlador de la impresora. Permite que la aplicación siga adelante aunque la impresora esté detenida
EMF (metarchivo mejorado) Formato de archivo que registra las instrucciones de dibujo de GDI. En la ruta de impresión GDI, se entrega al spooler en este formato
WYSIWYG (What You See Is What You Get) Estado en el que lo que se ve en pantalla se genera tal cual en la salida. En impresión, se refiere a que «la vista previa coincide con el papel impreso real»
Sesión 0 A partir de Windows Vista, la sesión reservada exclusivamente para procesos no vinculados a un usuario interactivo, como los servicios. Está separada de la pantalla del usuario que ha iniciado sesión14
Margen físico (hard margin) Banda en el borde del papel donde la impresora no puede imprimir físicamente. La aplicación no puede reducirla

La arquitectura de impresión de Windows está formada por el spooler de impresión y los controladores de cada impresora. La aplicación solo necesita llamar a funciones independientes del dispositivo para poder enviar trabajos de impresión a destinos tan variados como impresoras láser, trazadores vectoriales, impresoras ráster o faxes.16

Existen, a grandes rasgos, dos sistemas de ruta de impresión.

  • Ruta de impresión GDI. Cuando una aplicación GDI de Win32 imprime, el motor gráfico GDI encola en el spooler las instrucciones de dibujo como EMF (metarchivo mejorado), o bien renderiza directamente la imagen imprimible en colaboración con el controlador de la impresora y la envía al spooler. Las aplicaciones WinForms tradicionales pasan por esta ruta.166
  • Ruta de impresión XPS. Es la ruta dirigida a los controladores de impresora basados en XPS (XML Paper Specification); las aplicaciones WPF la usan originalmente y envían al spooler en formato de documento XPS. Si la impresora de destino no tiene un controlador XPSDrv, se convierte automáticamente al formato GDI antes de enviarse.67

En forma de diagrama queda así. Sea cual sea la ruta que se siga, ambas terminan pasando por el spooler y el controlador.

Impresora compatible con XPSDrvImpresora no compatible con XPSDrvAplicación WinFormsOpción ① PrintDocumentRuta de impresión GDIEncola las instrucciones de dibujo como EMFAplicación WPFOpción ② FlowDocument y FixedDocumentRuta de impresión XPSEncola como documento XPSSpooler de impresiónConversión automática a formato GDIControlador de la impresoraImpresora, faxMicrosoft Print to PDF, etc.

Figura 1: Ruta de impresión GDI y ruta de impresión XPS. Al enviar desde WPF a una impresora sin compatibilidad XPSDrv, se convierte al formato GDI por el camino

Por qué el aspecto en pantalla y el resultado impreso no coinciden. La causa más habitual es confundir el DPI. El Graphics de pantalla suele tener un sistema de coordenadas de alrededor de 96 DPI, mientras que el Graphics de la impresora funciona con una resolución mucho más alta, determinada por PageSettings.PrinterResolution y que en muchos casos ronda los 600 DPI.3 Si se pasan directamente al Graphics de impresión las coordenadas de píxel calculadas para la pantalla, el dibujo sale mucho más pequeño de lo previsto (o más grande, según la configuración de alta resolución). En impresión, lo más seguro es construir siempre las coordenadas a partir de unidades de 100 de pulgada (hundredths of an inch) o de medidas reales (milímetros o pulgadas), con un diseño que no dependa de la resolución del Graphics.

Otro punto que se pasa por alto fácilmente es la zona físicamente no imprimible que tiene la impresora. La mayoría de las impresoras no pueden imprimir en una franja de unos pocos milímetros desde el borde del papel. Esta restricción física se obtiene con PageSettings.HardMarginX/HardMarginY, y la documentación oficial la describe como «el margen físico que establece la impresora».4 El margen lógico que especifica la aplicación es PageSettings.Margins (1 pulgada arriba, abajo, a la izquierda y a la derecha por defecto), pero incluso si se fija en 0, no se puede imprimir más adentro que el margen físico de la impresora.5 Muchos de los problemas del tipo «puse el margen a 0 y aun así se corta el borde, o se desplaza más de lo esperado» se deben a no tener en cuenta la existencia de este margen físico.

Lo importante aquí es que estos dos valores se determinan por separado. El margen físico lo decide la impresora, y MarginBounds se calcula únicamente a partir de los Margins que especifica la aplicación. MarginBounds nunca se ajusta automáticamente para caber dentro de la zona imprimible. Si se representa la relación en un diagrama, no queda anidada, sino así:

PageBounds ── tamaño total del papelZona imprimiblePrintableArea, HardMarginX, HardMarginYLa decide la impresora. La aplicación no puede reducirlaMarginBoundsSe calcula solo a partir de MarginsNo se ajusta a la zona imprimibleZona garantizada ── donde ambas se superponen

Figura 2: MarginBounds y el margen físico se determinan por separado. Si Margins es menor que el margen físico, MarginBounds sobresale de la zona imprimible

  • PageBounds: el tamaño total del papel.
  • Margen físico: la zona físicamente no imprimible que determina la impresora. Se obtiene con HardMarginX / HardMarginY y la aplicación no puede reducirla.4 La zona imprimible en sí se obtiene con PageSettings.PrintableArea (en unidades de 100 de pulgada).17
  • MarginBounds: el área de dibujo que refleja los Margins especificados por la aplicación (1 pulgada arriba, abajo, a la izquierda y a la derecha por defecto).5

Es decir, como ocurre al poner Margins en 0, cuando el margen lógico es menor que el margen físico, MarginBounds sobresale de la zona imprimible. Si en ese estado se dibuja tomando MarginBounds como referencia, el contenido de la franja que sobresale no se imprime y se pierde. Ese es el origen real del problema de «puse el margen a 0 y aun así se corta el borde».

Para quedarse del lado seguro, se puede optar por una de estas dos vías.

  • Mantener el margen lógico igual o mayor que el margen físico. Compare cada lado de Margins con HardMarginX / HardMarginY y aumente el que sea menor
  • Calcular el rectángulo de dibujo cruzándolo con PrintableArea. En vez de usar MarginBounds directamente, obtenga la intersección con la zona imprimible mediante Rectangle.Intersect
// Dentro del evento PrintPage. Todas las unidades están en centésimas de pulgada
private void OnPrintPage(object? sender, PrintPageEventArgs e)
{
    PageSettings page = e.PageSettings;

    // La "zona imprimible" vista desde la esquina superior izquierda del papel. Esa esquina es justamente el margen físico
    var printableOnPaper = new RectangleF(
        page.HardMarginX, page.HardMarginY,
        page.PrintableArea.Width, page.PrintableArea.Height);

    // MarginBounds es un valor calculado a partir de Margins y no se ajusta a la zona imprimible.
    // Si se toma la intersección, se obtiene una zona que no se recorta ni en modelos con un margen físico grande.
    // Hasta aquí, las coordenadas tienen como origen la esquina superior izquierda del papel
    Rectangle safeOnPaper = Rectangle.Intersect(
        e.MarginBounds, Rectangle.Round(printableOnPaper));

    // Atención aquí. Cuando OriginAtMargins tiene su valor por defecto, false, el origen de e.Graphics
    // está en la "esquina superior izquierda de la zona imprimible", no en la del papel. Si se dibuja
    // tal cual con las coordenadas del papel, todo se desplaza hacia abajo a la derecha lo que mide el margen físico, y el borde derecho e inferior
    // sobresalen de la zona imprimible y se recortan: exactamente el síntoma que este ejemplo intenta evitar.
    // Antes de dibujar, se trasladan a las coordenadas de Graphics
    Rectangle safe = safeOnPaper;
    safe.Offset(
        -(int)Math.Round(page.HardMarginX),
        -(int)Math.Round(page.HardMarginY));

    e.Graphics!.DrawRectangle(Pens.Black, safe);
}

Si se establece OriginAtMargins = true, el origen cambia de posición, así que esta corrección también cambia. Decida primero con cuál de los dos va a dibujar, y luego construya las coordenadas. Si los mezcla, obtendrá un fallo difícil de rastrear que solo se manifiesta al cambiar de modelo de impresora.

También hay que prestar atención al origen de coordenadas. El valor por defecto de PrintDocument.OriginAtMargins es false, y en ese caso el origen de Graphics se sitúa en la esquina superior izquierda de la zona imprimible, y el valor de Margins no interviene en la determinación del origen.18 Si tiene la sensación de que «configuré Margins pero no se desplaza lo esperado» o, al contrario, «se desplaza de más», sospeche primero de esto. Si quiere que el origen quede dentro del margen, indique explícitamente OriginAtMargins = true.

3. WinForms: la práctica de System.Drawing.Printing

La impresión en WinForms se organiza en torno al componente PrintDocument. El flujo básico consiste en crear un PrintDocument, configurar PrinterSettings y DefaultPageSettings, realizar el dibujo real en el evento PrintPage e iniciar la impresión con el método Print().1

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;

public sealed class ReportPrinter
{
    // Datos a imprimir. Se asume una línea de detalle de comprobante por fila
    private readonly IReadOnlyList<string> _lines;
    private int _lineIndex;

    public ReportPrinter(IReadOnlyList<string> lines) => _lines = lines;

    public void Print(string printerName)
    {
        using var document = new PrintDocument();
        document.DocumentName = "Detalle del pedido";
        if (!string.IsNullOrEmpty(printerName))
        {
            document.PrinterSettings.PrinterName = printerName;
        }

        // Esto pasa a false si se especifica un nombre de impresora desconocido (no sirve para detectar que está fuera de línea)
        if (!document.PrinterSettings.IsValid)
        {
            throw new InvalidOperationException(
                $"No se encuentra la impresora especificada: {printerName}");
        }

        _lineIndex = 0;
        document.PrintPage += Document_PrintPage;
        document.Print();
    }

    private void Document_PrintPage(object? sender, PrintPageEventArgs e)
    {
        // MarginBounds es el área de dibujo que refleja el margen lógico (Margins).
        // PageBounds es el papel completo; incluso dibujando dentro del margen físico, se puede recortar
        using var font = new Font("メイリオ", 10);
        float lineHeight = font.GetHeight(e.Graphics);
        float y = e.MarginBounds.Top;

        while (_lineIndex < _lines.Count)
        {
            if (y + lineHeight > e.MarginBounds.Bottom)
            {
                // Hasta aquí llega esta página. Como queda contenido, se pasa a la siguiente
                e.HasMorePages = true;
                return;
            }

            e.Graphics.DrawString(_lines[_lineIndex], font, Brushes.Black,
                e.MarginBounds.Left, y);
            y += lineHeight;
            _lineIndex++;
        }

        // Como ya se dibujaron todas las líneas, esta es la última página
        e.HasMorePages = false;
    }
}

El patrón típico del fallo “no aparece la segunda página” consiste en olvidar poner explícitamente HasMorePages en false (su valor por defecto ya es false), o bien, al contrario, escribir mal la ramificación condicional y salir siempre dejándolo en false. Dado que el evento PrintPage se invoca repetidamente hasta que esta propiedad pasa a false, establézcala solo después de determinar explícitamente si «todavía quedan líneas por dibujar».12 Si quiere reflejar una configuración distinta por página (tamaño de papel, orientación, etc.), también puede aprovechar el evento QueryPageSettings además de PrintPage.

El patrón típico del fallo “el margen se desplaza” consiste en construir las coordenadas tomando como referencia e.Graphics.VisibleClipBounds o e.PageBounds, ignorando MarginBounds (el área que refleja el margen lógico) y HardMarginX/HardMarginY (la restricción física de la impresora).4 Es especialmente propenso a este incidente el caso en que, en la impresora de la máquina de desarrollo, el margen físico es pequeño y por casualidad no aparece el problema, mientras que en otro modelo del cliente el margen físico es mayor y se ven recortadas líneas o elementos. Verificar la impresión en varios modelos es un paso que suele pasarse por alto durante el desarrollo.

Si necesita ofrecer una vista previa, use PrintPreviewDialog y asigne el mismo PrintDocument a la propiedad Document; así puede reutilizar tal cual la lógica del evento PrintPage.1 Si quiere alternar la salida según la resolución de la impresora, obtenga las opciones desde PrinterSettings.PrinterResolutions y asígnelas a PageSettings.PrinterResolution.3

4. WPF: FlowDocument / FixedDocument y PrintDialog

En WPF, la elección entre FlowDocument y FixedDocument como eje depende de la naturaleza del documento.

  • FlowDocument. Es un documento que prioriza la legibilidad y recompone dinámicamente su contenido en función de variables de tiempo de ejecución, como el tamaño de la ventana o la configuración de fuente. Incluye de forma estándar funciones como búsqueda, paginación y cambio de modo de visualización.8
  • FixedDocument. Es un documento WYSIWYG que prioriza la fidelidad, en el que la aplicación controla por completo el diseño. Resulta adecuado cuando se necesita una reproducción de alta precisión en el dispositivo de visualización o impresión.9

El criterio básico es: FlowDocument para un informe interno donde prima la legibilidad de un contenido fluido, y FixedDocument para un comprobante o etiqueta en el que se quiere fijar al píxel la posición de las líneas de cuadrícula y de los elementos.

La impresión básica se realiza con System.Windows.Controls.PrintDialog. Esta clase es distinta de System.Windows.Forms.PrintDialog y es exclusiva de WPF.10

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

public static class FlowDocumentPrinter
{
    public static void Print(FlowDocument document, string jobName)
    {
        var printDialog = new PrintDialog();

        // Solo se imprime si el usuario pulsa Aceptar
        if (printDialog.ShowDialog() != true)
        {
            return;
        }

        // Como FlowDocument asume tamaños de página distintos para pantalla e impresión,
        // se ajusta el tamaño de página y el margen del documento a imprimir a la zona imprimible de la impresora
        var paginator = ((IDocumentPaginatorSource)document).DocumentPaginator;
        paginator.PageSize = new Size(
            printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);

        printDialog.PrintDocument(paginator, jobName);
    }
}

Cuando se quiere especificar con detalle el tamaño de papel, la bandeja de alimentación, la impresión a doble cara, etc., se usan PrintQueue y PrintTicket/PrintCapabilities. PrintTicket representa las instrucciones para el trabajo de impresión y PrintCapabilities representa las funciones que realmente admite la impresora; lo habitual es combinar y validar la configuración especificada con PrintQueue.MergeAndValidatePrintTicket para obtener valores adecuados propios de cada impresora antes de usarlos.711

using System.Linq;
using System.Printing;

public static class DuplexPrintTicketBuilder
{
    public static PrintTicket BuildDuplexTicket(PrintQueue printQueue)
    {
        PrintCapabilities capabilities = printQueue.GetPrintCapabilities();

        // Se comprueba de antemano si la impresora admite la impresión a doble cara (encuadernado por el lado largo)
        bool supportsDuplex = capabilities.DuplexingCapability
            .Contains(Duplexing.TwoSidedLongEdge);

        var requestedTicket = new PrintTicket();
        if (supportsDuplex)
        {
            requestedTicket.Duplexing = Duplexing.TwoSidedLongEdge;
        }

        // Se combina únicamente la solicitud de doble cara con el PrintTicket predeterminado del usuario.
        // Los elementos no admitidos se sustituyen automáticamente por valores válidos
        ValidationResult result = printQueue.MergeAndValidatePrintTicket(
            printQueue.UserPrintTicket, requestedTicket);

        return result.ValidatedPrintTicket;
    }
}

Cuando se compone un informe de diseño fijo con FixedDocument y se envía directamente a la ruta de impresión XPS, se añade el trabajo a PrintQueue con los métodos Write/WriteAsync de XpsDocumentWriter. Si el destino es una impresora sin compatibilidad XPSDrv, la conversión al formato GDI se realiza automáticamente por dentro antes del envío, por lo que quien hace la llamada no necesita preocuparse por la ruta.7

5. Alternativas de salida en PDF

El requisito de «quiero generar un PDF» en realidad puede referirse a una de estas tres situaciones, y el diseño cambia según cuál sea.

  1. Basta con que, como extensión de la impresión, el usuario pueda elegir manualmente guardar en PDF
  2. Se quiere generar el PDF desde dentro de la aplicación, a través del cuadro de diálogo de impresión
  3. No se imprime en absoluto: se quiere generar en masa solo archivos PDF mediante un proceso por lotes desatendido

Microsoft Print to PDF es una función pensada para los casos 1 y 2. Es una función incluida de serie desde Windows 10, con la consideración de una cola de impresión y un paquete de controladores, que la aplicación que imprime ve como una impresora normal más.12 Sin embargo, en la práctica es un mecanismo que presupone un comportamiento interactivo: «preguntar el nombre del archivo de destino cada vez que se imprime». Incluso de forma oficial, el único medio de control que se ofrece es que un administrador elimine de la imagen la cola de impresión y ese paquete de controladores como unidad.12 Por diseño, no es apto para el uso 3: generar PDF en silencio con una ruta de archivo fija en un proceso por lotes desatendido y masivo. Si se quiere lograr el uso 3, lo natural es usar una biblioteca que genere el PDF directamente, sin pasar por la canalización de impresión.

Los criterios de comparación al elegir una biblioteca de generación de PDF son los siguientes; antes de decantarse por un producto concreto, organizar los requisitos propios según estos ejes evita que la selección se tambalee.

Criterio Qué comprobar
Licencia Si es de código abierto (de tipo MIT/Apache, o de tipo GPL/AGPL) o una licencia comercial. Si la aplicación propia se va a distribuir o vender fuera de la empresa, compruebe siempre con la fuente primaria (el propio texto de la licencia, el sitio oficial del proveedor) que las condiciones de la licencia de la biblioteca (obligación o no de divulgar el código fuente, condiciones de redistribución, posibilidad de uso comercial) no entran en conflicto con la forma de distribución de su empresa
Tratamiento de fuentes japonesas Si admite la incrustación de subconjuntos de fuentes japonesas, si el propio PDF puede contener la fuente sin incrustarla (para que no se corrompan los caracteres si el entorno de lectura no tiene la fuente), y, si el negocio necesita escritura vertical o variantes de glifo (IVS), en qué estado está esa compatibilidad
Funciones orientadas a informes Si admite la definición de informes basada en plantillas, la generación de códigos de barras y códigos 2D, la firma electrónica, la compatibilidad con PDF/A (norma de conservación a largo plazo), y si resulta fácil trasladar tal cual el diseño de un formulario en papel existente
Dependencias y entorno de ejecución Si funciona también en un entorno de servidor (Windows Server sin GUI, contenedores), si depende de bibliotecas nativas, y su historial de funcionamiento en entornos .NET Core/.NET (no Framework)
Rendimiento El uso de memoria al generar por lotes un gran número de páginas o trabajos, y el comportamiento en la generación en paralelo

Como las condiciones de licencia varían según el producto y la versión, confirme siempre la documentación oficial del proveedor o el propio texto de la licencia antes de decidir su adopción. Este artículo no recomienda ningún nombre de biblioteca ni producto concreto; se limita a mostrar los «criterios» de comparación y dónde comprobar esa información.

El procedimiento para buscar y comparar candidatos es el siguiente. Buscar solo con los criterios en mente puede dar conclusiones distintas según la fecha en que se escribió el artículo consultado, así que lo más fiable es fijar de antemano el orden en que se consultan las fuentes primarias.

Etapa Dónde mirar Qué mirar en concreto
1. Enumerar candidatos Etiqueta pdf de NuGet Gallery, búsqueda por Categories (Tags) El orden de magnitud del número de descargas. Si difiere en dos o más órdenes de magnitud, es significativo como diferencia de adopción real
2. Comprobar si sigue vivo «Version History» de la página del paquete en NuGet Si ha habido alguna versión en el último año, y si el intervalo entre versiones no es demasiado largo
3. Observar el desarrollo real Repositorio de GitHub (enlace «Source repository» de la página de NuGet) La fecha del último commit, el grado de acumulación de issues abiertos, y si el mantenedor responde a los issues
4. Comprobar la compatibilidad con .NET Campo «Frameworks» de la página de NuGet Si incluye el destino que usa su empresa, como net8.0. Un paquete que solo tiene net472 no se puede usar en .NET (de la línea Core)
5. Comprobar la licencia El propio texto al que apunta el enlace «License» de la página de NuGet Si permite la distribución comercial, si obliga a divulgar el código fuente. Lea el texto legal, no artículos que lo resumen
6. Probarlo en la práctica Un pequeño proyecto de verificación La incrustación de fuentes japonesas, y el tiempo de generación y el uso de memoria con el número de páginas previsto

Los puntos 4 y 5 son los que más fácilmente se pasan por alto. Decidirse tras leer un artículo de presentación y descubrir después que «solo era compatible con .NET Framework» o «era AGPL y no se pudo incorporar al producto propio» supone tirar por la borda todo el esfuerzo de verificación. Pasar primero solo por 4 y 5, antes de reducir los candidatos a uno solo, reduce el retrabajo.

6. La alternativa de los informes vía Excel o Word

Cuando ya existe un informe que se gestiona con una plantilla de Excel, a veces resulta más realista aprovechar los recursos existentes que rehacerlo todo desde cero. Hay, a grandes rasgos, dos enfoques.

  • Generar y editar directamente xlsx/docx con Open XML SDK u otra herramienta similar. Al no iniciar el ejecutable de Excel/Word y leer y escribir directamente el formato de archivo (ZIP + XML), funciona de forma estable incluso en un entorno de servidor. Encaja bien con una configuración que aprovecha tal cual las líneas de cuadrícula y el formato de la plantilla e inserta solo los valores; los detalles están organizados en «Cómo construir la salida de informes de Excel: COM, Open XML y plantillas».
  • Manipular directamente Excel.Application con Excel COM Interop. Se elige cuando se quiere reutilizar tal cual un libro de Excel existente que implica macros o recálculos complejos, pero es propenso al problema de que el proceso EXCEL.EXE queda residente por no liberar las referencias COM; las medidas y los criterios de decisión se tratan en «El problema de que EXCEL.EXE queda residente al manipular Excel desde C#».

Lo importante aquí es que Microsoft declara oficialmente que la automatización COM de Office en el lado del servidor o en un servicio de Windows no está recomendada ni admitida. Las aplicaciones de Office están diseñadas partiendo de un escritorio interactivo y del perfil de un usuario con la sesión iniciada, y se indica que, si se ejecutan en un entorno desatendido y no interactivo, pueden provocar un comportamiento inestable o interbloqueos.13 Tampoco desde el punto de vista de la licencia está previsto en el EULA habitual el uso de una automatización del lado del servidor para ofrecer funciones de Office a clientes.13

Por tanto, si quiere incorporar la salida de informes a un servicio en segundo plano o a un trabajo por lotes, incluso si reutiliza una plantilla de Excel existente, lo más seguro es evitar una configuración que realmente inicie Excel.exe en tiempo de ejecución (COM Interop) y decantarse por la generación directa de archivos con Open XML SDK u otra herramienta similar. Si de verdad necesita COM Interop, una separación realista consiste en configurarlo como una aplicación residente (no un servicio) que se ejecute en una sesión de usuario con inicio de sesión interactivo.

7. Trampas de la práctica

Impresión desde un servicio de Windows y la sesión 0

No son pocas las consultas sobre incorporar el procesamiento de impresión a un servicio en segundo plano, pero los servicios de Windows se ejecutan en la sesión 0. La sesión 0 es, a partir de Windows Vista, la sesión reservada exclusivamente para servicios y procesos no vinculados a un usuario interactivo, y está separada de la sesión interactiva del usuario.14 Debido a esta separación, aunque el servicio muestre un cuadro de diálogo, el usuario no lo ve, y si se escribe código que espera la entrada del usuario (como esperar a que se muestre el cuadro de diálogo de impresión), da la impresión de que se ha quedado colgado.14

Otra trampa práctica habitual es cómo se ven las impresoras de red o las impresoras asignadas individualmente a cada usuario. Una impresora de red conectada tras el inicio de sesión de un usuario suele verse vinculada a la sesión interactiva de ese usuario, y no se ve de la misma forma desde otra sesión (el contexto de proceso de un servicio que se ejecuta en la sesión 0). En un proceso en segundo plano que implique impresión, es necesario confirmar desde el principio del diseño «desde qué cuenta y qué sesión se ve cada impresora». La visión general de la separación de sesiones está organizada en «Cómo entender la separación de sesiones de Windows», y los fundamentos de la implementación y la operación como servicio, en «Cómo crear y operar un servicio de Windows».

Comportamiento cuando la impresora no está o está fuera de línea

Si se especifica en PrinterSettings.PrinterName un nombre de impresora que no existe, o se ejecuta en un entorno sin impresora predeterminada configurada, se produce una excepción o un fallo en el momento de intentar imprimir. Como mínimo conviene cubrir dos puntos: validar de antemano con PrinterSettings.IsValid, y diseñar reintentos y notificaciones de error previendo el fallo de impresión. Cuando una impresora de red está temporalmente fuera de línea, el trabajo puede quedar acumulado en el spooler mientras la salida real permanece detenida, así que no dé por concluido el resultado de la impresión con un simple «se envió»; considere también, si hace falta, un mecanismo para comprobar el estado del trabajo.

El riesgo de depender de la impresora predeterminada

Una implementación que no especifica explícitamente PrinterSettings.PrinterName y siempre depende de la «impresora predeterminada» se vuelve más arriesgada cuanto más tiempo lleva en operación, porque cambios en el entorno —como que el usuario cambie la impresora predeterminada, o que se lleve el portátil a otro lugar y cambie la predeterminada— hacen que la salida vaya a un destino no deseado. Para los informes importantes para el negocio, conviene mantener explícitamente el nombre de la impresora de destino en un archivo de configuración o en una pantalla de ajustes de la aplicación, con un diseño que no se vea arrastrado por los cambios en la impresora predeterminada.

Fugas de identificadores GDI en ejecuciones prolongadas

Si en el procesamiento de impresión se escribe código que no libera con seguridad, mediante using o Dispose(), recursos GDI/GDI+ como Graphics, Font, Brush o Pen, tras un funcionamiento prolongado se agotan los identificadores de objetos GDI y las llamadas a la API relacionadas con el dibujo empiezan a fallar. Los identificadores de objetos GDI tienen un límite por defecto por proceso, un recurso finito que se puede ajustar entre 256 y 65.536 mediante el valor de registro GDIProcessHandleQuota.15 Cuanto más frecuentemente imprime una aplicación residente, más tiende este tipo de fuga a convertirse en un fallo que solo se manifiesta en producción. La forma de investigar y aislar las fugas de identificadores se explica en detalle, a partir de un caso de bloqueo tras una ejecución prolongada de una cámara industrial, en «Investigación de un bloqueo tras una ejecución prolongada de una cámara industrial: la parte de las fugas de identificadores».

8. Resumen

Árbol de decisión ── cuál de las cuatro opciones elegir

Las opciones ①-④ mencionadas al principio se pueden acotar planteando las preguntas en el siguiente orden.

  1. ¿Realmente hace falta imprimir? Si basta con que el resultado sea un archivo PDF y la salida en papel queda a discreción del usuario, lo más natural es ③ (generación directa con una biblioteca PDF), que no pasa por la canalización de impresión. Microsoft Print to PDF presupone un comportamiento interactivo que pregunta el destino de guardado, y no es apto para un proceso por lotes desatendido (capítulo 5).
  2. ¿Se quiere reutilizar una plantilla de Excel/Word existente? En ese caso, . Ahora bien, como Microsoft declara explícitamente que no admite la automatización COM desde un servicio en segundo plano o un proceso por lotes, conviene decantarse por la generación directa con Open XML SDK u otra herramienta similar (capítulo 6).
  3. ¿La interfaz es WinForms o WPF? Ajústese al framework de la aplicación existente. Si es WinForms, ; si es WPF, . No es necesario mezclar tecnologías (capítulos 3 y 4).
  4. Si eligió WPF, ¿contenido fluido o diseño fijo? FlowDocument para un informe interno donde prima la legibilidad, y FixedDocument para un comprobante o etiqueta en el que se quiere fijar la posición de las líneas de cuadrícula (capítulo 4).

Qué confirmar antes de implementar

  • Decidir primero la unidad y el sistema de coordenadas. No reutilice tal cual las coordenadas de píxel de pantalla en la impresión. Tome como referencia las unidades de 100 de pulgada o las medidas reales (milímetros o pulgadas) (capítulo 2).
  • Entender que el margen tiene una estructura en dos niveles. Dibuje tomando como referencia MarginBounds, no PageBounds. Aunque ponga Margins en 0, solo se puede imprimir dentro del margen físico (capítulo 2, figura 2).
  • Establecer HasMorePages de forma explícita. Configúrelo solo después de determinar si «todavía quedan líneas por dibujar». Su valor por defecto es false, y olvidarlo es la causa típica de que «no aparezca la segunda página» (capítulo 3).
  • Mantener explícitamente la impresora de destino. No dependa de la impresora predeterminada; guarde el nombre de la impresora en un archivo de configuración o en la pantalla de ajustes de la aplicación (capítulo 7).
  • Liberar siempre los recursos GDI con using. Las fugas de Font, Brush o Pen solo se manifiestan en un entorno de producción con funcionamiento prolongado (capítulo 7).
  • Confirmar primero los límites al imprimir desde un servicio. En la sesión 0 no se pueden mostrar cuadros de diálogo, y tampoco se ven las impresoras de red vinculadas al usuario (capítulo 7).
  • Verificar con varios modelos. El tamaño del margen físico varía según el modelo, así que aunque no haya problemas en la máquina de desarrollo, en el cliente pueden faltar líneas de cuadrícula (capítulo 3).

Artículos relacionados

Áreas de consultoría relacionadas

KomuraSoft LLC ofrece asesoría técnica sobre el diseño de funciones de impresión y salida de informes en aplicaciones empresariales de Windows, el rediseño de informes aprovechando los recursos de Excel existentes, y el diseño de impresión y salida desde servicios en segundo plano.

Referencias

  1. Microsoft Learn, PrintDocument Class. Sobre el papel de PrintDocument (configurar DocumentName/PrinterSettings e iniciar la impresión con Print()) y la indicación de que, para imprimir desde WPF, se debe usar el espacio de nombres System.Printing 2 3 4

  2. Microsoft Learn, PrintPageEventArgs.HasMorePages Property. Sobre que su valor por defecto es false y que el evento PrintPage se repite hasta que esta propiedad pasa a false 2

  3. Microsoft Learn, PageSettings.PrinterResolution Property. Sobre que el valor por defecto de la resolución de impresión de la página es la resolución predeterminada de la impresora, y que se puede obtener la lista de resoluciones seleccionables desde PrinterSettings.PrinterResolutions 2 3

  4. Microsoft Learn, PageSettings.HardMarginX Property. Sobre que el margen físico representa el margen físico que establece la impresora.  2 3 4

  5. Microsoft Learn, PageSettings.Margins Property. Sobre que el valor por defecto del margen de página es de 1 pulgada arriba, abajo, a la izquierda y a la derecha, y que se puede calcular el área imprimible combinándolo con la propiedad Bounds en el evento PrintPage 2 3

  6. Microsoft Learn, Windows Print Path Overview. Sobre la existencia en Windows de dos rutas de impresión principales: la ruta GDI (procedente de aplicaciones Win32) y la ruta XPS (procedente de aplicaciones WPF o de la API de impresión XPS).  2 3

  7. Microsoft Learn, Printing documents overview. Sobre que las aplicaciones WPF usan la ruta de impresión XPS, la impresión básica con PrintDialog, la impresión avanzada con PrintTicket/PrintCapabilities/PrintQueue/XpsDocumentWriter, y la conversión automática a GDI para impresoras sin XPSDrv.  2 3 4

  8. Microsoft Learn, Flow Document Overview. Sobre que FlowDocument es un documento que prioriza la legibilidad y recompone su contenido según variables de tiempo de ejecución como el tamaño de ventana o la resolución.  2

  9. Microsoft Learn, FixedDocument Class. Sobre que FixedDocument tiene un diseño WYSIWYG que prioriza la reproducción fiel en el dispositivo de visualización o impresión, con una filosofía distinta a la de FlowDocument 2

  10. Microsoft Learn, PrintDialog Class. Sobre que System.Windows.Controls.PrintDialog es un cuadro de diálogo que configura PrintTicket y PrintQueue según la entrada del usuario, y que es una clase distinta de System.Windows.Forms.PrintDialog 2

  11. Microsoft Learn, How to: Validate and Merge PrintTickets. Sobre el procedimiento para comprobar las funciones admitidas por la impresora con PrintQueue.GetPrintCapabilities y combinar y validar la solicitud en un PrintTicket válido y propio de la impresora con MergeAndValidatePrintTicket 2

  12. Microsoft Learn, RemoveMPDW. Sobre que Microsoft Print to PDF es una característica opcional instalada por defecto, y que se configura y elimina como unidad de cola de impresión y paquete de controladores.  2 3

  13. Microsoft Support, Considerations for server-side Automation of Office. Sobre que Microsoft no recomienda ni admite la automatización de Office desde aplicaciones cliente desatendidas y no interactivas, incluidas ASP, ASP.NET, DCOM y servicios NT, y que Office está diseñado partiendo de un escritorio interactivo y un perfil de usuario.  2 3

  14. Microsoft Learn, Service Changes for Windows Vista - Session 0 Isolation. Sobre que, a partir de Windows Vista, la sesión 0 se reserva exclusivamente para los servicios y para las aplicaciones no asociadas a un usuario interactivo, y que los servicios ya no pueden mostrar cuadros de diálogo directamente.  2 3 4

  15. Microsoft Learn, GDI Objects. Sobre que los identificadores de objetos GDI tienen un límite por defecto por proceso, ajustable entre 256 y 65.536 mediante el valor de registro GDIProcessHandleQuota 2

  16. Microsoft Learn, Introduction to printing. Sobre que la arquitectura de impresión de Windows está formada por el spooler y los controladores de impresora, y sobre el mecanismo por el que las instrucciones de dibujo de una aplicación GDI de Win32 se encolan como EMF.  2

  17. Microsoft Learn, PageSettings.PrintableArea Property. Sobre que representa en unidades de 100 de pulgada la zona en la que la impresora puede imprimir, y que con esta propiedad se puede imprimir fuera del margen de página, dentro de la zona imprimible. 

  18. Microsoft Learn, PrintDocument.OriginAtMargins Property. Sobre que su valor por defecto es false y que, en ese caso, solo la zona imprimible se usa para determinar el origen, ignorándose PageSettings.Margins

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Debo usar PrintDocument o la impresión de WPF?
Si la interfaz es WinForms, lo natural es usar PrintDocument; si es WPF, FlowDocument o FixedDocument. No es necesario mezclar tecnologías: el criterio práctico es seguir el framework de pantalla de la aplicación existente. Si opta por WPF en un proyecto nuevo, conviene decidir de antemano si el informe se basa en contenido de flujo continuo o en un diseño fijo, y elegir FlowDocument o FixedDocument en consecuencia para no dudar más adelante.
¿Conviene comprar una biblioteca de informes?
Cuando el coste de construir internamente, con dibujo GDI+/WPF propio, un informe complejo con muchas líneas de cuadrícula, compatibilidad con varios tamaños de papel, incrustación de fuentes japonesas o escritura vertical resulta alto, suele salir más barato adoptar una biblioteca de informes. A la inversa, para un informe sencillo como un listado A4, a menudo basta con una implementación propia sobre PrintDocument o FixedDocument. Lo más seguro es estimar primero la complejidad de los requisitos antes de decidir.
¿Es viable una arquitectura que solo genere PDF y no use la impresión?
Sí, es plenamente viable. El PDF es solo uno de los formatos de salida posibles, y la generación directa con una biblioteca PDF que no dependa del cuadro de diálogo de impresión ni de la impresora predeterminada suele encajar mejor con el procesamiento por lotes y la automatización. Microsoft Print to PDF es un controlador pensado para el uso interactivo, por lo que no es apropiado para generar PDF de forma desatendida.
¿También se pueden admitir impresoras de etiquetas o de recibos?
Es posible, pero puede resultar difícil lograrlo como una simple extensión de PrintDocument o FixedDocument de propósito general. Muchas impresoras de etiquetas tienen su propio lenguaje de comandos o SDK, y a menudo se controlan por una ruta distinta de la de impresión estándar de Windows. Empiece por comprobar si el modelo objetivo se comporta como un controlador de impresora normal de Windows o si requiere control mediante un SDK específico.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog