alafr / SVG-to-PDFKit

Insert SVG into a PDF document created with PDFKit
MIT License
391 stars 110 forks source link

Unable to convert svg with <image> tag to pdf #126

Open fubuk-i opened 3 years ago

fubuk-i commented 3 years ago

I am using SVGtoPDF npm library to get svg element on pdf but for following svg object it is not working as <image> tag is creating problem.

ERROR Message:: SVGElemImage: failed to open image "http://174.138.12.68:4000/uploads/products/1597070187552-Ash%20Back.jpg" in PDFKit

My SVG data::

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="400" height="400" viewBox="0 0 400 400" xml:space="preserve">
<desc>Created with Fabric.js 2.7.0</desc>
<defs>
</defs>
<g transform="matrix(0.31 0 0 0.31 200 200)"  >
    <image style="stroke: none; stroke-width: 0; 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;"  xlink:href="http://174.138.12.68:4000/uploads/products/1597070187552-Ash%20Back.jpg" x="-512" y="-640" width="1024" height="1280"></image>
</g>
<g transform="matrix(1 0 0 1 206.5 263.5)"  >
<rect style="stroke: rgb(0,0,255); stroke-width: 3; stroke-dasharray: 7 7; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(230,230,230); fill-opacity: 0.8; fill-rule: nonzero; opacity: 0.2;"  x="-100" y="-100" rx="0" ry="0" width="200" height="200" />
</g>
<g clip-path="url(#CLIPPATH_4)"  >
<g transform="matrix(0.9 0 0 0.9 205 244.88)"  >
<clipPath id="CLIPPATH_4" >
    <rect transform="matrix(1 0 0 1 206.5 263.5)" x="-100" y="-100" rx="0" ry="0" width="200" height="200" />
</clipPath>
    <image style="stroke: none; stroke-width: 0; 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;"  xlink:href="http://174.138.12.68:4000/transparent_images/1598349009675-1589379674035GoogleFiveStarCust.Rating.pdf-1.svg" x="-111" y="-92" width="222" height="184"></image>
</g>
</g>
</svg>
genialkartik commented 1 year ago

hey @fubuk-i , Did you find the solution?

Moziezez commented 6 months ago

i have the same problem. Thinking about extracting the image-tags attributes and use them for x,y-coordinates and an img-id to replace the images at their position from my svg's on file system... since i have all the images in plain svg format already, but they got converted when i put them together in a new graphic... Other option could be to convert the svg to svg in a way that the image-tags would be replaced..... Or just find a library not struggeling with image-tags xD

Also succesfully converted y svg to png and then to pdf using svg2img lib, but it still suffers in quaity so far.

petrkotek commented 2 weeks ago

I managed to get this working for both SVGs & raster images.

The strategy is:

Example code:

import axios from 'axios'
    // (...)
    // svgNode is the SVGElement to be printed
    const svgToPrint = svgNode.cloneNode(true) as SVGElement // we clone it as we're going to do some in-place changes
    const imageTags = svgToPrint.querySelectorAll('image')
    const http = axios.create() // axios for http requests
    const hrefToData: { [key: string]: ArrayBuffer } = {} // map for storing raster images
    for (const imageTag of imageTags) { // iterate over all image tags
      const href = imageTag.getAttribute('href')
      if (href?.endsWith('.svg')) {
        // download svgs & embed them into the original svg
        const img = await http.get(href)
        const parser = new DOMParser()
        const newSvg = parser.parseFromString(img.data, 'image/svg+xml').getElementsByTagName('svg')[0]

        for (const attrName of ['x', 'y', 'width', 'height']) {
          const val = imageTag.getAttribute(attrName)
          if (val !== null) {
            newSvg.setAttribute(attrName, val)
          }
        }
        imageTag.replaceWith(newSvg)
      } else if (href !== null) {
        // download other images (pngs, jpegs, ..) as "ArrayBuffer" (binary) and remember them
        const imgResponse = await http.get(href, { responseType: 'arraybuffer' })
        hrefToData[href] = imgResponse.data
      }
    }

    const doc = new (window as any).PDFDocument({ size: paperSize, layout })
    const chunks: any[] = []
    doc.pipe({
      write: (chunk: any) => chunks.push(chunk),
      end: () => {
        const pdfBlob = new Blob(chunks, {
          type: 'application/octet-stream'
        })
        window.open(URL.createObjectURL(pdfBlob))
      },
      on: () => {},
      once: () => {},
      emit: () => {}
    })

    SVGtoPDF(doc, svgToPrint.outerHTML, 0, 0, {
      // provide the raster images using this callback
      imageCallback: (href) => {
        return hrefToData[href]
      }
    })

    doc.end()

For reference, here are some code pointers in the SVG-to-PDFKit and PDFKit handling the images: