Generacion de PDF

136 views
Skip to first unread message

Edgar Ramos

unread,
Aug 7, 2015, 12:44:15 PM8/7/15
to altnet-...@googlegroups.com
Gente un saludo, en una app que tengo este momento (wep api + angular), requerimos generar pdfs los cuales mostraran un codigo de barras code-128, alguna idea de como iniciar con esto?
En una anterior winform, al tener crystal report esto fue relativamente sencillo, pero hoy me veo un poco atascado.

Cualquier sugerencia es agradecida, muchas gracias

--
Saludos
Edgar

Diego Jancic

unread,
Aug 7, 2015, 1:06:53 PM8/7/15
to AltNet-Hispano
Buenas,

1) PDFSharp,

2) Te regalo esta clase que hace lo que queres:



    public class LabelsDocument : IDisposable
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(LabelsDocument));

        #region Properties

        #region Layout

        private const int LabelsPerRow = 4;
        private const int LabelsPerColumn = 6;

        public static XUnit PageWidth = XUnit.FromInch(8.5);
        public static XUnit PageHeight = XUnit.FromInch(11);
        public static XUnit MarginTop = XUnit.FromInch(0.5);
        public static XUnit MarginBottom = XUnit.FromInch(0.5);
        public static XUnit MarginLeft = XUnit.FromInch(0.78);
        public static XUnit MarginRight = XUnit.FromInch(0.78);
        public static XUnit MarginX = XUnit.FromInch(0.31);
        public static XUnit MarginY = XUnit.FromInch(0.2);
        public static XUnit LabelSize = XUnit.FromInch(1.5);
        public static XUnit LabelHorizontalPadding = XUnit.FromInch(0.15);
        public static XUnit QrImageSize = XUnit.FromInch(1.2);
        public static XUnit NameLabelWidth = XUnit.FromInch(1.5);
        public static XUnit NameLabelHeight = XUnit.FromInch(0.3);

        #endregion

        private int LabelsCount { get; set; }

        private PdfDocument CurrentDocument { get; set; }

        private PdfPage CurrentPage { get; set; }

        private Point CurrentLabelPosition { get; set; }

        #endregion

        #region Ctor

        public LabelsDocument(string title, string subject, string author)
        {
            CurrentDocument = new PdfDocument();
            CurrentDocument.Info.Title = title;
            CurrentDocument.Info.Subject = subject;
            CurrentDocument.Info.Author = author;
        }

        #endregion

        #region Methods

        public void AddLabel(string qrData, string label)
        {
            using (var qrImage = CreateQr(qrData))
            {
                if (ShouldAddPage())
                    AddPage();

                DrawLabel(qrImage, label);

                if (ShouldStartRow())
                    StartRow();
                else
                    MoveNextLabel();
            }
        }

        public void SaveDocument(string path)
        {
            CurrentDocument.Save(path);
        }

        private bool ShouldAddPage()
        {
            return CurrentPage == null || LabelsCount == LabelsPerRow * LabelsPerColumn;
        }

        private void AddPage()
        {
            if (CurrentDocument == null)
            {
                throw new Exception("Tried to add a new page when there's no document");
            }

            CurrentPage = CurrentDocument.AddPage();
            CurrentPage.Width = PageWidth.Point - MarginLeft.Point - MarginRight.Point;
            CurrentPage.Height = PageHeight.Point - MarginTop.Point - MarginBottom.Point;
            CurrentPage.TrimMargins.Top = MarginTop.Point;
            CurrentPage.TrimMargins.Right = MarginRight.Point;
            CurrentPage.TrimMargins.Bottom = MarginBottom.Point;
            CurrentPage.TrimMargins.Left = MarginLeft.Point;

            LabelsCount = 0;
            CurrentLabelPosition = new Point(0, 0);
        }

        public void DrawNoResultsMessage()
        {
            AddPage();

            using (var gfx = XGraphics.FromPdfPage(CurrentPage))
            {
                //Draw name label...
                var rect = new XRect(CurrentLabelPosition.X, CurrentLabelPosition.Y + QrImageSize.Point, 450, NameLabelHeight.Point);
                var xtf = new XTextFormatter(gfx) { Alignment = XParagraphAlignment.Center };
                xtf.DrawString("There are no label(s) that match the specified criteria",
                               new XFont("sans-serif", 12, XFontStyle.Regular, new XPdfFontOptions(PdfFontEncoding.Unicode)),
                               new XSolidBrush(XColor.FromKnownColor(KnownColor.Black)),
                               rect,
                               XStringFormats.TopLeft);
            }
        }

        private void DrawLabel(Image qrImage, string labelCaption)
        {
            using (var gfx = XGraphics.FromPdfPage(CurrentPage))
            {
                //Draw border...
                gfx.DrawRectangle(new XPen(XColor.FromKnownColor(KnownColor.Black)),
                                  new XSolidBrush(XColor.FromKnownColor(KnownColor.White)),
                                  CurrentLabelPosition.X,
                                  CurrentLabelPosition.Y,
                                  LabelSize.Point,
                                  LabelSize.Point);

                //Draw qr image...
                if (qrImage != null)
                {
                    using (var xImage = XImage.FromGdiPlusImage(qrImage))
                    {
                        gfx.DrawImage(xImage, CurrentLabelPosition.X + LabelHorizontalPadding.Point, CurrentLabelPosition.Y + 1);
                    }
                }

                //PDFSharp does not support Arabic fonts, so we have to hack it...
                if (StringHelper.HasArabicGlyphs(labelCaption))
                {
                    labelCaption = StringHelper.Reverse(labelCaption).Replace(" ", "   ");
                }

                //Draw name label...
                var rect = new XRect(CurrentLabelPosition.X, CurrentLabelPosition.Y + QrImageSize.Point, NameLabelWidth.Point, NameLabelHeight.Point);
                var xtf = new XTextFormatter(gfx) { Alignment = XParagraphAlignment.Center };
                xtf.DrawString(labelCaption,
                               new XFont("sans-serif", 7, XFontStyle.Regular, new XPdfFontOptions(PdfFontEncoding.Unicode)),
                               new XSolidBrush(XColor.FromKnownColor(KnownColor.Black)),
                               rect,
                               XStringFormats.TopLeft);
            }

            LabelsCount++;
        }

        private bool ShouldStartRow()
        {
            return LabelsCount % LabelsPerRow == 0;
        }

        private void StartRow()
        {
            CurrentLabelPosition = new Point(0, CurrentLabelPosition.Y + Convert.ToInt32(Math.Round(LabelSize.Point + MarginY.Point)));
        }

        private void MoveNextLabel()
        {
            CurrentLabelPosition += new Size(Convert.ToInt32(Math.Round(LabelSize.Point + MarginX.Point)), 0);
        }

        private static Image CreateQr(string personNumber)
        {
            Image barcodeImage = null;
            try
            {
                if (!string.IsNullOrEmpty(personNumber))
                {
                    barcodeImage = BarcodeGenerator.GenerateQr(personNumber, GetPixelsFromPoints(Convert.ToInt32(QrImageSize.Point)));
                }
            }
            catch (Exception ex)
            {
                Log.Error("Could not generate qr barcode", ex);
                barcodeImage = null;
            }
            return barcodeImage;
        }

        public const int DotsPerInch = 72;
        public const int PixelsPerInch = 96;

        public static int GetPixelsFromPoints(int points)
        {
            return (int)Math.Round(points * ((double)PixelsPerInch / DotsPerInch));
        }

        #endregion

        public void Dispose()
        {
            CurrentDocument.Dispose();
        }
    }



