Generar pdf desde HTML en div usando Javascript

232

Tengo el siguiente código html:

<!DOCTYPE html>
<html>
    <body>
        <p>don't print this to pdf</p>
        <div id="pdf">
            <p><font size="3" color="red">print this to pdf</font></p>
        </div>
    </body>
</html>

Todo lo que quiero hacer es imprimir en pdf lo que se encuentre en el div con una identificación de "pdf". Esto debe hacerse usando JavaScript. El documento "pdf" se debe descargar automáticamente con un nombre de archivo de "foobar.pdf"

He estado usando jspdf para hacer esto, pero la única función que tiene es "texto" que acepta solo valores de cadena. Quiero enviar HTML a jspdf, no texto.

John Crawford
fuente
1
Como se mencionó anteriormente, no quiero usar la función "texto". Quiero darle HTML. Su enlace solo trata con texto sin formato y no html
John Crawford
2
jsPDF hace tener una fromHTMLfunción; vea el ejemplo de " Representador
mg1075
Prueba el tutorial de jsPDF aquí freakyjolly.com/create-multipage-html-pdf-jspdf-html2canvas
Code Spy el

Respuestas:

228

jsPDF puede usar complementos. Para permitirle imprimir HTML, debe incluir ciertos complementos y, por lo tanto, debe hacer lo siguiente:

  1. Vaya a https://github.com/MrRio/jsPDF y descargue la última versión.
  2. Incluya los siguientes scripts en su proyecto:
    • jspdf.js
    • jspdf.plugin.from_html.js
    • jspdf.plugin.split_text_to_size.js
    • jspdf.plugin.standard_fonts_metrics.js

Si desea ignorar ciertos elementos, debe marcarlos con un ID, que luego puede ignorar en un controlador de elementos especial de jsPDF. Por lo tanto, su HTML debería verse así:

<!DOCTYPE html>
<html>
  <body>
    <p id="ignorePDF">don't print this to pdf</p>
    <div>
      <p><font size="3" color="red">print this to pdf</font></p>
    </div>
  </body>
</html>

Luego, usa el siguiente código JavaScript para abrir el PDF creado en una ventana emergente:

var doc = new jsPDF();          
var elementHandler = {
  '#ignorePDF': function (element, renderer) {
    return true;
  }
};
var source = window.document.getElementsByTagName("body")[0];
doc.fromHTML(
    source,
    15,
    15,
    {
      'width': 180,'elementHandlers': elementHandler
    });

doc.output("dataurlnewwindow");

Para mí, esto creó un PDF bonito y ordenado que solo incluía la línea 'imprimir esto a pdf'.

Tenga en cuenta que los controladores de elementos especiales solo tratan con ID en la versión actual, que también se indica en un problema de GitHub . Afirma:

Debido a que la comparación se realiza con cada elemento del árbol de nodos, mi deseo era hacerlo lo más rápido posible. En ese caso, significaba que "solo las ID de los elementos coinciden". Las ID de los elementos todavía se hacen en estilo jQuery "#id", pero no significa que todos los selectores jQuery sean compatibles.

Por lo tanto, reemplazar '#ignorePDF' con selectores de clase como '.ignorePDF' no funcionó para mí. En su lugar, tendrá que agregar el mismo controlador para todos y cada uno de los elementos, que desea ignorar como:

var elementHandler = {
  '#ignoreElement': function (element, renderer) {
    return true;
  },
  '#anotherIdToBeIgnored': function (element, renderer) {
    return true;
  }
};

De los ejemplos también se afirma que es posible seleccionar etiquetas como 'a' o 'li'. Sin embargo, eso podría ser un poco restrictivo para la mayoría de los casos de uso:

Apoyamos manipuladores de elementos especiales. Regístrelos con el selector de ID de estilo jQuery para ID o nombre de nodo. ("#iAmID", "div", "span", etc.) No hay soporte para ningún otro tipo de selectores (clase, compuesto) en este momento.

