Wygeneruj pdf z HTML w div za pomocą Javascript


232

Mam następujący kod 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>

Wszystko, co chcę zrobić, to wydrukować do pdf wszystko, co znajdzie się w div z identyfikatorem „pdf”. Należy to zrobić za pomocą JavaScript. Dokument „pdf” należy następnie automatycznie pobrać z nazwą pliku „foobar.pdf”

Użyłem do tego jspdf, ale jedyną jego funkcją jest „tekst”, który akceptuje tylko wartości ciągu. Chcę przesłać HTML do jspdf, a nie tekstu.


1
Jak wspomniano powyżej, nie chcę korzystać z funkcji „tekstowej”. Chcę dać to HTML. Twój link dotyczy tylko zwykłego tekstu, a nie HTML
John Crawford,

2
jsPDF nie posiada fromHTMLfunkcji; patrz przykład „Renderer HTML” pod adresem: mrrio.github.io/jsPDF
mg1075

Odpowiedzi:


228

jsPDF może korzystać z wtyczek. Aby umożliwić drukowanie HTML, musisz dołączyć pewne wtyczki i dlatego musisz wykonać następujące czynności:

  1. Wejdź na https://github.com/MrRio/jsPDF i pobierz najnowszą wersję.
  2. Dołącz następujące skrypty do swojego projektu:
    • jspdf.js
    • jspdf.plugin.from_html.js
    • jspdf.plugin.split_text_to_size.js
    • jspdf.plugin.standard_fonts_metrics.js

Jeśli chcesz zignorować niektóre elementy, musisz oznaczyć je identyfikatorem, który możesz następnie zignorować w specjalnym module obsługi jsPDF. Dlatego twój HTML powinien wyglądać tak:

<!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>

Następnie używasz następującego kodu JavaScript, aby otworzyć utworzony plik PDF w PopUp:

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");

Dla mnie stworzyło to ładny i schludny plik PDF, który zawierał tylko wiersz „wydrukuj to do pliku pdf”.

Należy pamiętać, że programy obsługi elementów specjalnych zajmują się tylko identyfikatorami w bieżącej wersji, co zostało również określone w numerze GitHub . W Stanach:

Ponieważ dopasowanie jest wykonywane względem każdego elementu w drzewie węzłów, moim pragnieniem było uczynienie go możliwie jak najszybszym. W takim przypadku oznaczało to „Dopasowywane są tylko identyfikatory elementów” Identyfikatory elementów są nadal wykonywane w stylu jQuery „#id”, ale nie oznacza to, że obsługiwane są wszystkie selektory jQuery.

Dlatego zastąpienie „#ignorePDF” selektorami klas, takimi jak „.ignorePDF”, nie działało dla mnie. Zamiast tego będziesz musiał dodać ten sam moduł obsługi dla każdego elementu, który chcesz zignorować:

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

Z przykładów wynika również, że można wybrać tagi takie jak „a” lub „li”. Jednak może to być nieco zbyt restrykcyjne w przypadku większości przypadków użycia:

Wspieramy specjalne elementy obsługi. Zarejestruj je za pomocą selektora identyfikatorów w stylu jQuery dla identyfikatora lub nazwy węzła. („#iAmID”, „div”, „span” itp.) Obecnie nie ma obsługi żadnego innego typu selektorów (klasy, związku).

Jedną bardzo ważną rzeczą do dodania jest to, że tracisz wszystkie informacje o swoim stylu (CSS). Na szczęście jsPDF jest w stanie ładnie sformatować h1, h2, h3 itd., Co było wystarczające do moich celów. Dodatkowo drukuje tylko tekst w węzłach tekstowych, co oznacza, że ​​nie drukuje wartości obszarów tekstowych i tym podobnych. Przykład:

<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>

3
Zgaduję, że moduł obsługi elementów może być klasą? Byłoby to znacznie bardziej semantycznie zgodne ze standardami HTML5. Identyfikator nie tylko ma zbyt dużą wagę specyficzną w CSS, ale także musi być unikalny.
Imperatyw

Nie, o ile mi wiadomo, klasy nie są w tej chwili prawidłowymi selektorami, jak podano w ich przykładach: „obsługujemy specjalne procedury obsługi elementów. Zarejestruj je za pomocą selektora identyfikatora w stylu jQuery dla identyfikatora lub nazwy węzła. („ #IAmID ”, „div”, „span” itp.) W tej chwili nie ma wsparcia dla żadnego innego typu selektorów (klasy, związku). ”. Mimo to można używać nazw znaczników, jeśli jest to zgodne z przypadkiem użycia i nie ukrywa innych ważnych elementów na stronie.
snrlx,

Na swojej stronie parall.ax/products/jspdf stwierdzają, że wspierają IE6 + za pomocą podkładki dystansowej. Jednak sam tego nie próbowałem.
snrlx,

2
@ snrlx Dostaję pusty pdf i ten błąd: renderer.pdf.sHashCode nie jest funkcją
Lisa Solomon

5
„Jeśli chcesz zignorować pewne elementy, musisz oznaczyć je identyfikatorem” - wspaniałą biblioteką, zniszczoną przez odwrócone wymagania. OP chciał wydrukować jeden <div>, który może być jednym z setek - więc musi zaznaczyć wszystkie niechciane elementy DOM?
Mawg mówi o przywróceniu Moniki

53

To jest proste rozwiązanie. To działa dla mnie. Możesz użyć koncepcji drukowania javascript i po prostu zapisać to jako 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>

27
„i po prostu zapisz to jako pdf” - tęskniłem za tą częścią. Jak ty to robisz?
Mawg mówi o przywróceniu Moniki

9
To zadziałało dla mnie, aby rozwiązać problem stylizacji CSS, stworzyłem inny plik css o nazwie printPDF.css i dodałem za pomocą znacznika link, tak jak w powyższym przykładzie: printWindow.document.write ('<html> <head> <title> DIV Contents </title> '); printWindow.document.write ('<link rel = "stylesheet" href = "../ css / printPDF.css" />'); printWindow.document.write ('</head> <body>');
Prime_Coder

2
Kilka komentarzy: 1) Nie potrzebujesz specjalnego arkusza stylów do drukowania. W bieżącym arkuszu stylów zrób coś takiego: @media print {.pageBreak {page-break-before: always; } .labelPdf {font-weight: bold; rozmiar czcionki: 20px; } .noPrintPdf {display: none; }} i użyj tych klas jako swoich potrzeb. 2) .live („kliknięcie”, ...) nie działa, więc zamiast tego używam .on („kliknięcie”, ...).
Davidson Lima,

1
@DavidsonLima ten kod tworzy nowe okno, w którym nie będzie widać starego okna css. Dlatego „ignoruje” css, nie ignoruje, tylko w nowym oknie nie jest ładowany. Wystarczy załadować go do głowy, a będzie renderowany.
Lukas Liesis

1
Na wypadek, gdyby ktoś próbował to zrobić w Angular, umieść plik CSS w folderze zasobów.
rgantla

16

Możesz użyć autoPrint () i ustawić wyjście na 'dataurlnewwindow' w następujący sposób:

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
}

1
Jestem ciekawy, czy to kiedykolwiek działało dla kogoś innego niż OP? Po przeczytaniu kodu wydaje mi się, że rozumiem, że będzie on działał tylko z elementami, które mają identyfikator. Prawdopodobnie jest to trochę bardziej skomplikowane, w każdym razie nie mam pojęcia, jak to zrobić.
Michael

14
Z tego, co zaobserwowałem, bardzo ironicznie, fromHTML działa tylko wtedy, gdy element wysłany jako parametr nie zawiera żadnego HTML: obsługiwany jest tylko zwykły tekst. Trochę zabija cel całego imo.
Michael