Y aca esta la que usas para generar el codigo de barras (el ejemplo de arriba usa QR):

using System.Drawing;
using System.Drawing.Imaging;
using com.google.zxing;
using com.google.zxing.oned;
using com.google.zxing.qrcode;

namespace XXXXXXXX
{
    using System;
    using System.IO;
   
    public static class BarcodeGenerator
    {
        public static Image GenerateQr(string text, int pixelSize)
        {
            try
            {
                text = (text ?? "").Trim();
                var writer = new QRCodeWriter();
                var matrix = writer.encode(text, BarcodeFormat.QR_CODE, pixelSize, pixelSize, null);
                var img = new Bitmap(matrix.Width, matrix.Height);
                for (var y = 0; y < matrix.Height; y++)
                {
                    for (var x = 0; x < matrix.Width; x++)
                    {
                        img.SetPixel(x, y, matrix.get_Renamed(x, y) == -1 ? Color.White : Color.Black);
                    }
                }
                return img;
            }
            catch (Exception ex)
            {
                var message = "Unable to generate QR code for text: '" + text + "'";
                throw new InvalidDataException(message, ex);
            }
        }

        public static void GenerateAndSaveQr(string text, string savePath, int pixelSize)
        {
            using (var img = GenerateQr(text, pixelSize))
            {
                img.Save(savePath);
            }
        }