Una cosa muy importante para agregar es que pierde toda su información de estilo (CSS). Afortunadamente, jsPDF es capaz de formatear bien h1, h2, h3, etc., lo cual fue suficiente para mis propósitos. Además, solo imprimirá texto dentro de nodos de texto, lo que significa que no imprimirá los valores de áreas de texto y similares. Ejemplo:

<body>
  <ul>
    <!-- This is printed as the element contains a textnode -->        
    <li>Print me!</li>
  </ul>
  <div>
    <!-- This is not printed because jsPDF doesn't deal with the value attribute -->
    <input type="textarea" value="Please print me, too!">
  </div>
</body>
snrlx
fuente
3
¿Voy a adivinar que el controlador de elementos puede ser una clase? Eso sería mucho más semántico en línea con los estándares HTML5. Una identificación no solo tiene demasiada especificidad en CSS, sino que también debe ser única.
Imperativo el
No, hasta donde sé, las clases no son selectores válidos en este momento como se indica en sus ejemplos: "admitimos manejadores de elementos especiales. Regístrelos con el selector de ID de estilo jQuery para ID o nombre de nodo. (" #IAmID ", "div", "span", etc.) No hay soporte para ningún otro tipo de selectores (clase, compuesto) en este momento ". Aún así, es posible usar nombres de etiquetas si eso está en línea con su caso de uso y no oculta otros elementos importantes en su página.
snrlx
En su página parall.ax/products/jspdf afirman que son compatibles con IE6 + a través de una cuña. Sin embargo, no lo he probado.
snrlx
2
@snrlx Me sale un pdf en blanco y este error: renderer.pdf.sHashCode no es una función
Lisa Solomon
55
"Si desea ignorar ciertos elementos, debe marcarlos con una identificación", una biblioteca maravillosa, arruinada por esa exigencia invertida. El OP quería imprimir un solo <div>, que podría ser uno de cientos, por lo que tiene que marcar todos los elementos DOM no deseados.
Mawg dice que reinstale a Mónica el
53

Esta es la solución simple. Esto funciona para mí. Puede usar el concepto de impresión de JavaScript y simplemente guardarlo como pdf.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script type="text/javascript">
        $("#btnPrint").live("click", function () {
            var divContents = $("#dvContainer").html();
            var printWindow = window.open('', '', 'height=400,width=800');
            printWindow.document.write('<html><head><title>DIV Contents</title>');
            printWindow.document.write('</head><body >');
            printWindow.document.write(divContents);
            printWindow.document.write('</body></html>');
            printWindow.document.close();
            printWindow.print();
        });
    </script>
</head>
<body>
    <form id="form1">
    <div id="dvContainer">
        This content needs to be printed.
    </div>
    <input type="button" value="Print Div Contents" id="btnPrint" />
    </form>
</body>
</html>
vaibhav kulkarni
fuente
27
"y simple guardar esto como pdf" - Me perdí esa parte. ¿Cómo lo haces?
Mawg dice que reinstale a Mónica el
99
Esto funcionó para mí, para resolver el problema del estilo CSS, creé otro archivo css llamado printPDF.css y lo agregué usando una etiqueta de enlace como esta en el ejemplo anterior: printWindow.document.write ('<html> <head> <title> Contenido DIV </title> '); printWindow.document.write ('<link rel = "stylesheet" href = "../ css / printPDF.css" />'); printWindow.document.write ('</head> <body>');
Prime_Coder
2
Algunos comentarios: 1) No necesita una hoja de estilo específica para imprimir. En su hoja de estilo actual, haga algo como: @media print {.pageBreak {page-break-before: always; } .labelPdf {font-weight: bold; tamaño de fuente: 20px; } .noPrintPdf {display: none; }} y usa estas clases según tus necesidades. 2) .live ("clic", ...) no me funciona, así que en su lugar uso .on ("clic", ...).
Davidson Lima
1
@DavidsonLima este código crea una nueva ventana que no verá la ventana anterior css. Es por eso que está "ignorando" css, no está ignorando, solo en una nueva ventana no está cargado. Simplemente cárguelo en la cabeza y se renderizará.
Lukas Liesis
1
En caso de que alguien esté tratando de hacer que esto suceda con Angular, coloque su archivo CSS en la carpeta de activos.
rgantla
16

