Estoy haciendo una aplicación web usando Javascript para el front-end y así es como funciona:
Inicio la aplicación y abre una página web a través de mi navegador.
Muestra una página PDF obtenida de mi directorio.
Tengo la opción de hacer clic en un sello y arrastrar y mover el pdf y colocarlo donde quiera.
Cuando termine, podría hacer clic en Guardar y guardará automáticamente el archivo pdf en mi directorio.
Puedo abrir el archivo pdf en mi carpeta para ver el PDF actualizado junto con el sello agregado.
El problema es que cuando abro el archivo PDF para verlo, el posicionamiento no es idéntico al posicionamiento del sello en el navegador web.
window.dragMoveListener = dragMoveListener; interact('.signer-box') .draggable({ onmove: dragMoveListener, inertia: true, autoScroll: true, restrict: { elementRect: {top: 0, left: 0, bottom: 1, right: 1} } }) .resizable({ onmove: resizeMoveListener, inertia: true, edges: {left: true, right: true, bottom: true, top: true} }) function dragMoveListener(event) { var target = event.target; var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx; var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px, ' + y + 'px)'; target.setAttribute('data-x', x); target.setAttribute('data-y', y); computeSignerBoxPosition(); } function resizeMoveListener(event) { var target = event.target; var x = (parseFloat(target.getAttribute('data-x')) || 0); var y = (parseFloat(target.getAttribute('data-y')) || 0); x += event.deltaRect.left; y += event.deltaRect.top; target.style.width = event.rect.width + 'px'; target.style.height = event.rect.height + 'px'; target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px,' + y + 'px)'; target.setAttribute('data-x', x); target.setAttribute('data-y', y); computeSignerBoxPosition(); } function computeSignerBoxPosition() { var $signatureBox = $('.signer-box'); var sbDataX = parseFloat($signatureBox.attr('data-x')); var sbDataY = parseFloat($signatureBox.attr('data-y')); var sbOuterWidth = $signatureBox.outerWidth(); var sbOuterHeight = $signatureBox.outerHeight(); var w = $('#pdf-page').width(); var h = $('#pdf-page').height(); var top = sbDataX / w; var left = sbDataY / h; var width = sbOuterWidth / w; var height = sbOuterHeight / h; document.getElementById("widthValue").value = width; document.getElementById("heightValue").value = height; document.getElementById("coorX").value = top; document.getElementById("coorY").value = left; }
@charset "UTF-8"; #content{ text-align: center; } #pdf-container { display: inline-block; width: 100%; user-select: none; } #pdf-page { width: 100%; } .signer-box { background: url('../images/pen_icon.png') #29e no-repeat 50% 50%; background-size: 50%; color: white; font-size: 20px; font-family: sans-serif; border-radius: 8px; width: 180px; height: 150px; position:absolute; opacity: .8; box-sizing: border-box; box-shadow: rgb(0, 0, 0, 0.7) 0.2em 0.2em 0.5em; -ms-touch-action: none; touch-action: none; } #signature-pad { position: relative; width: 100%; height: 160px; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; user-select: none; } #signatureImg{ width: 100%; }
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <div id="content"> <div class="wrap"> <hr style="border:15px;"><hr style="border:2px;"> <div id="wrapper"> <div id="content"> <div id="pdf-container" > <div id="signers-list"> <div id="signer-1" class="signer-box"></div> </div> <img id="pdf-page" src="data:image/png;base64,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" /> </div> </div> </div> </table></form> <hr style="border:15px;"><hr style="border:2px;"> <div class="content"> <table id="customers"> <tr> <td> X: </td> <td> <input type="text" name="coorX" id="coorX" value="0" readonly="readonly"> </td> <td> h: </td> <td> <input type="text" name="heightValue" id="heightValue" value="150" readonly="readonly"> </td> </tr> <tr> <td> Y: </td> <td> <input type="text" name="coorY" id="coorY" value="0" readonly="readonly"> </td> <td> w: </td> <td> <input type="text" name="widthValue" id="widthValue" value="180" readonly="readonly"> </td> </tr> </table> <hr> </form> </div><!-- /.wrap --> </div><!-- /.content --> </div> <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.943/pdf.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.2.9/interact.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.943/pdf.worker.min.js'></script> </body> </html>
A continuación se muestra el código de back-end para manejar la parte de estampado usando iText:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { File file = new File("D:\\Documents\\pruebaPdf\\Ejemplo_Uno.pdf"); file.getParentFile().mkdirs(); PdfReader reader = new PdfReader("D:\\Documents\\pruebaPdf\\Ejemplo_Uno.pdf"); PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("D:\\Documents\\pruebaPdf\\Ejemplo_Dos.pdf")); Document document = new Document(); try { PdfContentByte cb = stamper.getOverContent(request.getParameter("page")); float top = Float.valueOf(request.getParameter("top")); float left = Float.valueOf(request.getParameter("left")); float width = Float.valueOf(request.getParameter("width")); float height = Float.valueOf(request.getParameter("height")); // Just in case, take into account page rotation Rectangle pdfRectangle = reader.getPageSizeWithRotation(1); float pdfWidth = pdfRectangle.getWidth(); float pdfHeight = pdfRectangle.getHeight(); float llx = pdfWidth * left; float lly = pdfHeight * (1 - top - height); // Until iText 5 this code should work float urx = llx + (pdfWidth * width); float ury = lly + (pdfHeight * height); Rectangle rect = new Rectangle(llx, lly, urx, ury); rect.setBorder(Rectangle.BOX); rect.setBorderWidth(1); rect.setBackgroundColor(BaseColor.GRAY); rect.setBorderColor(BaseColor.GREEN); cb.rectangle(rect); } catch (Exception e) { System.out.println("ERROR=>>>>>>" + e); }finally{ stamper.close(); reader.close(); document.close(); } }
Como se indica en esta pregunta complementaria , el objetivo es poder traducir la posición y la dimensión de su imagen en relación con el PDF entre su representación en el navegador y en el PDF real.
En este caso de uso específico, ya tiene una estructura de elementos bien definida, en la que su imagen de vista previa de PDF se muestra de manera predecible.
Siguiendo el consejo de la pregunta anterior, creo que debe tomar los puntos relevantes de su cuadro de firma, diga:
var $signatureBox = $('.signer-box'); var sbDataX = parseFloat($signatureBox.attr('data-x')); var sbDataY = parseFloat($signatureBox.attr('data-y')); var sbOuterWidth = $signatureBox.outerWidth(); var sbOuterHeight = $signatureBox.outerHeight();
Y conviértalos en porcentajes relativos al ancho y alto de su imagen PDF:
var w = $('#pdf-page').width(); var h = $('#pdf-page').height(); var top = sbDataY / h; var left = sbDataX / w; var width = sbOuterWidth / w; var height = sbOuterHeight / h;
He usado valores relativos a la unidad, por favor siéntete libre de multiplicarlos por 100
si prefieres trabajar con porcentajes. Solo tenga en cuenta en el siguiente paso.
Estos valores relativos top
, left
, width
y height
se enviarán a su backend.
Esta información se puede calcular en sus diferentes oyentes. Considere, por ejemplo, la definición de una función común que se utilizará para definir los valores de formulario correctos cuando se produzca un evento de arrastrar y soltar o de cambio de tamaño:
window.dragMoveListener = dragMoveListener; interact('.signer-box') .draggable({ onmove: dragMoveListener, inertia: true, autoScroll: true, restrict: { elementRect: {top: 0, left: 0, bottom: 1, right: 1} } }) .resizable({ onmove: resizeMoveListener, inertia: true, edges: {left: true, right: true, bottom: true, top: true} }) function dragMoveListener(event) { var target = event.target; var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx; var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px, ' + y + 'px)'; target.setAttribute('data-x', x); target.setAttribute('data-y', y); computeSignerBoxPosition(); } function resizeMoveListener(event) { var target = event.target; var x = (parseFloat(target.getAttribute('data-x')) || 0); var y = (parseFloat(target.getAttribute('data-y')) || 0); x += event.deltaRect.left; y += event.deltaRect.top; target.style.width = event.rect.width + 'px'; target.style.height = event.rect.height + 'px'; target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px,' + y + 'px)'; target.setAttribute('data-x', x); target.setAttribute('data-y', y); computeSignerBoxPosition(); } function computeSignerBoxPosition() { var $signatureBox = $('.signer-box'); var sbDataX = parseFloat($signatureBox.attr('data-x')); var sbDataY = parseFloat($signatureBox.attr('data-y')); var sbOuterWidth = $signatureBox.outerWidth(); var sbOuterHeight = $signatureBox.outerHeight(); var w = $('#pdf-page').width(); var h = $('#pdf-page').height(); var top = sbDataY / h; var left = sbDataX / w; var width = sbOuterWidth / w; var height = sbOuterHeight / h; document.getElementById("widthValue").value = width; document.getElementById("heightValue").value = height; document.getElementById("coorX").value = left; document.getElementById("coorY").value = top; }
Como se mencionó, la idea es calcular la información que debe enviarse al backend cuando ambos oyentes cambian. Como optimización, en lugar de calcular la información necesaria en el oyente, considere adjuntar al botón o elemento visual que está utilizando para enviar su formulario, un oyente de clics que invoque la función de cálculo de posición de caja de computeSignerBoxPosition
mencionada antes de enviar la información al backend, ya que solo es necesario en ese momento.
Con esa información, como se indica en la respuesta original, puede obtener la posición correcta como esta:
float top = Float.valueOf(request.getParameter("top")); float left = Float.valueOf(request.getParameter("left")); float width = Float.valueOf(request.getParameter("width")); float height = Float.valueOf(request.getParameter("height")); // Just in case, take into account page rotation Rectangle pdfRectangle = reader.getPageSizeWithRotation(1); float pdfWidth = pdfRectangle.getWidth(); float pdfHeight = pdfRectangle.getHeight(); float llx = pdfWidth * left; float lly = pdfHeight * (1 - top - height); // Until iText 5 this code should work float urx = llx + (pdfWidth * width); float ury = lly + (pdfHeight * height); // It seems that changed in Itext7 to this (they use just width and height) // If it is your use case, please, comment the block above // and uncomment the following lines // float urx = pdfWidth * width; // float ury = pdfHeight * height; Rectangle rect = new Rectangle(llx, lly, urx, ury); PdfStampAnnotation stamp = new PdfStampAnnotation(rect).setStampName(new PdfName("Approved")); PdfFormXObject xObj = new PdfFormXObject(new Rectangle(width,height)); PdfCanvas canvas = new PdfCanvas(xObj,doc); canvas.addImage(image,0,0,false);
El algoritmo tiene en cuenta que en PDF la esquina inferior izquierda de la página coincide con el origen del sistema de coordenadas (0, 0)
, donde los valores positivos de x
están a la derecha del origen y los valores positivos de y
están arriba del origen ( en contraste con el navegador):
El crédito de la imagen es para Bruno Lowagie cuando describe cómo interpretar las coordenadas de un rectángulo en PDF y dónde está el origen de una página PDF .
También es importante tener en cuenta que las dimensiones reales de su imagen y la página PDF en su representación en el navegador y el archivo PDF real deben ser constantes, deben conservar la relación de aspecto adecuada; por el contrario, necesita corregir sus dimensiones por el factor correspondiente.