svg2pdf.js icon indicating copy to clipboard operation
svg2pdf.js copied to clipboard

Gradients won't render as gradients in the PDF

Open henrikskar opened this issue 9 years ago • 4 comments

Hi,

SVGs won't render in the PDF with simple gradients. I added a method to fill the svg node to the first stop color and a test (test19.html) to the test folder. Is there a way to make the svg2pdf.js render this simple gradient?

Best regards, Henrik Skar

henrikskar avatar Sep 14 '16 13:09 henrikskar

@henrikskar The bug is probably not related to the gradientTransform attribute as suggested in the pull request.

Two changes makes the gradient work in PDF:

  1. Add x1, x2, y1 and y2 arguments. These are usually added, but perhaps svgtopdf should have the same defaults as SVG.
  2. Set the stop-opacity to 1 on both stops. Seems liks opacity is handled incorrectly if supported at all.

Minimal, working code with current svg2pdf, modified from test19:

        <linearGradient id="MyGradient" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0" stop-color="#7cb5ec" stop-opacity="1"/>
          <stop offset="1" stop-color="#ffffff" stop-opacity="1"/>
        </linearGradient>

TorsteinHonsi avatar Sep 14 '16 20:09 TorsteinHonsi

Regarding opacity: AFAIK PDF does not support different opacities per gradient stop. A global opacity can be applied to the whole gradient, though. The only workaround is to precalculate the fill as a bitmap, which is costly both in terms of processing, resulting pdf size and quality. So we decided to not support opacity for gradients for now. Pull requests are welcome, of course.

yGuy avatar Sep 15 '16 06:09 yGuy

Thanks for your clarification @yGuy, that makes sense. It seems to leave us with some options:

  1. Apply a global opacity to the whole gradient, like svg2pdf.js currently does.
  2. Precalculate the fill as a bitmap. If I remember correctly, Batik uses this approach. In capable browsers, we could probably create an SVG with the filled shape only, draw it on a canvas, serialize it to PNG and add it to the PDF.
  3. Add a gradient only if all stops' opacity is 1. It seems like this is the approach PhantomJS is using when saving to PDF.
  4. If there's a stop with opacity 1, use that as the solid fill. Which would probably work best for our purpose.

If we want to implement this, could we add an option for it? Something in the lines of:

// render the svg element
svg2pdf(svgElement, pdf, {
    xOffset: 0,
    yOffset: 0,
    scale: 1,
    gradientOpacity: 'first-solid' // or 'bitmap', 'average'
});

TorsteinHonsi avatar Sep 15 '16 19:09 TorsteinHonsi

I like the proposed API - we could discuss whether adding another level in between like this is more future-proof, but I don't really have a strong opinion here, now:

// render the svg element
svg2pdf(svgElement, pdf, {
    xOffset: 0,
    yOffset: 0,
    scale: 1,
    emulation: {
        gradientOpacity: 'first-solid' // or 'bitmap', 'average'
    }
});

... in general I like the idea of making this configurable that way instead of hard-coding a single logic.

yGuy avatar Sep 16 '16 06:09 yGuy

Closing this, feel free to reopen ;)

HackbrettXXX avatar Dec 11 '23 09:12 HackbrettXXX