From c0298979ccd5b1bc266cb31f167d854145a7ce77 Mon Sep 17 00:00:00 2001 From: manuel Date: Fri, 4 Sep 2026 08:56:53 +0200 Subject: [PATCH] - 3.0.8.0 2026-08-03 Correcciones clase FirmaCanvas --- Depurador TSpdfUtils/App.config | 10 +- .../Depurador TSpdfUtils.vbproj | 77 ++ Depurador TSpdfUtils/Form1.Designer.vb | 12 + Depurador TSpdfUtils/Form1.vb | 98 +- Depurador TSpdfUtils/packages.config | 25 + TSPdfUtils/FirmaCanvas.vb | 1214 +++++++++++++++++ TSPdfUtils/Firmas.vb | 3 +- TSPdfUtils/TSpdfUtils.vbproj | 15 +- 8 files changed, 1440 insertions(+), 14 deletions(-) create mode 100644 Depurador TSpdfUtils/packages.config create mode 100644 TSPdfUtils/FirmaCanvas.vb diff --git a/Depurador TSpdfUtils/App.config b/Depurador TSpdfUtils/App.config index 1c75772..aa7ac40 100644 --- a/Depurador TSpdfUtils/App.config +++ b/Depurador TSpdfUtils/App.config @@ -1,6 +1,14 @@ - + + + + + + + + + \ No newline at end of file diff --git a/Depurador TSpdfUtils/Depurador TSpdfUtils.vbproj b/Depurador TSpdfUtils/Depurador TSpdfUtils.vbproj index 61f3ccb..dea48c5 100644 --- a/Depurador TSpdfUtils/Depurador TSpdfUtils.vbproj +++ b/Depurador TSpdfUtils/Depurador TSpdfUtils.vbproj @@ -14,6 +14,8 @@ v4.8 true true + + AnyCPU @@ -48,16 +50,83 @@ On + + ..\packages\Portable.BouncyCastle.1.9.0\lib\net40\BouncyCastle.Crypto.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.9.0.9\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.9.0.9\lib\net462\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.9.0.9\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.9.0.9\lib\net462\Microsoft.Extensions.Logging.dll + + + ..\packages\Microsoft.Extensions.Logging.Abstractions.9.0.9\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Options.9.0.9\lib\net462\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Primitives.9.0.9\lib\net462\Microsoft.Extensions.Primitives.dll + + + ..\packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + ..\packages\System.Diagnostics.DiagnosticSource.9.0.9\lib\net462\System.Diagnostics.DiagnosticSource.dll + + + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + + ..\packages\TSpdf.bouncy-castle-adapter.3.0.4\lib\net48\TSpdf.bouncy-castle-adapter.dll + + + ..\packages\TSpdf.bouncy-castle-connector.3.0.4\lib\net48\TSpdf.bouncy-castle-connector.dll + + + ..\packages\TSpdf.bouncy-castle-fips-adapter.3.0.4\lib\netstandard2.0\TSpdf.bouncy-castle-fips-adapter.dll + + + ..\packages\TSpdf.commons.3.0.4\lib\net48\TSpdf.commons.dll + + + ..\packages\TSpdf.io.3.0.4\lib\net48\TSpdf.io.dll + + + ..\packages\TSpdf.kernel.3.0.4\lib\net48\TSpdf.kernel.dll + @@ -118,6 +187,7 @@ Settings.Designer.vb + @@ -130,4 +200,11 @@ + + + + Este proyecto hace referencia a los paquetes NuGet que faltan en este equipo. Use la restauración de paquetes NuGet para descargarlos. Para obtener más información, consulte http://go.microsoft.com/fwlink/?LinkID=322105. El archivo que falta es {0}. + + + \ No newline at end of file diff --git a/Depurador TSpdfUtils/Form1.Designer.vb b/Depurador TSpdfUtils/Form1.Designer.vb index eb4b072..f38a547 100644 --- a/Depurador TSpdfUtils/Form1.Designer.vb +++ b/Depurador TSpdfUtils/Form1.Designer.vb @@ -23,6 +23,7 @@ Partial Class Form1 _ Private Sub InitializeComponent() Me.Button1 = New System.Windows.Forms.Button() + Me.Button2 = New System.Windows.Forms.Button() Me.SuspendLayout() ' 'Button1 @@ -34,11 +35,21 @@ Partial Class Form1 Me.Button1.Text = "Button1" Me.Button1.UseVisualStyleBackColor = True ' + 'Button2 + ' + Me.Button2.Location = New System.Drawing.Point(363, 214) + Me.Button2.Name = "Button2" + Me.Button2.Size = New System.Drawing.Size(75, 23) + Me.Button2.TabIndex = 1 + Me.Button2.Text = "Button2" + Me.Button2.UseVisualStyleBackColor = True + ' 'Form1 ' Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font Me.ClientSize = New System.Drawing.Size(800, 450) + Me.Controls.Add(Me.Button2) Me.Controls.Add(Me.Button1) Me.Name = "Form1" Me.Text = "Form1" @@ -47,4 +58,5 @@ Partial Class Form1 End Sub Friend WithEvents Button1 As Button + Friend WithEvents Button2 As Button End Class diff --git a/Depurador TSpdfUtils/Form1.vb b/Depurador TSpdfUtils/Form1.vb index 6a06137..d095776 100644 --- a/Depurador TSpdfUtils/Form1.vb +++ b/Depurador TSpdfUtils/Form1.vb @@ -1,7 +1,11 @@ -Imports System.IO +Imports System +Imports System.IO Imports System.Security.Cryptography Imports System.Security.Cryptography.X509Certificates Imports TSpdfUtils +Imports tsPDFUtilsCore +Imports TSpdf + Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click @@ -48,8 +52,8 @@ Public Class Form1 Public Shared Sub FirmaPDFPFX(Razon As String, Contacto As String, Localizacion As String, FicheroPdfDef As String, EliminaFirmasAnteriores As Boolean, Optional EliminaFicheroDestino As Boolean = False) Dim p As String = "prueba1234." Try - Dim bpfx = IO.File.ReadAllBytes("C:\tmp\mmm.pfx") - Dim bpdf = IO.File.ReadAllBytes("c:\tmp\E_2026-00322_sellado.pdf") + Dim bpfx = System.IO.File.ReadAllBytes("C:\tmp\mmm.pfx") + Dim bpdf = System.IO.File.ReadAllBytes("c:\tmp\E_2026-00322_sellado.pdf") Dim pk As New X509Certificate2(bpfx, p, X509KeyStorageFlags.Exportable) CambiaKeyContainerName(pk) If pk.NotAfter < Now Then Throw New Exception("El certificado está caducado") @@ -57,8 +61,8 @@ Public Class Form1 Dim ms As New MemoryStream 'tsUtilsPDF.pdf.FirmaPDF(pk, "", PdfStream, ms, Razon, Contacto, Localizacion, RutaTmp, EliminaFirmasAnteriores) TSpdfUtils.Firmas.FirmaPDF(bpfx, p, "", New MemoryStream(bpdf), ms, Razon, Contacto, Localizacion, EliminaFirmasAnteriores) - If EliminaFicheroDestino AndAlso IO.File.Exists(FicheroPdfDef) Then IO.File.Delete(FicheroPdfDef) - IO.File.WriteAllBytes("c:\tmp\pruebafirmada.pdf", ms.ToArray) + If EliminaFicheroDestino AndAlso System.IO.File.Exists(FicheroPdfDef) Then System.IO.File.Delete(FicheroPdfDef) + System.IO.File.WriteAllBytes("c:\tmp\pruebafirmada.pdf", ms.ToArray) Catch ex As Exception Throw New Exception(ex.Message, ex) End Try @@ -71,4 +75,88 @@ Public Class Form1 rsa2.ImportParameters(rsa.ExportParameters(True)) Certificado.PrivateKey = rsa2 End Sub + + Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click + + End Sub + Private Shared Sub FirmaDocumentoCFO(PdfOrigen As String, PdfDestino As String) + Dim br As Brush + Dim Imagen As Byte() = IO.File.ReadAllBytes("c:\tmp\OPENCOAS_LOGO.png") + 'Dim TextoArt As String = "" + 'If Doc.tiposdocumentosCOAS.Descripcion = "C.F.O. VISADO UNICO" Then + ' TextoArt = "Conforme al Art. 5.1 del R.D. 1000/2010" + 'End If + 'If ds.NumeroExpedienteDocumentoOriginal.ToString <> "" Then + ' ' probablemente haya que reducir la fuente en este caso + ' TextoArt = "Sustituye al visado el " & ds.FechaVisadoOriginal.ToString("dd-MM-yyyy") & ". " & ds.NumeroExpedienteDocumentoOriginal.Split(" ")(1) + 'Else + ' Dim releds As relacionesentredocumentos = Doc.RelacionesComoHijo.FirstOrDefault(Function(x) x.Tipo = TipoRelacionEntreDocumentosEnum.SUSTITUCION) + ' If releds IsNot Nothing Then + ' Dim sFecVis As String + ' If releds.DocumentoPadre.NumeroExpedienteDocumento.StartsWith("24/003566") OrElse releds.DocumentoPadre.NumeroExpedienteDocumento.StartsWith("24/003565") Then + ' sFecVis = "22-12-2023" + ' Else + ' If releds.DocumentoPadre.FechaVisado.HasValue = False Then Throw New Exception("El documento al que sustituye no tiene fecha de visado") + ' sFecVis = releds.DocumentoPadre.FechaVisado.Value.ToString("dd-MM-yyyy") + ' End If + ' TextoArt = "Sustituye al visado " & releds.DocumentoPadre.Documento & " el " & sFecVis + ' End If + 'End If + + 'If ds.TipoSello.StartsWith("2") Or ds.TipoSello.StartsWith("3") Then + ' br = Brushes.Green + ' Imagen = My.Resources.CFO_Verde + 'Else + ' br = Brushes.DarkBlue + ' Imagen = My.Resources.CFO_Azul + 'End If + Dim listadoTextos As List(Of DatosTextos) = New List(Of DatosTextos) + Dim listadoImagenes As List(Of DatosImagenFirma) = New List(Of DatosImagenFirma) + + Dim Texto1 As New DatosTextos + Texto1.texto = "Solicitud Certificado Digital del COAS" + Texto1.size = 7.25 + Texto1.NombreFuente = "Montserrat-Regular" + Texto1.PosicionEsquina = EsquinaEnum.INFERIOR_IZQUIERDA + Texto1.anguloRotacion = 270 + Texto1.distanciaX = 7 + Texto1.distanciaY = 715 + Texto1.colorTexto = br + Texto1.estiloTexto = FontStyle.Regular + Texto1.AlineacionTexto = AlineamientoEnum.IZQUIERDA + + listadoTextos.Add(Texto1) + + Dim imagenFondo As New DatosImagenFirma + imagenFondo.PosicionEsquina = EsquinaEnum.INFERIOR_IZQUIERDA + imagenFondo.Alto = 100 + imagenFondo.Ancho = 100 + imagenFondo.PosicionX = 0 + imagenFondo.PosicionY = 0 + imagenFondo.AnguloRotacion = 270 + imagenFondo.Imagen = Imagen + + listadoImagenes.Add(imagenFondo) + + Dim listadoFuentes As New List(Of DatosFuente) + + Dim dfr As New DatosFuente + With dfr + .NombreFuente = "Montserrat-Regular" + .memoryFont = IO.File.ReadAllBytes("C:\tecnosis.tfs\Clientes\COAS\GestionWebCOAS\Content\Fuentes\Montserrat_Regular.ttf") + End With + listadoFuentes.Add(dfr) + Dim dfb As New DatosFuente + With dfb + .NombreFuente = "Montserrat-Bold" + .memoryFont = IO.File.ReadAllBytes("C:\tecnosis.tfs\Clientes\COAS\GestionWebCOAS\Content\Fuentes\Montserrat_Bold.ttf") + End With + listadoFuentes.Add(dfb) + + Dim medidas As Kernel.Geom.Rectangle + 'medidas = New Kernel.Geom.Rectangle(0, 0, 48.1889648, 396.8502) + medidas = New Kernel.Geom.Rectangle(0, 0, 48.1889648, 312.735) + + TSpdfUtils.FirmaCanvas.FirmaPDFVisible(bPfx, ClavePFX, ClavePDF, PdfOrigen, PdfDestino, Razon, Contacto, Localizacion, bimagen, False, False, NombreCampoFirma, dcf, BloquearDocumento) + End Sub End Class diff --git a/Depurador TSpdfUtils/packages.config b/Depurador TSpdfUtils/packages.config new file mode 100644 index 0000000..07ea84f --- /dev/null +++ b/Depurador TSpdfUtils/packages.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TSPdfUtils/FirmaCanvas.vb b/TSPdfUtils/FirmaCanvas.vb new file mode 100644 index 0000000..047ac57 --- /dev/null +++ b/TSPdfUtils/FirmaCanvas.vb @@ -0,0 +1,1214 @@ +Imports System.Drawing +Imports System.IO +Imports System.Text +Imports Org.BouncyCastle.Pkcs +Imports TSpdf +Imports TSpdf.Bouncycastle.Cert +Imports TSpdf.Bouncycastle.Crypto +Imports TSpdf.Commons.Bouncycastle.Cert +Imports TSpdf.Commons.Bouncycastle.Crypto +Imports TSpdf.Forms +Imports TSpdf.IO.Font +Imports TSpdf.IO.Font.Constants +Imports TSpdf.IO.Image +Imports TSpdf.Kernel.Colors +Imports TSpdf.Kernel.Font +Imports TSpdf.Kernel.Pdf +Imports TSpdf.Kernel.Pdf.Canvas +Imports TSpdf.Layout +Imports TSpdf.Layout.Element +Imports TSpdf.Layout.Properties +Imports TSpdf.Signatures +Imports TSpdfUtils.Firmas +Imports tsPDFUtilsCore + + + +' ============================================================================ +' +' +' Funciones auxiliares para sustituir el Bitmap/GDI+ de la apariencia visible +' de una firma por elementos dibujados directamente sobre el Layer2 de iText 7. +' +' REQUISITOS: +' - DatosTextos +' - DatosImagenFirma +' - DatosFuente +' - EsquinaEnum +' - AlineamientoEnum +' +' Estas clases/enums NO se declaran aquí porque ya existen en el proyecto. +' +' La conversión mantiene el sistema de coordenadas del código antiguo: +' - distancias/posiciones: unidades equivalentes a los píxeles del Bitmap +' original de 600 DPI. +' - iText: puntos PDF. +' +' 600 DPI -> 72 puntos/pulgada +' 1 unidad antigua = 72 / 600 puntos. +' ============================================================================ + +Public Class FirmaCanvas + + Private Const DPI_BITMAP_ORIGINAL As Single = 600.0F + Private Const PUNTOS_POR_PULGADA As Single = 72.0F + Private Const FACTOR_BITMAP_A_PDF As Single = PUNTOS_POR_PULGADA / DPI_BITMAP_ORIGINAL + + Public Shared Sub FirmaPDF(bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Byte(), ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, ByVal listadoTextos As List(Of DatosTextos), ByVal listadoImagenes As List(Of DatosImagenFirma), Optional listadoFuentes As List(Of DatosFuente) = Nothing, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) + Dim StreamPfx As New MemoryStream(bPfx) + Dim fso = New MemoryStream(PdfOrigen) + FirmaPDFVisible(StreamPfx, ClavePFX, ClavePDF, fso, pdfDestino, Razon, Contacto, Localizacion, listadoTextos, listadoImagenes, listadoFuentes, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, DatosCampoFirma, BloquearDocumento) + End Sub + + + + Public Shared Sub FirmaPDFVisible(bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As String, ByVal pdfDestino As String, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, ByVal listadoTextos As List(Of DatosTextos), ByVal listadoImagenes As List(Of DatosImagenFirma), Optional listadoFuentes As List(Of DatosFuente) = Nothing, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) + Dim StreamPfx As New MemoryStream(bPfx) + Dim fso = New MemoryStream(System.IO.File.ReadAllBytes(PdfOrigen)) + Dim fs As New FileStream(pdfDestino, FileMode.Create) + FirmaPDFVisible(StreamPfx, ClavePFX, ClavePDF, fso, fs, Razon, Contacto, Localizacion, listadoTextos, listadoImagenes, listadoFuentes, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, DatosCampoFirma, BloquearDocumento) + fs.Close() + End Sub + + Public Shared Sub FirmaPDFVisible(ByVal StreamPFX As Stream, ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Stream, ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, ByVal listadoTextos As List(Of DatosTextos), ByVal listadoImagenes As List(Of DatosImagenFirma), Optional listadoFuentes As List(Of DatosFuente) = Nothing, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) + Try + Dim ms As New MemoryStream + Dim reader As New PdfReader(PdfOrigen) + reader.SetUnethicalReading(ObviarSeguridad) + Dim sp As New StampingProperties() + ' Dim ListaCampos() As String + Dim medidas As Kernel.Geom.Rectangle + If NombreCampoFirma = "" Then + medidas = New Kernel.Geom.Rectangle(DatosCampoFirma.CoordenadaX, DatosCampoFirma.CoordenadaY, DatosCampoFirma.Ancho, DatosCampoFirma.Alto) + Else + Dim pdfDoc As PdfDocument = New PdfDocument(reader) + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + Dim cf = pdfAcrof.GetField(NombreCampoFirma) + If cf Is Nothing Then + medidas = New Kernel.Geom.Rectangle(DatosCampoFirma.CoordenadaX, DatosCampoFirma.CoordenadaY, DatosCampoFirma.Ancho, DatosCampoFirma.Alto) + Else + medidas = cf.GetWidgets(0).GetRectangle.ToRectangle + End If + pdfDoc.Close() + PdfOrigen.Seek(0, 0) + reader = New PdfReader(PdfOrigen) + End If + If EliminarFirmasAnteriores = False Then + sp.UseAppendMode() + If ClavePDF <> "" Then + Dim bc = System.Text.Encoding.ASCII.GetBytes(ClavePDF) + Dim props As WriterProperties = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + Dim writer As PdfWriter = New PdfWriter(ms, props) + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + ' If NombreCampoFirma <> "" Then medidas = pdfAcrof.GetField(NombreCampoFirma).GetWidgets(0).GetRectangle.ToRectangle + pdfDoc.Close() + Dim rp As New ReaderProperties() + rp.SetPassword(bc) + reader = New PdfReader(New MemoryStream(ms.ToArray), rp) + + End If + reader.SetUnethicalReading(ObviarSeguridad) + + Else + Dim writer As PdfWriter + If ClavePDF <> "" Then + Dim bc = System.Text.Encoding.ASCII.GetBytes(ClavePDF) + Dim props = New WriterProperties().SetStandardEncryption(Nothing, bc, EncryptionConstants.ALLOW_PRINTING, EncryptionConstants.ENCRYPTION_AES_128 Or EncryptionConstants.DO_NOT_ENCRYPT_METADATA) + writer = New PdfWriter(ms, props) + Else + writer = New PdfWriter(ms) + End If + Dim pdfDoc As PdfDocument = New PdfDocument(reader, writer) + Dim pdfAcrof = PdfAcroForm.GetAcroForm(pdfDoc, False) + pdfAcrof.FlattenFields() + pdfDoc.Close() + If ClavePDF = "" Then + reader = New PdfReader(New MemoryStream(ms.ToArray)) + Else + Dim pr As New ReaderProperties() + pr.SetPassword(Encoding.ASCII.GetBytes(ClavePDF)) + reader = New PdfReader(New MemoryStream(ms.ToArray), pr) + End If + reader.SetUnethicalReading(ObviarSeguridad) + End If + + Dim cpassword = ClavePFX.ToCharArray + Dim [alias] As String = Nothing + Dim pk12 As Pkcs12Store = New Pkcs12Store(StreamPFX, cpassword) + + For Each a In pk12.Aliases + [alias] = (CType(a, String)) + + If pk12.IsKeyEntry([alias]) Then + Exit For + End If + Next + + Dim pk As IPrivateKey = New PrivateKeyBC(pk12.GetKey([alias]).Key) + Dim pks = New PrivateKeySignature(pk, DigestAlgorithms.SHA256) + + Dim ce As X509CertificateEntry() = pk12.GetCertificateChain([alias]) + Dim chain = New IX509Certificate(ce.Length - 1) {} + + For k As Integer = 0 To ce.Length - 1 + chain(k) = New X509CertificateBC(ce(k).Certificate) + Next + + Dim OCSPVerifier = New OCSPVerifier(Nothing, Nothing) + Dim ocspClient = New OcspClientBouncyCastle(OCSPVerifier) + Dim crlClients = New List(Of ICrlClient) + Dim listadoDatosCertificado = ce(0).Certificate.SubjectDN.GetValueList + Dim nombre = listadoDatosCertificado(4).ToString + + Dim pdfSign As New PdfSigner(reader, pdfDestino, sp) + If NombreCampoFirma = "" Then + pdfSign.SetFieldName(DatosCampoFirma.NombreCampo) + Else + pdfSign.SetFieldName(NombreCampoFirma) + End If + + Dim appearance As PdfSignatureAppearance = pdfSign.GetSignatureAppearance() + + appearance. + SetReason(Razon). + SetLocation(Localizacion). + SetContact(Contacto). + SetPageRect(medidas). + SetLayer2Text(""). + SetReuseAppearance(False) + + Dim layer2 = appearance.GetLayer2() + + Dim pdfCanvas As New PdfCanvas( + layer2, + pdfSign.GetDocument()) + + Dim layoutCanvas As New Canvas( + pdfCanvas, + medidas) + + DibujarFirmaEnCanvas( + layoutCanvas, + listadoTextos, + listadoImagenes, + listadoFuentes, + medidas) + + layoutCanvas.Close() + + + + + + If BloquearDocumento Then + pdfSign.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED) + End If + pdfSign.SignDetached(pks, chain, crlClients, ocspClient, Nothing, 0, PdfSigner.CryptoStandard.CADES) + + reader.Close() + + Catch ex As Exception + Throw New Exception(ex.Message, ex) + End Try + End Sub + + + ' ========================================================================= + ' FUNCIÓN PRINCIPAL + ' ========================================================================= + ' + ' Dibuja textos e imágenes directamente en el Canvas de Layer2. + ' + ' Ejemplo de uso: + ' + ' Dim layer2 = appearance.GetLayer2() + ' Dim pdfCanvas As New PdfCanvas(layer2, pdfSign.GetDocument()) + ' Dim layoutCanvas As New Canvas(pdfCanvas, medidas) + ' + ' DibujarFirmaEnCanvas(layoutCanvas, listadoTextos, listadoImagenes, + ' listadoFuentes, medidas) + ' + ' layoutCanvas.Close() + ' + ' ========================================================================= + + Public Shared Sub DibujarFirmaEnCanvas( + ByVal canvas As Canvas, + ByVal listadoTextos As List(Of DatosTextos), + ByVal listadoImagenes As List(Of DatosImagenFirma), + ByVal listadoFuentes As List(Of DatosFuente), + ByVal medidas As TSpdf.Kernel.Geom.Rectangle) + + If canvas Is Nothing Then + Throw New ArgumentNullException(NameOf(canvas)) + End If + + If medidas Is Nothing Then + Throw New ArgumentNullException(NameOf(medidas)) + End If + + + ' Dimensiones del Bitmap original de 600 DPI. + ' + ' El Bitmap antiguo se creaba así: + ' + ' anchoPixeles = anchoPDF * 600 / 72 + ' altoPixeles = altoPDF * 600 / 72 + ' + Dim anchoBitmap As Integer = + CInt(Math.Round( + medidas.GetWidth() / FACTOR_BITMAP_A_PDF)) + + Dim altoBitmap As Integer = + CInt(Math.Round( + medidas.GetHeight() / FACTOR_BITMAP_A_PDF)) + + + ' ===================================================================== + ' TEXTOS + ' ===================================================================== + + If listadoTextos IsNot Nothing Then + + For Each datosTexto As DatosTextos In listadoTextos + + If datosTexto Is Nothing Then + Continue For + End If + + DibujarTextoFirma( + canvas, + datosTexto, + listadoFuentes, + anchoBitmap, + altoBitmap) + + Next + + End If + + + ' ===================================================================== + ' IMÁGENES + ' ===================================================================== + + If listadoImagenes IsNot Nothing Then + + For Each datosImagen As DatosImagenFirma In listadoImagenes + + If datosImagen Is Nothing Then + Continue For + End If + + DibujarImagenFirma( + canvas, + datosImagen, + anchoBitmap, + altoBitmap) + + Next + + End If + + End Sub + + + ' ========================================================================= + ' TEXTO + ' ========================================================================= + + Private Shared Sub DibujarTextoFirma( + ByVal canvas As Canvas, + ByVal datosTexto As DatosTextos, + ByVal listadoFuentes As List(Of DatosFuente), + ByVal anchoFirmaBitmap As Integer, + ByVal altoFirmaBitmap As Integer) + + Dim font As PdfFont = + ObtenerFuentePdf( + datosTexto.NombreFuente, + listadoFuentes) + + + Dim tamanoFuente As Single = + datosTexto.size + + If tamanoFuente <= 0 Then + tamanoFuente = 1 + End If + + + ' --------------------------------------------------------------------- + ' Medición aproximada equivalente a MeasureString(). + ' + ' GDI+ y las fuentes PDF no miden exactamente igual. Esta medición + ' sirve para mantener el posicionamiento por esquina. + ' --------------------------------------------------------------------- + + Dim anchoTextoPdf As Single = + font.GetWidth( + If(datosTexto.texto, String.Empty), + tamanoFuente) + + Dim altoTextoPdf As Single = + ObtenerAltoTexto(font, tamanoFuente) + + + Dim anchoTextoBitmap As Integer = + Math.Max( + 1, + CInt(Math.Ceiling( + anchoTextoPdf / FACTOR_BITMAP_A_PDF))) + + + Dim altoTextoBitmap As Integer = + Math.Max( + 1, + CInt(Math.Ceiling( + altoTextoPdf / FACTOR_BITMAP_A_PDF))) + + + ' --------------------------------------------------------------------- + ' Utilizamos exactamente la lógica de calcularX/calcularY del código + ' antiguo, trabajando en las unidades del Bitmap virtual de 600 DPI. + ' --------------------------------------------------------------------- + + Dim coordenadaXBitmap As Integer = + CalcularX( + datosTexto.distanciaX, + datosTexto.distanciaY, + datosTexto.PosicionEsquina, + anchoFirmaBitmap, + altoFirmaBitmap, + datosTexto.anguloRotacion, + anchoTextoBitmap, + altoTextoBitmap) + + + Dim coordenadaYBitmap As Integer = + CalcularY( + datosTexto.distanciaY, + datosTexto.distanciaX, + datosTexto.PosicionEsquina, + anchoFirmaBitmap, + altoFirmaBitmap, + datosTexto.anguloRotacion, + anchoTextoBitmap, + altoTextoBitmap) + + + ' --------------------------------------------------------------------- + ' Bitmap -> PDF. + ' + ' El Bitmap antiguo tiene origen arriba/izquierda. + ' PDF tiene origen abajo/izquierda. + ' --------------------------------------------------------------------- + + Dim x As Single = + coordenadaXBitmap * FACTOR_BITMAP_A_PDF + + Dim y As Single = + (altoFirmaBitmap - + coordenadaYBitmap - + altoTextoBitmap) * + FACTOR_BITMAP_A_PDF + + + Dim p As New Paragraph( + If(datosTexto.texto, String.Empty)) + + + p.SetFont(font) + p.SetFontSize(tamanoFuente) + p.SetMargin(0) + p.SetPadding(0) + p.SetMultipliedLeading(1.0F) + + + ' --------------------------------------------------------------------- + ' COLOR + ' --------------------------------------------------------------------- + + If datosTexto.colorTexto IsNot Nothing Then + + p.SetFontColor( + ObtenerColorPdf(datosTexto.colorTexto)) + + End If + + + ' --------------------------------------------------------------------- + ' ESTILO + ' --------------------------------------------------------------------- + + If (datosTexto.estiloTexto And FontStyle.Bold) <> 0 Then + p.SetBold() + End If + + If (datosTexto.estiloTexto And FontStyle.Italic) <> 0 Then + p.SetItalic() + End If + + + ' --------------------------------------------------------------------- + ' ROTACIÓN + ' --------------------------------------------------------------------- + + If Math.Abs(datosTexto.anguloRotacion) > 0.0001F Then + + p.SetRotationAngle( + datosTexto.anguloRotacion * + Math.PI / + 180.0) + + End If + + + ' --------------------------------------------------------------------- + ' ALINEACIÓN + ' --------------------------------------------------------------------- + + Select Case datosTexto.AlineacionTexto + + Case AlineamientoEnum.IZQUIERDA + + canvas.ShowTextAligned( + p, + x, + y, + TextAlignment.LEFT) + + + Case AlineamientoEnum.CENTRO + + canvas.ShowTextAligned( + p, + x, + y, + TextAlignment.CENTER) + + + Case AlineamientoEnum.DERECHA + + canvas.ShowTextAligned( + p, + x, + y, + TextAlignment.RIGHT) + + + Case AlineamientoEnum.JUSTIFICADO + + DibujarTextoJustificado( + canvas, + p, + x, + y, + anchoFirmaBitmap * + FACTOR_BITMAP_A_PDF) + + + Case AlineamientoEnum.JUSTIFICADO_ALL + + DibujarTextoJustificado( + canvas, + p, + x, + y, + anchoFirmaBitmap * + FACTOR_BITMAP_A_PDF) + + + Case Else + + canvas.ShowTextAligned( + p, + x, + y, + TextAlignment.LEFT) + + End Select + + End Sub + + + ' ========================================================================= + ' IMAGEN + ' ========================================================================= + + Private Shared Sub DibujarImagenFirma( + ByVal canvas As Canvas, + ByVal datosImagen As DatosImagenFirma, + ByVal anchoFirmaBitmap As Integer, + ByVal altoFirmaBitmap As Integer) + + If datosImagen.Imagen Is Nothing OrElse + datosImagen.Imagen.Length = 0 Then + + Return + + End If + + + Dim imageData As ImageData = + ImageDataFactory.Create( + datosImagen.Imagen) + + + Dim imagen As New TSpdf.Layout.Element.Image( + imageData) + + + ' --------------------------------------------------------------------- + ' IMPORTANTE: + ' + ' En la versión Bitmap original se estaba usando el tamaño del Bitmap + ' completo en DrawImage(), ignorando Ancho/Alto. + ' + ' Aquí utilizamos Ancho/Alto de DatosImagenFirma, que es lo lógico si + ' esas propiedades representan el tamaño deseado de la imagen. + ' --------------------------------------------------------------------- + + Dim anchoPdf As Single = + datosImagen.Ancho * + FACTOR_BITMAP_A_PDF + + Dim altoPdf As Single = + datosImagen.Alto * + FACTOR_BITMAP_A_PDF + + + If anchoPdf <= 0 OrElse altoPdf <= 0 Then + Return + End If + + + imagen.SetWidth(anchoPdf) + imagen.SetHeight(altoPdf) + + + ' --------------------------------------------------------------------- + ' Coordenadas equivalentes a calcularX/calcularY. + ' --------------------------------------------------------------------- + + Dim coordenadaXBitmap As Integer = + CalcularX( + datosImagen.PosicionX, + datosImagen.PosicionY, + datosImagen.PosicionEsquina, + anchoFirmaBitmap, + altoFirmaBitmap, + datosImagen.AnguloRotacion, + datosImagen.Ancho, + datosImagen.Alto) + + + Dim coordenadaYBitmap As Integer = + CalcularY( + datosImagen.PosicionY, + datosImagen.PosicionX, + datosImagen.PosicionEsquina, + anchoFirmaBitmap, + altoFirmaBitmap, + datosImagen.AnguloRotacion, + datosImagen.Ancho, + datosImagen.Alto) + + + Dim x As Single = + coordenadaXBitmap * + FACTOR_BITMAP_A_PDF + + Dim y As Single = + (altoFirmaBitmap - + coordenadaYBitmap - + datosImagen.Alto) * + FACTOR_BITMAP_A_PDF + + + imagen.SetFixedPosition(x, y) + + + ' --------------------------------------------------------------------- + ' ROTACIÓN + ' --------------------------------------------------------------------- + + If Math.Abs(datosImagen.AnguloRotacion) > 0.0001 Then + + imagen.SetRotationAngle( + datosImagen.AnguloRotacion * + Math.PI / + 180.0) + + End If + + + canvas.Add(imagen) + + End Sub + + + ' ========================================================================= + ' FUENTES + ' ========================================================================= + + Private Shared Function ObtenerFuentePdf( + ByVal nombreFuente As String, + ByVal listadoFuentes As List(Of DatosFuente)) As PdfFont + + If String.IsNullOrWhiteSpace(nombreFuente) Then + + Return PdfFontFactory.CreateFont( + StandardFonts.HELVETICA) + + End If + + + Select Case nombreFuente.ToUpperInvariant() + + Case "HELVETICA" + + Return PdfFontFactory.CreateFont( + StandardFonts.HELVETICA, + PdfEncodings.WINANSI) + + + Case "HELVETICA_BOLD" + + Return PdfFontFactory.CreateFont( + StandardFonts.HELVETICA_BOLD, + PdfEncodings.WINANSI) + + + Case "HELVETICA_OBLIQUE" + + Return PdfFontFactory.CreateFont( + StandardFonts.HELVETICA_OBLIQUE, + PdfEncodings.WINANSI) + + + Case "HELVETICA_BOLDOBLIQUE" + + Return PdfFontFactory.CreateFont( + StandardFonts.HELVETICA_BOLDOBLIQUE, + PdfEncodings.WINANSI) + + + Case "TIMES_ROMAN" + + Return PdfFontFactory.CreateFont( + StandardFonts.TIMES_ROMAN, + PdfEncodings.WINANSI) + + + Case "TIMES_BOLD" + + Return PdfFontFactory.CreateFont( + StandardFonts.TIMES_BOLD, + PdfEncodings.WINANSI) + + + Case "TIMES_ITALIC" + + Return PdfFontFactory.CreateFont( + StandardFonts.TIMES_ITALIC, + PdfEncodings.WINANSI) + + + Case "TIMES_BOLDITALIC" + + Return PdfFontFactory.CreateFont( + StandardFonts.TIMES_BOLDITALIC, + PdfEncodings.WINANSI) + + + Case "COURIER" + + Return PdfFontFactory.CreateFont( + StandardFonts.COURIER, + PdfEncodings.WINANSI) + + + Case "COURIER_BOLD" + + Return PdfFontFactory.CreateFont( + StandardFonts.COURIER_BOLD, + PdfEncodings.WINANSI) + + + Case "COURIER_OBLIQUE" + + Return PdfFontFactory.CreateFont( + StandardFonts.COURIER_OBLIQUE, + PdfEncodings.WINANSI) + + + Case "COURIER_BOLDOBLIQUE" + + Return PdfFontFactory.CreateFont( + StandardFonts.COURIER_BOLDOBLIQUE, + PdfEncodings.WINANSI) + + + Case "SYMBOL" + + Return PdfFontFactory.CreateFont( + StandardFonts.SYMBOL) + + + Case "ZAPFDINGBATS" + + Return PdfFontFactory.CreateFont( + StandardFonts.ZAPFDINGBATS) + + End Select + + + ' --------------------------------------------------------------------- + ' Fuente personalizada incluida en DatosFuente. + ' --------------------------------------------------------------------- + + If listadoFuentes IsNot Nothing Then + + Dim fuente As DatosFuente = + listadoFuentes.FirstOrDefault( + Function(f) + Return f IsNot Nothing AndAlso + String.Equals( + f.NombreFuente, + nombreFuente, + StringComparison.OrdinalIgnoreCase) + End Function) + + + If fuente IsNot Nothing AndAlso + fuente.memoryFont IsNot Nothing AndAlso + fuente.memoryFont.Length > 0 Then + + Return PdfFontFactory.CreateFont( + fuente.memoryFont, + PdfEncodings.IDENTITY_H) + + End If + + End If + + + Throw New Exception( + "Fuente " & nombreFuente & " no encontrada") + + End Function + + + ' ========================================================================= + ' COLOR + ' ========================================================================= + + Private Shared Function ObtenerColorPdf( + ByVal brush As Brush) As TSpdf.Kernel.Colors.Color + + If brush Is Nothing Then + Return ColorConstants.BLACK + End If + + + If TypeOf brush Is SolidBrush Then + + Dim solidBrush As SolidBrush = + DirectCast(brush, SolidBrush) + + + Return New DeviceRgb( + solidBrush.Color.R, + solidBrush.Color.G, + solidBrush.Color.B) + + End If + + + ' Para otros tipos de Brush no existe una equivalencia directa sencilla + ' en TSpdf. Se utiliza negro como valor por defecto. + + Return ColorConstants.BLACK + + End Function + + + ' ========================================================================= + ' ALTURA DE TEXTO + ' ========================================================================= + + Private Shared Function ObtenerAltoTexto( + ByVal font As PdfFont, + ByVal fontSize As Single) As Single + + Try + + Dim descriptor = font.GetFontProgram.GetFontMetrics + + Dim ascender As Single = + descriptor.GetTypoAscender() + + Dim descender As Single = + descriptor.GetTypoDescender() + + + If ascender > descender Then + + Return (ascender - descender) / + 1000.0F * + fontSize + + End If + + Catch + ' Usamos el fallback. + End Try + + + Return fontSize + + End Function + + + ' ========================================================================= + ' JUSTIFICADO + ' ========================================================================= + ' + ' La implementación GDI+ tenía DrawJustifiedString(). Como esa función no + ' está disponible aquí, para JUSTIFICADO/JUSTIFICADO_ALL se utiliza una + ' caja cuyo ancho es el disponible y iText se encarga de la alineación. + ' + ' Si tu DrawJustifiedString() hace un tratamiento especial de espacios, + ' habría que reproducirlo aparte. + ' + ' ========================================================================= + + Private Shared Sub DibujarTextoJustificado( + ByVal canvas As Canvas, + ByVal paragraph As Paragraph, + ByVal x As Single, + ByVal y As Single, + ByVal anchoDisponible As Single) + + Dim tabla As New TSpdf.Layout.Element.Table( + New Single() {anchoDisponible}) + + tabla.SetFixedPosition( + x, + y, + anchoDisponible) + + tabla.SetBorder( + TSpdf.Layout.Borders.Border.NO_BORDER) + + tabla.SetPadding(0) + tabla.SetMargin(0) + + Dim celda As New TSpdf.Layout.Element.Cell() + + celda.SetBorder( + TSpdf.Layout.Borders.Border.NO_BORDER) + + celda.SetPadding(0) + celda.SetMargin(0) + + celda.SetTextAlignment( + TextAlignment.JUSTIFIED) + + celda.Add(paragraph) + + tabla.AddCell(celda) + + canvas.Add(tabla) + + End Sub + + + ' ========================================================================= + ' CALCULAR X + ' ========================================================================= + ' + ' Conversión directa de tu calcularX(). + ' ========================================================================= + + Private Shared Function CalcularX( + ByVal coordenadaX As Integer, + ByVal coordenadaY As Integer, + ByVal posicionEsquina As EsquinaEnum, + ByVal anchoFirma As Integer, + ByVal altoFirma As Integer, + ByVal rotacionElemento As Single, + ByVal anchoElemento As Integer, + ByVal altoElemento As Integer) As Integer + + Dim ejeX As Integer = 0 + + Dim rotacion As Integer = + NormalizarRotacion(rotacionElemento) + + + Select Case posicionEsquina + + Case EsquinaEnum.SUPERIOR_IZQUIERDA + + Select Case rotacion + + Case 90 + ejeX = coordenadaY + + Case 180 + ejeX = -coordenadaX - anchoElemento + + Case 270 + ejeX = -coordenadaY - anchoElemento + + Case Else + ejeX = coordenadaX + + End Select + + + Case EsquinaEnum.INFERIOR_IZQUIERDA + + Select Case rotacion + + Case 90 + ejeX = + altoFirma - + (coordenadaY + anchoElemento) + + Case 180 + ejeX = + -coordenadaX - + anchoElemento + + Case 270 + ejeX = + coordenadaY - + altoFirma + + Case Else + ejeX = coordenadaX + + End Select + + + Case EsquinaEnum.SUPERIOR_DERECHA + + Select Case rotacion + + Case 90 + ejeX = coordenadaY + + Case 180 + ejeX = + -anchoFirma + + coordenadaX + + Case 270 + ejeX = + -coordenadaY - + anchoElemento + + Case Else + ejeX = + anchoFirma - + (anchoElemento + coordenadaX) + + End Select + + + Case EsquinaEnum.INFERIOR_DERECHA + + Select Case rotacion + + Case 90 + ejeX = + altoFirma - + (coordenadaY + anchoElemento) + + Case 180 + ejeX = + -anchoFirma + + coordenadaX + + Case 270 + ejeX = + coordenadaY - + altoFirma + + Case Else + ejeX = + anchoFirma - + (anchoElemento + coordenadaX) + + End Select + + End Select + + + Return ejeX + + End Function + + + ' ========================================================================= + ' CALCULAR Y + ' ========================================================================= + ' + ' Conversión directa de tu calcularY(). + ' ========================================================================= + + Private Shared Function CalcularY( + ByVal coordenadaY As Integer, + ByVal coordenadaX As Integer, + ByVal posicionEsquina As EsquinaEnum, + ByVal anchoFirma As Integer, + ByVal altoFirma As Integer, + ByVal rotacionElemento As Single, + ByVal anchoElemento As Integer, + ByVal altoElemento As Integer) As Integer + + Dim ejeY As Integer = 0 + + Dim rotacion As Integer = + NormalizarRotacion(rotacionElemento) + + + Select Case posicionEsquina + + Case EsquinaEnum.SUPERIOR_IZQUIERDA + + Select Case rotacion + + Case 90 + ejeY = + -coordenadaX - + altoElemento + + Case 180 + ejeY = + -coordenadaY - + altoElemento + + Case 270 + ejeY = coordenadaX + + Case Else + ejeY = coordenadaY + + End Select + + + Case EsquinaEnum.INFERIOR_IZQUIERDA + + Select Case rotacion + + Case 90 + ejeY = + -coordenadaX - + altoElemento + + Case 180 + ejeY = + -altoFirma + + coordenadaY + + Case 270 + ejeY = coordenadaX + + Case Else + ejeY = + altoFirma - + (altoElemento + coordenadaY) + + End Select + + + Case EsquinaEnum.SUPERIOR_DERECHA + + Select Case rotacion + + Case 90 + ejeY = + coordenadaX - + anchoFirma + + Case 180 + ejeY = + -coordenadaY - + altoElemento + + Case 270 + ejeY = + anchoFirma - + (altoElemento + coordenadaX) + + Case Else + ejeY = coordenadaY + + End Select + + + Case EsquinaEnum.INFERIOR_DERECHA + + Select Case rotacion + + Case 90 + ejeY = + coordenadaX - + anchoFirma + + Case 180 + ejeY = + -altoFirma + + coordenadaY + + Case 270 + ejeY = + anchoFirma - + (altoElemento + coordenadaX) + + Case Else + ejeY = + altoFirma - + (altoElemento + coordenadaY) + + End Select + + End Select + + + Return ejeY + + End Function + + + ' ========================================================================= + ' ROTACIÓN + ' ========================================================================= + + Private Shared Function NormalizarRotacion( + ByVal rotacion As Single) As Integer + + ' Tu código original solamente contemplaba 0, 90, 180 y 270. + ' Mantenemos ese comportamiento. + + Dim resultado As Integer = + CInt(Math.Round(rotacion / 90.0R) * 90) + + resultado = + resultado Mod 360 + + If resultado < 0 Then + resultado += 360 + End If + + Return resultado + + End Function + +End Class diff --git a/TSPdfUtils/Firmas.vb b/TSPdfUtils/Firmas.vb index f34170a..a8706e9 100644 --- a/TSPdfUtils/Firmas.vb +++ b/TSPdfUtils/Firmas.vb @@ -449,9 +449,8 @@ Public Class Firmas Public Shared Sub FirmaPDFVisible(bPfx As Byte(), ByVal ClavePFX As String, ByVal ClavePDF As String, ByVal PdfOrigen As Byte(), ByVal pdfDestino As Stream, ByVal Razon As String, ByVal Contacto As String, ByVal Localizacion As String, ByVal listadoTextos As List(Of DatosTextos), ByVal listadoImagenes As List(Of DatosImagenFirma), Optional listadoFuentes As List(Of DatosFuente) = Nothing, Optional EliminarFirmasAnteriores As Boolean = False, Optional ObviarSeguridad As Boolean = True, Optional NombreCampoFirma As String = "", Optional DatosCampoFirma As DatosNuevoCampoFirma = Nothing, Optional BloquearDocumento As Boolean = False) Dim StreamPfx As New MemoryStream(bPfx) - Dim ms As New MemoryStream Dim fso = New MemoryStream(PdfOrigen) - FirmaPDFVisible(StreamPfx, ClavePFX, ClavePDF, fso, ms, Razon, Contacto, Localizacion, listadoTextos, listadoImagenes, listadoFuentes, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, DatosCampoFirma, BloquearDocumento) + FirmaPDFVisible(StreamPfx, ClavePFX, ClavePDF, fso, pdfDestino, Razon, Contacto, Localizacion, listadoTextos, listadoImagenes, listadoFuentes, EliminarFirmasAnteriores, ObviarSeguridad, NombreCampoFirma, DatosCampoFirma, BloquearDocumento) End Sub diff --git a/TSPdfUtils/TSpdfUtils.vbproj b/TSPdfUtils/TSpdfUtils.vbproj index 20d046c..ffd9f6c 100644 --- a/TSPdfUtils/TSpdfUtils.vbproj +++ b/TSPdfUtils/TSpdfUtils.vbproj @@ -19,16 +19,19 @@ TSpdfUtils TSpdfUtils libreria, netstandard2.0 - 3.0.7.0 - 3.0.7.0 - 3.0.7.0 + 3.0.10.0 + 3.0.10.0 + 3.0.10.0 Manuel Tecnosis S.A Utilidades de tratamiento de pdf, de firmas digitales e imagenes. - - 3.0.7.0 2026-05-20 Se elimina PdfSignatureTools, Nuevo metodo ObtenerPropiedadDePdf - - 3.0.6.0 2026-05-20 Correccion PdfSignatureTools - - 3.0.5.0 2026-05-20 Nuevas rutinas en PdfSignatureTools + - 3.0.8.0 2026-08-03 Correcciones clase FirmaCanvas + - 3.0.8.0 2026-08-03 Se agregan Sobrecargas a la clase FirmaCanvas + - 3.0.8.0 2026-08-03 Se agrega clase FirmaCanvas + - 3.0.7.0 2026-08-01 Se elimina PdfSignatureTools, Nuevo metodo ObtenerPropiedadDePdf + - 3.0.6.0 2026-08-01 Correccion PdfSignatureTools + - 3.0.5.0 2026-08-01 Nuevas rutinas en PdfSignatureTools - 3.0.4.0 2026-05-20 Actualización tsPDF - 3.0.0.0 2026-05-20 Correcciones dependencias Microsoft.Extensions.Logging - 3.0.0.0 2025-10-23 Actualización de tspdfutilscore