Działa dla mnie idealnie. Element, który chcesz przekazać, nie musi koniecznie mieć identyfikatora. W ten sposób replikator znalazł węzeł, który chciał przekazać. Ponadto to rozwiązanie działa również bez „printDoc.autoPrint ()”. Jeśli chcesz zostawić ten konkretny wiersz w kodzie, musisz dołączyć wtyczkę autoPrint.
snrlx

13

jeśli chcesz pobrać pdf konkretnej strony, po prostu dodaj przycisk w ten sposób

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

użyj window.print (), aby wydrukować całą stronę, a nie tylko div


2
Wystarczy prosty dodatek, jeśli chcesz utworzyć pdf iframe do pobrania, skorzystaj z konsoli programisty: document.querySelector("#myIframe").contentWindow.print()
ioCron

11

Jak wspomniano, powinieneś używać jsPDF i html2canvas . Znalazłem również funkcję w wydaniach jsPDF, która automatycznie dzieli twój pdf na wiele stron ( źródeł )

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');
    }
  });
}

3
nie konwertuje zdjęć
Probosckie

1
dzięki za odpowiedź, czy możesz podać jakąś wskazówkę, jak ustawić format strony A4?
johannesMatevosyan

3
Tak naprawdę nie tworzy dobrego wektorowego pliku pdf, tworzy wiele bitmap z płótnem i układa je jako obrazy. Wynik ma wiele wad - duży, niskiej jakości, nie można skopiować i wkleić z pliku PDF itp.
Roman Zenka

6

Jednym ze sposobów jest użycie funkcji window.print (). Który nie wymaga żadnej biblioteki

Plusy

1. Żadna zewnętrzna biblioteka nie wymaga.

2. Możemy również wydrukować tylko wybrane części ciała.

3. Brak konfliktów css i problemów z js.

4. rdzeń html / js funkcjonalność

--- Po prostu dodaj poniższy kod

CSS do

@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;
        }
    }

Kod JS - Wywołaj funkcję bewlow po kliknięciu btn

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

Uwaga: Użyj! Ważne w każdym obiekcie css

Przykład -

.legend  {
  background: #9DD2E2 !important;
}

Występują problemy z funkcją drukowania w przeglądarkach. Użytkownicy zazwyczaj mają domyślne opcje wybrane dla widoku Drukuj (marginesy, rozmiar strony i więcej). Dlatego bardzo trudno jest wygenerować wymagany plik PDF z wymaganym stylem bez przeszkolenia użytkownika, co jest znacznie trudniejsze i prawie niemożliwe ...
Rahmat Ali

4

Używam jspdf i html2canvas do renderowania css i eksportuję zawartość określonego div, ponieważ jest to mój kod

$(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);

        });
});

działa, ale konwertuje zawartość do obrazu
Samad

Jak ustawić podział strony, aby zawartość / obraz były drukowane na nowej stronie, jeśli nie zmieści się na bieżącej stronie?
SHEKHAR SHETE

4
  • Bez zależności, czysty JS
  • Aby dodać CSS lub obrazy - nie używaj względnych adresów URL, używaj pełnych adresów URL http://...domain.../path.csslub mniej więcej. Tworzy osobny dokument HTML i nie ma kontekstu głównego.
  • możesz również osadzać obrazy jako base64

Służyło mi to od lat:

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;
}

1
Problem polega na tym, że pdf nie będzie miał w nim żadnych efektów css.
Shadab Faiz

@ShadabFaiz Będzie, ale może nie być taki sam jak okno główne. Nadal możesz dodać niestandardowy css również do tego html.
Lukas Liesis

Tak. Nie zgadzam się z tym, że możesz dodać css w oknie, ale jak wydrukować wynik w pliku pdf?
Mirko Cianfarani

1
Jednak nie renderuje obrazów.
Jan Pi

1
Uwielbiam to! Kilka poprawek tu i tam i wygląda dobrze. I jedna drobna rzecz, która nie usuwa dodatkowych odstępów <body >, potrzebuje: P
Phil Roggenbuck

2

Jeśli chcesz wyeksportować tabelę, możesz rzucić okiem na tę próbkę eksportu dostarczoną przez widget Siatka interfejsu użytkownika osłony.