Puede usar autoPrint () y establecer la salida en 'dataurlnewwindow' de esta manera:

function printPDF() {
    var printDoc = new jsPDF();
    printDoc.fromHTML($('#pdf').get(0), 10, 10, {'width': 180});
    printDoc.autoPrint();
    printDoc.output("dataurlnewwindow"); // this opens a new popup,  after this the PDF opens the print window view but there are browser inconsistencies with how this is handled
}
Marco
fuente
1
Tengo curiosidad, ¿esto ha funcionado para alguien que no sea OP? Al leer el código, parece que entiendo que solo funcionaría con elementos que tienen una ID. Probablemente sea un poco más complicado que eso, de todos modos no tengo idea de cómo hacer que esto funcione.
Michael
14
Por lo que observé, irónicamente, fromHTML solo funciona si el elemento enviado como parámetro no contiene ningún HTML: solo se admite texto sin formato. Un poco mata el propósito de todo el asunto OMI.
Michael
Funcionó perfectamente para mí. No es necesario que el elemento que desea pasar tenga una ID. Esa es la forma en que el replicante encontró el nodo que quería pasar. Además, esta solución también funciona sin 'printDoc.autoPrint ()'. Si desea dejar esta línea específica en el código, debe incluir el complemento autoPrint.
snrlx
13

Si necesita descargar PDF de una página específica, simplemente agregue un botón como este

<h4 onclick="window.print();"> Print </h4>

use window.print () para imprimir toda su página, no solo un div

Wael Mahmoud
fuente
2
Solo una simple adición, si desea crear un pdf descargable de un iframe, use la consola del desarrollador: document.querySelector("#myIframe").contentWindow.print()
ioCron
11

Como se mencionó, debe usar jsPDF y html2canvas . También he encontrado una función dentro de los problemas de jsPDF que divide automáticamente su PDF en varias páginas ( fuentes )

function makePDF() {

    var quotes = document.getElementById('container-fluid');

    html2canvas(quotes, {
        onrendered: function(canvas) {

        //! MAKE YOUR PDF
        var pdf = new jsPDF('p', 'pt', 'letter');

        for (var i = 0; i <= quotes.clientHeight/980; i++) {
            //! This is all just html2canvas stuff
            var srcImg  = canvas;
            var sX      = 0;
            var sY      = 980*i; // start 980 pixels down for every new page
            var sWidth  = 900;
            var sHeight = 980;
            var dX      = 0;
            var dY      = 0;
            var dWidth  = 900;
            var dHeight = 980;

            window.onePageCanvas = document.createElement("canvas");
            onePageCanvas.setAttribute('width', 900);
            onePageCanvas.setAttribute('height', 980);
            var ctx = onePageCanvas.getContext('2d');
            // details on this usage of this function: 
            // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images#Slicing
            ctx.drawImage(srcImg,sX,sY,sWidth,sHeight,dX,dY,dWidth,dHeight);

            // document.body.appendChild(canvas);
            var canvasDataURL = onePageCanvas.toDataURL("image/png", 1.0);

            var width         = onePageCanvas.width;
            var height        = onePageCanvas.clientHeight;

            //! If we're on anything other than the first page,
            // add another page
            if (i > 0) {
                pdf.addPage(612, 791); //8.5" x 11" in pts (in*72)
            }
            //! now we declare that we're working on that page
            pdf.setPage(i+1);
            //! now we add content to that page!
            pdf.addImage(canvasDataURL, 'PNG', 20, 40, (width*.62), (height*.62));

        }
        //! after the for loop is finished running, we save the pdf.
        pdf.save('test.pdf');
    }
  });
}
BaptWaels
fuente
3
no convierte imágenes
Probosckie
1
gracias por la respuesta, ¿podría darnos alguna pista sobre cómo ponerlo en formato de página A4?
johannesMatevosyan
3
Esto realmente no es un buen vector pdf, crea muchos mapas de bits con el lienzo y los apila como imágenes. El resultado tiene muchas desventajas: grande, de baja calidad, no se puede copiar y pegar desde el PDF, etc.
Roman Zenka
6

