yWorks / svg2pdf.js

A javascript-only SVG to PDF conversion utility that runs in the browser. Brought to you by yWorks - the diagramming experts
MIT License
643 stars 96 forks source link

The exported PDF is not rendering custom fonts on some of the PDF text objects. #255

Open sauriio opened 1 year ago

sauriio commented 1 year ago

Describe the bug I'm using this library and fabricJS to make and exportable version of the canvas and have a pdf from there, I was able to add custom fonts and the source of the pdf generator is a svg export, but some of the pdf objects (once is generated) have missing font declarations, and i used some text styling to and it doesn't appear to be applied (underlining and striking) too.

P.S: the PDF file size is exponentially bigger than the SVG representation, any hints about this.

What version are you using (exact version of svg2pdf.js and jspdf)? svg2pdf.js -> 2.2.1 jspdf-> 2.5.1

To Reproduce

//add jspdf font files as follow, the font is an object holding all the font information //some of the code is removed but all should work

const [fontName] = font.file_name.split('.'); const fontFetchResponse = await Axios.get(font.file_url, { responseType: 'arraybuffer' }); pdf.addFileToVFS(font.file_name, Buffer.from(fontFetchResponse.data, 'binary').toString('base64')); pdf.addFont(font.file_name, fontName, 'normal'); pdf.setFont(fontName);

//then

const pdf = new jsPDF({ unit: 'in', putOnlyUsedFonts: true }); const imageInformation = canvas.toSVG({ suppressPreamble: true }); //add a page pdf.addPage([pageWidth, pageHeight], orientation); //adding the svg to pdf await pdf.svg(imageInformation, { x: 0, y: 0, width: pageWidth, height: pageHeight });

Expected behavior have the pdf objects with their respective font declaration, and styling

Desktop (please complete the following information):

Additional context Added fonts, svg file and exported PDF on this file

export_svg.zip

HackbrettXXX commented 1 year ago

Please reduce your SVG to just the text where the fonts are not applied.

sauriio commented 1 year ago

Here it is, there seems to be the same structure as the one that has the font applied, any ideas what could be happening with text styles too? canvas

sauriio commented 1 year ago

so I checked how we could implement the underlining and the line-through text decoration properties on the pdf, and we need to draw a line with the same color as the tspan style declaration but in order to do that, we need to get the text object width and height to add this to the x and y props and then draw from there, so I if you can guide me on how to get these props (style declaration, text rendered height and width) after we set the text and were specifically we need to set it on svg2pdf I will gladly do the work and testing needed @HackbrettXXX

HackbrettXXX commented 1 year ago

Thank you for reducing the SVG. Regarding the font issue: could also attach a PDF of the reduced SVG, please?

Regarding the underline and strike-through: a PR would be very much appreciated. The best location to put the code is probably somewhere in textchunk: https://github.com/yWorks/svg2pdf.js/blob/master/src/textchunk.ts#L72. The text position and width is already known there. One issue you might run into is that every tspan might get its own line and we might need to consolidate the lines for inline tspans. There is already a similar open issue: #226

sauriio commented 1 year ago

I checked the possible font issues and they seems to be related to the font weight declaration, in the svg these are custom fonts that are added as 'normal' font weights, so there's no match on both the use and font declaration, I will start working on the changes required to add the underlining.

insinfo commented 5 months ago

I'm having a related problem, I'm also using fabric.js and I need to generate a PDF from the SVG generated by canvas, the SVG is being generated and displayed correctly, I use some custom fonts, I managed to add the custom fonts and it's working If the text element has a tspan inside, but when I use a standard PDF font like Helvetica and Courier it doesn't work

pdf

modelo.pdf

svg


<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="637" height="1012" viewBox="301.5 50 637 1012" xml:space="preserve">
<desc>Created with Fabric.js 5.3.0</desc>
<defs>
</defs>
<g transform="matrix(1 0 0 1 620.5 556.5)" id="stage">
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" x="-318.5" y="-506" rx="0" ry="0" width="637" height="1012"/>
</g>
<g transform="matrix(4.41 0 0 4.41 512.63 234.05)" style="" id="itext-001c8nc3i">
        <text xml:space="preserve" font-family="Helvetica" font-size="20" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(254,0,0); fill-rule: nonzero; opacity: 1; white-space: pre;"><tspan x="-41.13" y="6.28">Helvetica</tspan></text>
