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