Una forma es usar la función window.print (). Que no requiere ninguna biblioteca

Pros

1.No requiere biblioteca externa.

2. También podemos imprimir solo partes seleccionadas del cuerpo.

3.No hay conflictos de CSS y problemas de js.

4.Core funcionalidad html / js

--- Simplemente agregue el siguiente código

CSS para

@media print {
        body * {
            visibility: hidden; // part to hide at the time of print
            -webkit-print-color-adjust: exact !important; // not necessary use         
               if colors not visible
        }

        #printBtn {
            visibility: hidden !important; // To hide 
        }

        #page-wrapper * {
            visibility: visible; // Print only required part
            text-align: left;
            -webkit-print-color-adjust: exact !important;
        }
    }

Código JS - Llamada función bewlow en btn click

$scope.printWindow = function () {
  window.print()
}

Nota: Use! Important en cada objeto css

Ejemplo

.legend  {
  background: #9DD2E2 !important;
}
Rohit Parte
fuente
Hay problemas con la función de impresión de los navegadores. Los usuarios generalmente tienen opciones predeterminadas seleccionadas para la vista Imprimir (márgenes, tamaño de página y más). Por lo tanto, es muy difícil producir requerida PDF con un estilo requerido sin formación de los usuarios, que es mucho más difícil y aprox imposible ...
Rahmat Ali
4

Yo uso jspdf y html2canvas para renderizar css y exporto contenido de div específico ya que este es mi código

$(document).ready(function () {
    let btn=$('#c-oreder-preview');
    btn.text('download');
    btn.on('click',()=> {

        $('#c-invoice').modal('show');
        setTimeout(function () {
            html2canvas(document.querySelector("#c-print")).then(canvas => {
                //$("#previewBeforeDownload").html(canvas);
                var imgData = canvas.toDataURL("image/jpeg",1);
                var pdf = new jsPDF("p", "mm", "a4");
                var pageWidth = pdf.internal.pageSize.getWidth();
                var pageHeight = pdf.internal.pageSize.getHeight();
                var imageWidth = canvas.width;
                var imageHeight = canvas.height;

                var ratio = imageWidth/imageHeight >= pageWidth/pageHeight ? pageWidth/imageWidth : pageHeight/imageHeight;
                //pdf = new jsPDF(this.state.orientation, undefined, format);
                pdf.addImage(imgData, 'JPEG', 0, 0, imageWidth * ratio, imageHeight * ratio);
                pdf.save("invoice.pdf");
                //$("#previewBeforeDownload").hide();
                $('#c-invoice').modal('hide');
            });
        },500);

        });
});
ghazaleh javaheri
fuente
esto funciona pero convierte el contenido en imagen
Samad
Además, ¿cómo configurar el salto de página para que el contenido / imagen se imprima en una página nueva si no cabe en la página actual?
SHEKHAR SHETE
4
  • Sin dependencias, JS puro
  • Para agregar CSS o imágenes, no use URL relativas, use URL completas más http://...domain.../path.csso menos. Crea un documento HTML separado y no tiene contexto de lo principal.
  • también puedes incrustar imágenes como base64

Esto me sirvió durante años:

export default function printDiv({divId, title}) {
  let mywindow = window.open('', 'PRINT', 'height=650,width=900,top=100,left=150');

  mywindow.document.write(`<html><head><title>${title}</title>`);
  mywindow.document.write('</head><body >');
  mywindow.document.write(document.getElementById(divId).innerHTML);
  mywindow.document.write('</body></html>');

  mywindow.document.close(); // necessary for IE >= 10
  mywindow.focus(); // necessary for IE >= 10*/

  mywindow.print();
  mywindow.close();

  return true;
}
Lukas Liesis
fuente
1
El problema con esto es que el pdf no tendrá ningún efecto CSS.
Shadab Faiz
@ShadabFaiz Lo hará, pero puede que no sea lo mismo que la ventana principal. Todavía puede agregar CSS personalizado a través de este HTML también.
Lukas Liesis
Si. No estoy de acuerdo con usted, puede agregar CSS en la ventana, pero ¿cómo imprimir el resultado en el archivo PDF?
Mirko Cianfarani
1
Sin embargo, no renderiza imágenes.
Jan Pi
1
¡Me encanta esto! Algunos ajustes aquí y allá y se ve bien. Y una pequeña cosa no elimina el espacio adicional <body >que necesita: P
Phil Roggenbuck
2

Si desea exportar una tabla, puede ver esta muestra de exportación proporcionada por el widget Shield UI Grid.

Se hace extendiendo la configuración de esta manera:

...
exportOptions: {
    proxy: "/filesaver/save",
    pdf: {
        fileName: "shieldui-export",
        author: "John Smith",
        dataSource: {
            data: gridData
        },
        readDataSource: true,
        header: {
            cells: [
                { field: "id", title: "ID", width: 50 },
                { field: "name", title: "Person Name", width: 100 },
                { field: "company", title: "Company Name", width: 100 },
                { field: "email", title: "Email Address" }
            ]
        }
    }
}
...
Vladimir Georgiev
fuente
La funcionalidad "Exportar a PDF" resultó en un documento PDF vacío.
Richard Nalezynski
Probablemente configuración incorrecta en su extremo ... Si lo desea, publique una pregunta con la etiqueta shieldui
Vladimir Georgiev
2

Utilice pdfMake.js y este Gist .

(Encontré el Gist aquí junto con un enlace al paquete html-to-pdfmake , que termino sin usar por ahora).

Después npm install pdfmakey guardando el Gist en htmlToPdf.jslo uso así:

const pdfMakeX = require('pdfmake/build/pdfmake.js');
const pdfFontsX = require('pdfmake-unicode/dist/pdfmake-unicode.js');
pdfMakeX.vfs = pdfFontsX.pdfMake.vfs;
import * as pdfMake from 'pdfmake/build/pdfmake';
import htmlToPdf from './htmlToPdf.js';

var docDef = htmlToPdf(`<b>Sample</b>`);
pdfMake.createPdf({content:docDef}).download('sample.pdf');

Observaciones:

  • Mi caso de uso es crear el html relevante a partir de un documento de descuento (con markdown-it ) y luego generar el pdf y cargar su contenido binario (que puedo obtener con pdfMakela getBuffer()función de), todo desde el navegador. El pdf generado resulta ser mejor para este tipo de html que con otras soluciones que he probado.
  • Estoy insatisfecho con los resultados que obtuve de lo jsPDF.fromHTML()sugerido en la respuesta aceptada, ya que esa solución se confunde fácilmente con caracteres especiales en mi HTML que aparentemente se interpretan como una especie de marcado y confunden totalmente el PDF resultante.
  • Usar soluciones basadas en lienzo (como la jsPDF.from_html()función obsoleta , que no debe confundirse con la de la respuesta aceptada) no es una opción para mí, ya que quiero que el texto en el PDF generado sea pegable, mientras que las soluciones basadas en lienzo generan archivos PDF basados ​​en mapas de bits.
  • Los convertidores de reducción directa a pdf como md-a-pdf son solo del lado del servidor y no funcionarían para mí.
  • El uso de la funcionalidad de impresión del navegador no funcionaría para mí, ya que no quiero mostrar el PDF generado pero subir su contenido binario.