</g>
<g transform="matrix(4.29 0 0 4.29 443.87 125.01)" style="" id="itext-001th0bi9">
        <text xml:space="preserve" font-family="Calibri" font-size="20" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1; white-space: pre;"><tspan x="-25.75" y="6.28">Calibri</tspan></text>
</g>
<g transform="matrix(4.34 0 0 4.34 730.56 125)" style="" id="itext-001s4e88y">
        <text xml:space="preserve" font-family="Roboto" font-size="20" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,216,0); fill-rule: nonzero; opacity: 1; white-space: pre;"><tspan x="-32.05" y="6.28">Roboto</tspan></text>
</g>
<g transform="matrix(3.43 0 0 3.43 533.1 334.93)" style="" id="itext-001od0816">
        <text xml:space="preserve" font-family="Gotham Condensed" font-size="20" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,188); fill-rule: nonzero; opacity: 1; white-space: pre;"><tspan x="-56.73" y="6.28">Gotham Condensed</tspan></text>
</g>
<g transform="matrix(4.18 0 0 4.18 596.4 444.54)" style="" id="itext-000kx8wdn">
        <text xml:space="preserve" font-family="Brush Script MT" font-size="20" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(250,116,0); fill-rule: nonzero; opacity: 1; white-space: pre;"><tspan x="-59.88" y="6.28">Brush Script MT</tspan></text>
</g>
<g transform="matrix(3.66 0 0 3.66 817.59 236.17)" style="" id="itext-000eqyaha">
        <text xml:space="preserve" font-family="Courier" font-size="20" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(207,0,254); fill-rule: nonzero; opacity: 1; white-space: pre;"><tspan x="-30" y="6.28">Texto</tspan></text>
</g>
</svg>

cracha_modelo

image

 <script src="assets/js/chart.js"></script>
    <script src="assets/js/fabric/5.3.0/fabric.js"></script>

    <script src="assets/js/jspdf/2.5.1/jspdf.umd.min.js"></script>
    <script src="assets/js/svg2pdf.js/2.2.3/svg2pdf.umd.min.js"></script>

    <script>       
        const { jsPDF } = window.jspdf      

        async function svgToPdf(svgElement, x, y, width, height, fileName) {
            await loadFont('assets/fonts/calibri/calibri.ttf', 'Calibri', 'normal', 'normal', '400');
            await loadFont('assets/fonts/roboto/roboto_regular.ttf', 'Roboto', 'normal', '400');
            await loadFont('assets/fonts/gotham/gotham_condensed_medium.ttf', 'Gotham Condensed', 'normal', '400');
            await loadFont('assets/fonts/brush_script/brush_script_mt.ttf', 'Brush Script MT', 'normal', '400');
            await loadFont('assets/fonts/arial/arial.ttf', 'Arial', 'normal', '400');
            await loadFont('assets/fonts/arial/arial_black.ttf', 'Arial Black', 'normal', '400');

            //orientation, unit, format
            const doc = new jsPDF('p', 'mm', [width, height]);         

            await doc
                .svg(svgElement, {
                    x: x,
                    y: y,
                    width: width,
                    height: height
                });

            doc.setFont('Helvetica', 'normal');
            doc.text("Texto Helvetica", 100, 100);
            // save the created pdf
            doc.save(fileName)
        }

        // [src] file 'calibri-normal.ttf', [name] 'calibri', [style] 'normal'
        async function loadFont(src, name, style, weight) {

            const dataBase64 = await urlContentToDataUri(src);
            const filename = src.split('\\').pop().split('/').pop();
            console.log(filename);
            console.log(name);

            var callAddFont = function () {
                this.addFileToVFS(filename, dataBase64);
                this.addFont(filename, name, style, weight);

            };
            jsPDF.API.events.push(['addFonts', callAddFont]);
        }

        function urlContentToDataUri(url) {
            return fetch(url)
                .then(response => response.blob())
                .then(blob => new Promise(callback => {
                    const reader = new FileReader();
                    reader.onload = function () { callback(this.result.substr(this.result.indexOf(',') + 1)) };
                    reader.readAsDataURL(blob);
                }));
        }

    </script>
insinfo commented 5 months ago

If I add the helvetica.ttf file as a custom font then it works

image

sauriio commented 5 months ago

I will add my findings here, you must add all the fonts used on the canvas to the PDF to be safe, even the safe fonts