Есть ли способ преобразовать элемент SVG в PDF, сохранив его свойства CSS?

У меня есть график svg, созданный с использованием библиотеки d3 и стилизованный с использованием css. Я хотел бы преобразовать этот график в PDF, сохранив его свойства CSS.

Я пробовал разные подходы, но ни один из них, похоже, не захватил svg с его атрибутами стиля. В настоящее время я использую html2canvas и jsPDF для создания холста и сохранения его в формате PDF. Я включил код для минимального воспроизводимого примера стилизованного графика + код, который мне сейчас нужен для создания PDF-файла.

<script src = "https://d3js.org/d3.v5.js"></script>

<!-- jQuery CDN -->
<script src = "http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type = "text/javascript">
</script>

<!-- html2canvas CDN -->
<script src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js" type = "text/javascript">

<!-- jsPDF CDN -->
<script src = "https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.debug.js"
    integrity = "sha384-NaWTHo/8YCBYJ59830LTz/P4aQZK1sS0SneOgAvhsIl3zBu8r9RevNg5lHCHAuQ/" crossorigin = "anonymous">
</script>

<script>
    function genPDF() {
        // gets the quality based on the value that is input in the field
        if (quality = document.getElementById("quality").value) {
            // window.devicePixelRatio = 2; 
            html2canvas(document.getElementById("svg1"), {
                // scale based on quality value input
                scale: quality,
                // logging for debugging
                logging: true,
                letterRendering: 1,
                // allows for cross origin images
                allowTaint: true,
                useCORS: true
            }).then(function (canvas) {
                // creating a canvas with page data 
                var img = canvas.toDataURL('image/png');
                // creating a portrait a4 page
                var doc = new jsPDF('l', 'mm', 'a4');
                // setting the width of the page to auto
                const imgProps = doc.getImageProperties(img);
                const pdfWidth = doc.internal.pageSize.getWidth();
                const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
                // adding the image canvas to the pdf and saving
                doc.addImage(img, 'PNG', 2, 2, pdfWidth, pdfHeight);
                doc.save('generatedPDF.pdf');
            });
        } else {
            // throw if no scale value is entered
            throw "Please enter a scale value!";
        }
    }
</script>
<style>

    rect {
        fill: #868e96;
        opacity: 0.3;
    }
</style>

<body>
<form>
    Enter scale quality:<br>
    <input type = "text" id = "quality" name = "quality" placeholder = "1 - 5">
</form>
<button onclick = "genPDF()">Generate PDF</button>
    <svg id = "svg1" width = "1000" height = "700">
        <g id = "elementsContainer">

            <rect x = "25" y = "25" width = "240" height = "240" />
            <rect x = "275" y = "25" width = "240" height = "240" />
            <rect x = "25" y = "275" width = "240" height = "240" />
            <rect x = "275" y = "275" width = "240" height = "240" />
   </g>
</svg>
</body>

Я просто получаю черный нестилизованный график.

Редактировать: Я попытался добавить функцию тайм-аута, которая до сих пор не решает проблему:

function genPDF() {
        // gets the quality based on the value that is input in the field
        if (quality = document.getElementById("quality").value) {
            // window.devicePixelRatio = 2; 
            html2canvas(document.getElementById("apply"), {
                // scale based on quality value input
                scale: quality,
                // logging for debugging
                logging: true,
                letterRendering: 1,
                // allows for cross origin images
                allowTaint: true,
                useCORS: true
            }).then(function (canvas) {
                setTimeout(function () {
                    var img = canvas.toDataURL('image/png');
                    var doc = new jsPDF('l', 'mm', 'a4');
                    const imgProps = doc.getImageProperties(img);
                    const pdfWidth = doc.internal.pageSize.getWidth();
                    const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
                    doc.addImage(img, 'PNG', 2, 2, pdfWidth, pdfHeight);
                    doc.save('generatedPDF.pdf');
                }, 3000);
            });
        } else {
            // throw if no scale value is entered
            throw "Please enter a scale value!";
        }
    }

