SVG-to-PDFKit
SVG-to-PDFKit copied to clipboard
Unable to convert svg with <image> tag to pdf
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>
hey @fubuk-i , Did you find the solution?
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.
I managed to get this working for both SVGs & raster images.
The strategy is:
- For SVGs (
<image href='path/image.svg'>
): download & embed the external SVGs into the printed SVG. - For PNGs/JPEGs (
<image href='path/image.png'>
): download them & serve using imageCallback.
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:
- https://github.com/alafr/SVG-to-PDFKit/blob/b091ebd4e7b7d2310eb1003511cd5de480f7e0e1/source.js#L1710
- https://github.com/foliojs/pdfkit/blob/b35c6f91799b969e570222f19ca4ef357c480aff/lib/mixins/images.js#L219
- https://github.com/foliojs/pdfkit/blob/b35c6f91799b969e570222f19ca4ef357c480aff/lib/image.js#L11