        public static Image Generate39(string text, int pixelWidth, int pixelHeight)
        {
            try
            {
                text = (text ?? "").Trim();
                var writer = new Code39Writer();
                var matrix = writer.encode(text, BarcodeFormat.CODE_39, pixelWidth, pixelHeight, null);
                return matrix.ToBitmap();
            }
            catch (Exception ex)
            {
                var message = "Unable to generate barcode for text: '" + text + "'";
                throw new InvalidDataException(message, ex);
            }
        }

        public static void GenerateAndSave39(string text, string savePath, int pixelWidth, int pixelHeight)
        {
            using (var img = Generate39(text, pixelWidth, pixelHeight))
            {
                img.Save(savePath, ImageFormat.Png);
            }
        }
    }
}




--
Has recibido este mensaje porque estás suscrito al grupo "AltNet-Hispano" de Grupos de Google.
Para anular la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a altnet-hispan...@googlegroups.com.
Para publicar en este grupo, envía un correo electrónico a altnet-...@googlegroups.com.
Visita este grupo en http://groups.google.com/group/altnet-hispano.
Para acceder a más opciones, visita https://groups.google.com/d/optout.

Edgar Ramos

unread,
Aug 7, 2015, 1:14:00 PM8/7/15
to altnet-...@googlegroups.com
Muchas gracias Diego, estoy viendo tambien q generando el pdf en crystal report y enviandolo ya en este formato al cliente se puede mostrar, en teorica claro, no lo he probado todavia
--
Saludos
Edgar

Kiquenet

unread,
Aug 7, 2015, 1:33:04 PM8/7/15
to AltNet-Hispano
Sin duda PdfSharp es una buena alternativa
http://pdfsharp.net/?AspxAutoDetectCookieSupport=1

Otros productos (con licencia) como, PdfLib es potente

O los productos de Aspose para PDF

Leonardo Micheloni

unread,
Aug 7, 2015, 3:46:16 PM8/7/15
to altnet-hispano
Si, en el paso lo he hecho, cristal reports a pdf, funciona....es todo lo que puedo decir..... :)
@leomicheloni Microsoft MVP

Kiquenet

unread,
Sep 9, 2015, 7:49:57 AM9/9/15
to AltNet-Hispano

Hola a todos,

 