Редактировать: решение, которое я нашел, работает лучше всего, просто добавьте свойства стиля, встроенные в теги SVG.

У меня была аналогичная проблема, и это была проблема времени. Можете ли вы подтвердить это, добавив setTimeout к вашему коду.

Akash Tomar 09.07.2019 15:24

@ user2507, не могли бы вы указать, где добавить тайм-аут и, возможно, как долго. Я попытался добавить тайм-аут сам, и это ничего не меняет.

Aadil Anil Kumar 09.07.2019 15:36

Поместите его туда, где вы разрешаете обещание, просто поместите его на 1 секунду.

Akash Tomar 09.07.2019 15:38

нет, поместите его в метод .then.

Akash Tomar 09.07.2019 15:40

then(function (canvas) { setTimeout(function () { var img = canvas.toDataURL('image/png'); var doc = new jsPDF('l', 'mm', 'a4'); .... .. doc.addImage(img, 'PNG', 2, 2, pdfWidth, pdfHeight); doc.save('generatedPDF.pdf'); }, 3000); Я попробовал это, и это все равно не сработало :( @user2507

Aadil Anil Kumar 09.07.2019 15:46

Какой браузер вы используете?

Akash Tomar 09.07.2019 15:53

Я использую гугл хром.

Aadil Anil Kumar 09.07.2019 15:56

Ваш код выглядит хорошо для меня. Проверьте этот ответ здесь: stackoverflow.com/a/27370207/6579047

Akash Tomar 09.07.2019 15:57
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
8
1 783
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Возможно, вы захотите рассмотреть решение на стороне сервера. cairosvg (https://cairosvg.org/) звучит так, как будто он должен справиться с этим. Он поддерживает стили css для svg. Хотя я стараюсь избегать решений на стороне сервера, обычно используются конечные точки сервера для обработки форматирования, преобразования и печати изображений. Это также имеет то преимущество, что для этого не требуется большая часть браузера, поэтому вам не придется сталкиваться с проблемами совместимости браузера или использования памяти.

Ответ принят как подходящий

Вам нужно добавить стили CSS в контейнер SVG:

SVG в Canvas с помощью d3.js

    function getGraphImage(container) {
        addCssStyles(container);
        var svgChart = d3.select("#" + container + " svg");
        svgChart.attr('width', svgChart.node().clientWidth);
        svgChart.attr('height', svgChart.node().clientHeight);

        //Get svg markup as string
        var svg = document.getElementById(container).innerHTML;

        if (svg)
            svg = svg.replace(/\r?\n|\r/g, '').trim();

        var canvas = document.createElement('canvas');
        var context = canvas.getContext('2d');

        context.clearRect(0, 0, canvas.width, canvas.height);
        canvg(canvas, svg);

        return canvas.toDataURL('image/png');
    }

    function addCssStyles(container) {
        // get styles from all required stylesheets
        // http://www.coffeegnome.net/converting-svg-to-png-with-canvg/
        // https://stackoverflow.com/questions/11567668/svg-to-canvas-with-d3-js
        var style = "\n";
        var requiredSheets = ['nv.d3.css']; // list of required CSS
        for (var i = 0; i < document.styleSheets.length; i++) {
            var sheet = document.styleSheets[i];
            if (sheet.href) {
                var sheetName = sheet.href.split('/').pop();
                if (requiredSheets.indexOf(sheetName) !== -1) {
                    var rules = sheet.rules;
                    if (rules) {
                        for (var j = 0; j < rules.length; j++) {
                            style += (rules[j].cssText + '\n');
                        }
                    }
                }
            }
        }

        d3.select("#" + container + " svg")
                .insert('defs', ":first-child");
        d3.select("#" + container + " svg defs")
                .append('style')
                .attr('type', 'text/css')
                .html(style);
    }

затем вы просто добавляете изображение с помощью addImage():

    var doc = new jsPDF('p', 'mm', 'letter');
    doc.addImage(getGraphImage(containerID), 'PNG', x, y, w, h);
    doc.output('dataurlnewwindow');

Другие вопросы по теме