ilmiacs
fuente
Si leo el código correctamente, esto no admite estilos de borde CSS (por ejemplo, en tablas), ¿correcto?
ninjagecko
1

Pude obtener jsPDF para imprimir tablas creadas dinámicamente desde un div.

$(document).ready(function() {

        $("#pdfDiv").click(function() {

    var pdf = new jsPDF('p','pt','letter');
    var specialElementHandlers = {
    '#rentalListCan': function (element, renderer) {
        return true;
        }
    };

    pdf.addHTML($('#rentalListCan').first(), function() {
        pdf.save("caravan.pdf");
    });
    });
});

Funciona muy bien con Chrome y Firefox ... el formato está explotado en IE.

También incluí estos:

<script src="js/jspdf.js"></script>
    <script src="js/jspdf.plugin.from_html.js"></script>
    <script src="js/jspdf.plugin.addhtml.js"></script>
    <script src="//mrrio.github.io/jsPDF/dist/jspdf.debug.js"></script>
    <script src="http://html2canvas.hertzen.com/build/html2canvas.js"></script>
    <script type="text/javascript" src="./libs/FileSaver.js/FileSaver.js"></script>
    <script type="text/javascript" src="./libs/Blob.js/Blob.js"></script>
    <script type="text/javascript" src="./libs/deflate.js"></script>
    <script type="text/javascript" src="./libs/adler32cs.js/adler32cs.js"></script>

    <script type="text/javascript" src="js/jspdf.plugin.addimage.js"></script>
    <script type="text/javascript" src="js/jspdf.plugin.sillysvgrenderer.js"></script>
    <script type="text/javascript" src="js/jspdf.plugin.split_text_to_size.js"></script>
    <script type="text/javascript" src="js/jspdf.plugin.standard_fonts_metrics.js"></script>
Wasskinny
fuente
2
¿Puedes publicar un ejemplo de cómo pasas html
Erum
pero su código es <script src = " html2canvas.hertzen.com/build/html2canvas.js " > </ > lo que significa que necesita internet?
Erum
1
@Erum puedes descargar el archivo.
Eduardo Cuomo
0

Para capturar div como PDF, puede usar la solución https://grabz.it . Tiene una API de JavaScript que es fácil y flexible y le permitirá capturar el contenido de un único elemento HTML, como un div o un span

Para implementarlo, primero deberá obtener una clave y un secreto de la aplicación y descargar el SDK (gratuito).

Y ahora un ejemplo.

Digamos que tienes el HTML:

<div id="features">
    <h4>Acme Camera</h4>
    <label>Price</label>$399<br />
    <label>Rating</label>4.5 out of 5
</div>
<p>Cras ut velit sed purus porttitor aliquam. Nulla tristique magna ac libero tempor, ac vestibulum felisvulput ate. Nam ut velit eget
risus porttitor tristique at ac diam. Sed nisi risus, rutrum a metus suscipit, euismod tristique nulla. Etiam venenatis rutrum risus at
blandit. In hac habitasse platea dictumst. Suspendisse potenti. Phasellus eget vehicula felis.</p>

Para capturar lo que está debajo de la identificación de características, necesitará:

//add the sdk
<script type="text/javascript" src="grabzit.min.js"></script>
<script type="text/javascript">
//login with your key and secret. 
GrabzIt("KEY", "SECRET").ConvertURL("http://www.example.com/my-page.html",
{"target": "#features", "format": "pdf"}).Create();
</script>

Por favor tenga en cuenta el target: #feature. #feature¿eres tu selector de CSS, como en el ejemplo anterior? Ahora, cuando se carga la página, se creará una captura de pantalla de la imagen en la misma ubicación que la etiqueta del script, que contendrá todo el contenido de las funciones div y nada más.

Hay otra configuración y personalización que puedes hacer con el mecanismo de captura de pantalla div, por favor échale un vistazo aquí

Johnny
fuente