Odbywa się to poprzez rozszerzenie konfiguracji w następujący sposób:

...
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" }
            ]
        }
    }
}
...

Funkcja „Eksportuj do PDF” zaowocowała pustym dokumentem PDF.
Richard Nalezynski

Prawdopodobnie zła konfiguracja na twoim końcu ... Jeśli chcesz, zadaj pytanie z tagiem shieldui
Vladimir Georgiev

2

Użyj pdfMake.js i tego Gist .

(Znalazłem tutaj Gist wraz z linkiem do pakietu html-to-pdfmake , którego na razie nie używam.)

Po npm install pdfmakezapisaniu Gist htmlToPdf.jsi używam go w następujący sposób:

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');

Uwagi:

  • Mój przypadek użycia jest stworzenie odpowiedniego dokumentu html z wyprzedaży (z obniżki-it ), a następnie generowania PDF i przesłanie jej zawartości binarnej (które można uzyskać z pdfMake„s getBuffer()funkcji), wszystkie z przeglądarki. Wygenerowany plik PDF okazuje się być lepszy dla tego rodzaju plików HTML niż w przypadku innych rozwiązań, które wypróbowałem.
  • Jestem niezadowolony z wyników, które otrzymałem z jsPDF.fromHTML()sugerowanej w przyjętej odpowiedzi, ponieważ rozwiązanie to łatwo pomylić ze znakami specjalnymi w moim HTML, które najwyraźniej są interpretowane jako rodzaj znaczników i całkowicie psują wynikowy plik PDF.
  • Korzystanie z rozwiązań opartych na kanwie (takich jak przestarzała jsPDF.from_html()funkcja, których nie należy mylić z tą z zaakceptowanej odpowiedzi) nie jest dla mnie opcją, ponieważ chcę, aby tekst w wygenerowanym pliku PDF mógł być wklejany, podczas gdy rozwiązania oparte na kanwie generują pliki PDF na podstawie bitmapy.
  • Bezpośrednie obniżanie cen do konwerterów plików PDF, takich jak md-to-pdf, dotyczy tylko serwera i nie działałoby dla mnie.
  • Korzystanie z funkcji drukowania w przeglądarce nie działałoby dla mnie, ponieważ nie chcę wyświetlać wygenerowanego pliku PDF, ale przesłać jego zawartość binarną.

Jeśli poprawnie odczytam kod, nie obsługuje to stylów ramek CSS (np. W tabelach), prawda?
ninjagecko

1

Byłem w stanie zmusić jsPDF do drukowania dynamicznie tworzonych tabel z 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");
    });
    });
});

Działa świetnie z Chrome i Firefox ... formatowanie jest wysadzone w IE.

Dołączyłem także:

<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>

2
czy możesz
napisać

ale twój kod to <script src = " html2canvas.hertzen.com/build/html2canvas.js " > </… > co oznacza, że ​​potrzebuje internetu?
Erum,

1
@Erum możesz pobrać plik.
Eduardo Cuomo,

0

Aby przechwycić div jako PDF, możesz użyć rozwiązania https://grabz.it . Ma JavaScript API, który jest łatwy i elastyczny i pozwala uchwycić zawartość pojedynczego elementu HTML, takiego jak div lub span

Aby go zaimplementować, musisz najpierw uzyskać klucz aplikacji i klucz tajny oraz pobrać (bezpłatny) zestaw SDK.

A teraz przykład.

Powiedzmy, że masz 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>

Aby przechwycić zawartość identyfikatora funkcji, musisz:

//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>

Uwaga target: #feature. #featureczy jesteś selektorem CSS, jak w poprzednim przykładzie. Teraz, gdy strona zostanie załadowana, zrzut ekranu obrazu zostanie teraz utworzony w tej samej lokalizacji, co tag skryptu, który będzie zawierał całą zawartość div div i nic więcej.

Są inne konfiguracje i dostosowania, które możesz zrobić w mechanizmie div-screenshot, sprawdź je tutaj

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.