我遇到了将 html 转换为 pdf 的情况,幸运的是我可以通过 Flying Sacer api 实现这一点。但我的 HTML 由 svg 标签组成,在转换时我无法获取 pdf 中的 svg。可以使用 Stackoverflow 问题 来实现 和教程。
replacedElementFactory
是什么意思?
ChainingReplacedElementFactory chainingReplacedElementFactory
= new ChainingReplacedElementFactory();
chainingReplacedElementFactory.addReplacedElementFactory(replacedElementFactory);
chainingReplacedElementFactory.addReplacedElementFactory(new SVGReplacedElementFactory());
renderer.getSharedContext().setReplacedElementFactory(chainingReplacedElementFactory);
这只是教程中的一个错误,带有
replacedElementFactory
的行是不需要的。
这是我的工作示例。
Java:
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextRenderer;
public class PdfSvg {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document inputDoc = builder.parse("svg.html");
ByteArrayOutputStream output = new ByteArrayOutputStream();
ITextRenderer renderer = new ITextRenderer();
ChainingReplacedElementFactory chainingReplacedElementFactory = new ChainingReplacedElementFactory();
chainingReplacedElementFactory.addReplacedElementFactory(new SVGReplacedElementFactory());
renderer.getSharedContext().setReplacedElementFactory(chainingReplacedElementFactory);
renderer.setDocument(inputDoc, "");;
renderer.layout();
renderer.createPDF(output);
OutputStream fos = new FileOutputStream("svg.pdf");
output.writeTo(fos);
}
}
HTML:
<html>
<head>
<style type="text/css">
svg {display: block;width:100mm;height:100mm}
</style>
</head>
<body>
<div>
<svg xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3"
fill="red" />
</svg>
</div>
</body>
</html>
ChainingReplacedElementFactory
、SVGReplacedElement
和SVGReplacedElementFactory
来自教程。
如果您想要页面内解决方案,这里有一个使用@cloudformatter的替代方案,它是一种远程格式化服务。我将他们的 Javascript 以及一些文本和 Highchart 图表添加到您的小提琴中。
http://jsfiddle.net/yk0Lxzg0/1/
var click="return xepOnline.Formatter.Format('printme', {render:'download'})";
jQuery('#buttons').append('<button onclick="'+ click +'">PDF</button>');
上面放在小提琴中的代码会将带有“id”printme的div格式化为PDF以供下载。该 div 包括您的图表和一些文本。
http://www.cloudformatter.com/CSS2Pdf.APIDoc.Usage 显示了使用说明,并提供了更多 SVG 格式的图表示例,这些图表可以单独格式化为 PDF,也可以作为与文本、表格等结合的页面的一部分。
@Rajesh 我希望您已经找到问题的解决方案。如果没有(或者任何在使用飞碟、蜡染和 svg 标签时遇到问题的人)那么您可能需要考虑这一点 - 从
clip-path="url(#highcharts-xxxxxxx-xx)"
标签中删除所有 <g>
对我来说成功了。
将 svg 转换为 pdf 图像时可获得最佳结果:
class ResourceLoaderUserAgent extends ITextUserAgent {
private final String sessionId;
private final ITextOutputDevice outputDevice;
public ResourceLoaderUserAgent(ITextOutputDevice outputDevice) {
super(outputDevice, DEFAULT_DOTS_PER_PIXEL);
this.outputDevice = outputDevice;
}
@Override
public ImageResource getImageResource(String uriStr) {
if (uriStr.contains(".svg")) {
return getSVGImageAsPDF(uriStr);
} else {
return super.getImageResource(uriStr);
}
}
@Override
public byte[] getBinaryResource(String uri) {
if (uri.contains(".svg")) {
try(var is = resolveAndOpenStream(uri)) {
Transcoder transcoder = new PDFTranscoder();
TranscoderInput input = new TranscoderInput(is);
var os = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput(os);
transcoder.transcode(input, output);
return os.toByteArray();
} catch (Exception e) {
LOG.error("Could not get svg {} as pdf", uri, e);
return new byte[]{};
}
} else {
return super.getBinaryResource(uri);
}
}
private ImageResource getSVGImageAsPDF(String uriStr) {
try {
// Code from ITextUserAgent.getImageResource(String)
URI uri = new URI(uriStr);
PdfReader reader = outputDevice.getReader(uri);
PDFAsImage image = new PDFAsImage(uri);
Rectangle rect = reader.getPageSizeWithRotation(1);
image.setInitialWidth(rect.getWidth() * outputDevice.getDotsPerPoint());
image.setInitialHeight(rect.getHeight() * outputDevice.getDotsPerPoint());
return new ImageResource(uriStr, image);
} catch (URISyntaxException | IOException e) {
throw new RuntimeException(e);
}
}
}
依赖关系:
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.17</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>fop</artifactId>
<version>2.9</version>
</dependency>
我的代码引用了缺失的代码部分“SVGReplacedElementFactory”。
我这样使用它:
renderer
.getSharedContext()
.setReplacedElementFactory( new B64ImgReplacedElementFactory() );
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.codec.Base64;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.JPEGTranscoder;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.w3c.dom.Element;
import org.xhtmlrenderer.extend.FSImage;
import org.xhtmlrenderer.extend.ReplacedElement;
import org.xhtmlrenderer.extend.ReplacedElementFactory;
import org.xhtmlrenderer.extend.UserAgentCallback;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.pdf.ITextFSImage;
import org.xhtmlrenderer.pdf.ITextImageElement;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class B64ImgReplacedElementFactory implements ReplacedElementFactory
{
public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac, int cssWidth, int cssHeight)
{
Element e = box.getElement();
if(e == null)
{
return null;
}
String nodeName = e.getNodeName();
if(nodeName.equals("img"))
{
String attribute = e.getAttribute("src");
FSImage fsImage;
try
{
fsImage = buildImage(attribute, uac);
}
catch(BadElementException e1)
{
fsImage = null;
}
catch(IOException e1)
{
fsImage = null;
}
if(fsImage != null)
{
if(cssWidth != -1 || cssHeight != -1)
{
fsImage.scale(cssWidth, cssHeight);
}
return new ITextImageElement(fsImage);
}
}
return null;
}
protected FSImage buildImage(String srcAttr, UserAgentCallback uac) throws IOException, BadElementException
{
if(srcAttr.startsWith("data:image/"))
{
// BASE64Decoder decoder = new BASE64Decoder();
// byte[] decodedBytes = decoder.decodeBuffer(b64encoded);
// byte[] decodedBytes = B64Decoder.decode(b64encoded);
byte[] decodedBytes = Base64.decode(srcAttr.substring(srcAttr.indexOf("base64,") + "base64,".length(), srcAttr.length()));
return new ITextFSImage(Image.getInstance(decodedBytes));
}
FSImage fsImage = uac.getImageResource(srcAttr).getImage();
if(fsImage == null)
{
return convertToPNG(srcAttr);
}
return null;
}
private FSImage convertToPNG(String srcAttr) throws IOException, BadElementException
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PNGTranscoder t = new PNGTranscoder();
// t.addTranscodingHint(JPEGTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER, (25.4f / 72f));
t.addTranscodingHint(JPEGTranscoder.KEY_WIDTH, 4000.0F);
t.addTranscodingHint(JPEGTranscoder.KEY_HEIGHT, 4000.0F);
try
{
t.transcode(
new TranscoderInput(srcAttr),
new TranscoderOutput(byteArrayOutputStream)
);
}
catch(TranscoderException e)
{
e.printStackTrace();
}
byteArrayOutputStream.flush();
byteArrayOutputStream.close();
return new ITextFSImage(Image.getInstance(byteArrayOutputStream.toByteArray()));
}
public void remove(Element e)
{
}
@Override
public void setFormSubmissionListener(FormSubmissionListener formSubmissionListener)
{
}
public void reset()
{
}
}