Trabajando con ficheros PDF y .NET (C#) no es trivial y hay que llegar al detalle de lo que realmente se quiere implementar. Alternativas hay muchas como librerías ITextsharp o PDfSharp, pasando por Ghostscript, y hasta Crystal Report y ReportViewer de Microsoft.

 

Por si fuera útil , expongo diversas estrategias y experiencias, agradecería también que compartáis comentarios y mismas experiencias.

 

Código HTML a PDF

 

éste de codeproject:

 

http://www.codeproject.com/KB/aspnet/HTML2PDF.aspx

 

http://www.codeproject.com/KB/cs/Easy_PDF_Programming.aspx

 

Luego ya productos de terceros de pago, hay muchos:  

http://www.codeproject.com/KB/showcase/TallComponents.aspx

 

 If you Html is XHtml you can use PDFizer to do the job.

http://sourceforge.net/projects/pdfizer/

 

 

 

 PDF en una aplicación Web ASP.NET 4.0

 

mostrar sólo la primera página de un PDF en ASP.NET

Para generar vistas previas de archivos pdf, podrías utilizar Magick.Net que es un wrapper de ImageMaick para .Net, y existen ejemplos en el sitio de documentación de Magick.Net para hacer lo que necesitas (solamente hay que tener cuidado de tener instalado Ghostscript y que la plataforma en el que instalas Ghostscript sea la misma que la libreria que tenes en tu proyecto x86 o x64).

 

 

Otras opciones: “Generando el PDF en Crystal Report (http://www.gabrielortiz.com/descargas/Manual_Crystal_Reports_XI.pdf) y enviándolo al cliente en ese formato (pdf)”

 

Una alternativa a Crystal es usar ReportViewer de Microsoft, que es menos potente pero tiene la ventaja de que el despliegue es algo más sencillo y el instalador ocupa menos. Tienes más info aquí: http://msdn.microsoft.com/en-us/library/ms251671%28v=vs.100%29.aspx

 

 

Diversas experiencias:

 

MAJimenez

Utiliza un wrapper para la dll de GhostScript, lee los pdf, extrae una imagen de la primera hoja y que te cree el thumbnail y la almacenas en tu filesystem o en una db:

http://www.mattephraim.com/blog/2009/01/06/a-simple-c-wrapper-for-ghostscript/

 

Yo utilizo esta configuracion para crear thumbnails en escala de grises:

 

private static string[] GetArgsSalidaThumbsJpg(string inputPath, string outputPath)

        {

            return new[]

            {

                "PdftoJpg",

                "-q",

                "-dQUIET",

                "-dPARANOIDSAFER",

                "-dBATCH",

                "-dNOPAUSE",

                "-dNOPROMPT",

                "-dMaxBitmap=500000000",

                "-dEPSCrop",

                "-dAlignToPixels=0",

                "-dGridFitTT=0",

                "-sDEVICE=jpeggray",

                "-dTextAlphaBits=4",

                "-dGraphicsAlphaBits=4",

                "-r11",

                String.Format("-sOutputFile={0}", outputPath),

                inputPath

            };

        }

 

Estuve utilizando una clase que servia como wrapper que se llama GhostscriptSharp.cs, nada mas seria que la buscaras en google y la integras en tu codigo.”

 

Walter Poch

 

Lo que yo terminé usando es iTextSharp con templates en PDF, entonces defino campos dinámicos en las templates PDF que luego lleno por código. Algo así:

 

 

 

public virtual void ImprimirComprobante(long turnoId)

 

 

        {

 

 

            var turno = SessionFactory.GetCurrentSession().Load<Turno>(turnoId);

 

 

            //Valido que sea del paciente actual

 

 

            if (turno.Paciente.Id != User.As<Paciente>().Id)

 

 

                throw new SecurityException("El usuario actual no es al que se le otorgó el turno.");

 

 

 

            // Read the template           

 

 

            var reader = new PdfReader(Server.MapPath("~/Reports/ComprobanteTurno.pdf"));

 

 

 

            // Writes the modified template to http response

 

 

            this.HttpContext.Response.Clear();

 

 

            this.HttpContext.Response.ContentType = "application/pdf";

 

 

            var stamper = new PdfStamper(reader, this.HttpContext.Response.OutputStream);

 

 

 

            // Retrieve the PDF form fields defined in the template

 

 

            var form = stamper.AcroFields;

 

 

 

            // Set values for the fields

 

 

            form.SetField("Fecha", turno.FechaTurno.ToString());

 

 

            form.SetField("Profesional", turno.Profesional.Persona.NombreCompleto);

 

 

            form.SetField("Especialidad", turno.Especialidad.Nombre);

 

 

            form.SetField("Paciente", turno.Paciente.Persona.NombreCompleto);

            form.SetField("Consultorio", turno.Consultorio.Nombre);

 

 

 

            // Setting this to true to make the document read-only

            stamper.FormFlattening = true;

 

 

 

            //Close the stamper instance

            stamper.Close();

 

 

        }

 

De esa forma evito tener que "diseñar" con iTextSharp. Pero no sirve para listados más que para la cabecera.”


Claudio Meschini

 

El problema principal que tiene .rdlc para trabajar con los objetos del dominio directamente es que no soporta jerarquias de objetos, entonces para imprimir una factura con detalles tenes que armar un mapper donde aplanes por ejemplo el objeto factura completo.

Este codigo trabaja con una clase ReportInfo que es un dto que usamos para pasarle al servicio los parametros del reporte.

Fijate que hay un thread de la lista donde se discutio un poco el tema, el subject era "Reportes e impresión desde proyecto web asp.net mvc 3". Ahí Leo Micheloni comento sobre un error de .rdlc: 
http://leomicheloni.blogspot.com.ar/2010/05/error-poco-descriptivo-en-microsoft.html

Fijate en este link tambien por el tema de los device info: 
http://msdn2.microsoft.com/en-us/library/ms155397.aspx

Y acá para su uso con MVC: 
http://weblogs.asp.net/rajbk/archive/2009/11/25/rendering-an-rdlc-directly-to-the-response-stream-in-asp-net-mvc.aspx

Un último tema muy importante: para poder usar el editor de .rdlc con MVC tenes que tener en VS20xx abierta una  página .aspx (sic)


using System;
using System.Web;
using System.Collections.Generic;
using Microsoft.Reporting.WebForms;

namespace TuNamespace
{
    public class ServiceReport
    {
        private static List<ReportInfo> _reports = new List<ReportInfo>();
        private static string dirCsv = System.Web.HttpContext.Current.Server.MapPath(@"~\CSV");
        public Dictionary<string, string> typesMime = new Dictionary<string, string>();

        private const string PathReports = "~/Content/Reports/";
        private const string FileExtensionReports = "rdlc";


        public ServiceReport()
        {
            typesMime.Add("pdf", "application/pdf");
            typesMime.Add("csv", "text/csv");
        }

        public byte[] BuildReport(ReportInfo reportInfo, string typeOutput)
        {
            LocalReport localReport = new LocalReport();
            localReport.ReportPath = HttpContext.Current.Server.MapPath(String.Format("{0}{1}.{2}", PathReports, reportInfo.Key, FileExtensionReports));  // "~/Content/Reports/CustomerReport.rdlc");
            ReportDataSource reportDataSource = new ReportDataSource(reportInfo.NameDataSource, reportInfo.DataSource);
            localReport.DataSources.Add(reportDataSource);

            string reportType = typeOutput.ToUpper();
            string mimeType;
            string encoding;
            string fileNameExtension;

            //The DeviceInfo settings should be changed based on the reportType
            //http://msdn2.microsoft.com/en-us/library/ms155397.aspx

            string deviceInfoPdf =
            "<DeviceInfo>" +
            "  <OutputFormat>PDF</OutputFormat>" +
            "  <PageWidth>8.5in</PageWidth>" +
            "  <PageHeight>11in</PageHeight>" +
            "  <MarginTop>0.5in</MarginTop>" +
            "  <MarginLeft>1in</MarginLeft>" +
            "  <MarginRight>1in</MarginRight>" +
            "  <MarginBottom>0.5in</MarginBottom>" +
            "</DeviceInfo>";

            string deviceInfoCsv =
            "<DeviceInfo>" +
            "  <OutputFormat>CSV</OutputFormat>" +
            "<Encoding>UTF-8</Encoding>" +
            "<ExcelMode>true</ExcelMode>" +
            "</DeviceInfo>";

            string deviceInfo;

            if (typeOutput.ToUpper() == "PDF")
                deviceInfo = deviceInfoPdf;
            else
                deviceInfo = deviceInfoCsv;



            Warning[] warnings;
            string[] streams;
            byte[] renderedBytes;

            //Render the report
            return localReport.Render(reportType, deviceInfo, out mimeType, out encoding, out fileNameExtension, out streams, out warnings);
        }
    
    }
}

using System;

namespace TuNamespace
{
    public class ReportInfo
    {
        public ReportInfo(string key, string title, string description, Func<object> getDataSource)
        {
            Key = key;
            Title = title;
            Description = description;
            GetDataSources = getDataSource;
            NameDataSource = key;
        }

        public string Key { get; private set; }

        public string Title { get; private set; }

        public string Description { get; private set; }

        public object DataSource
        {
            get
            {
                return GetDataSources.Invoke();
            }
        }

        public string NameDataSource { get; set; }

        private Func<object> GetDataSources { get; set; }

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (obj.GetType() == typeof(ReportInfo))
                return ((ReportInfo)obj).Key == this.Key;

            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();


        }
    }
}”

 

 

 

Librerías de referencia:

  • ITextSharp - iText is a PDF library that allows you to CREATE, ADAPT, INSPECT and MAINTAIN documents in the Portable Document Format (PDF)[$][Free for OSS]

Una clase helper , buscando en github

https://github.com/zhaojie411/SimplyBrand.SocialMonitor/blob/master/SimplyBrand.SocialMonitor.Business/Report/SimplyReport.cs

 

PDFSharp

http://www.pdfsharp.net/Downloads.ashx

 

“Existe una librería para trabajar con pdf´s liberada bajo la licencia MIT, la librería permite crear y producir pdfs desde código C# a si como otras opciones como rellenar pdfs de la declaración de la renta desde código. Los ejemplos, que os adjunto, son para concatenar varios pdfs, un proyecto los concatena una hoja por folio y el segundo dos hojas por folio.”

 

 

Otros productos (con licencia) como, PdfLib es potente

http://www.pdflib.com/products/

http://www.pdflib.com/products/pdflib-family/pdflib/

 

O los productos de Aspose para PDF

http://www.aspose.com/.net/pdf-component.aspx

 

Proyectos Open Source en C#

 

 

PDF

 

ASP.NET FO PDF

iTextSharp

PDF Clown

PDFsharp

SharpPDF

 

 

 

Espero que alguien le resulte útil.

Walter Poch

unread,
Sep 9, 2015, 9:54:44 AM9/9/15
to AltNet-Hispano

Yo he terminado usando Microsoft Reporting Services con local reports tal como plantea Cláudio en el mail superior.
El tema es el bug que tiene para que te tome los objetos de dominio en mvc que hay que agregar una página .aspx al proyecto, no se si lo habrán resuelto en la nuevas versiones.


<p class="MsoNormal" style="background-image: initial; background-attachment: initial; background-size: initial; background-origin: initial; bac

Kiquenet

unread,
Sep 9, 2015, 11:00:11 AM9/9/15
to AltNet-Hispano

Diversas experiencias de un compañero, para imprimir en forma batch una serie de documentos (facturas en PDF) desde una aplicación ASP.NET.


Con el comando Shell "print" e indicando la impresora no funcionaba. Finalmente, con una llamada de a través de un proceso de la shell pero en lugar de al Acrobat Reader con Ghostscript

 

public class GSInterface

    {

        public string GhostScriptExePath { get; private set; }

 

        public GSInterface(string gsExePath)

        {

            this.GhostScriptExePath = gsExePath;

        }

 

        public virtual void CallGhostScript(string[] args)

        {

            System.IO.StreamReader sError = null;

            System.IO.StreamReader sOut = null;

 

            Process p = new Process();

            p.StartInfo.FileName = this.GhostScriptExePath;

            p.StartInfo.Arguments = string.Join(" ", args);

            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

 

            if (ConfigurationManager.AppSettings["pruebas"].ToString() == "S")

            {

                p.StartInfo.UseShellExecute = false;

                p.StartInfo.RedirectStandardError = true;

                p.StartInfo.RedirectStandardOutput = true;

            }

 

      

            p.Start();

            p.WaitForExit();

 

            if (ConfigurationManager.AppSettings["pruebas"].ToString() == "S")

            {

                sError = p.StandardError;

                sOut = p.StandardOutput;

                if (sError.ReadToEnd().Trim() != "")

                {

                    System.IO.TextWriter fich = null;

                    fich = System.IO.File.CreateText(@"c:\tmp\error_" + System.DateTime.Now.Ticks.ToString() + ".txt");

                    fich.WriteLine("resultado:" + sOut.ReadToEnd().Trim());

                    fich.WriteLine("error:" + sError.ReadToEnd().Trim());

                }

            }

        }

 

 

        public void Print(string filename, string printerName)

        {

            this.CallGhostScript(new string[] {

            "-q",

            "-sDEVICE=mswinpr2",

            "-sPAPERSIZE=a4",

            "-dNOPAUSE",

            "-dNoCancel",

            "-dBATCH",

            "-dDuplex",

            string.Format(@"-sOutputFile=""\\spool\{0}""", printerName),

            string.Format(@"""{0}""", filename)

        });

        }

    }

 

Luego la invoco de esta manera:

 

string printerName = ConfigurationManager.AppSettings["impresora"].ToString();

GSInterface gs = new GSInterface(ConfigurationManager.AppSettings["rutaGS"].ToString());

gs.Print(sRutaFacturas + sNombrePDFFacturaSocio, printerName); 

Reply all
Reply to author
Forward
0 new messages