Multi-page PDF generation from SVG with Java and Apache Batik - java

I have two simple SVG documents that I want to convert to a PDF such that each document is on one page in the PDF.
My first SVG document has two rectangles that look as follow:
and the second one is a black circle.
The code looks as follow:
import java.io.*;
import org.apache.batik.anim.dom.*;
import org.apache.batik.transcoder.*;
import org.w3c.dom.*;
public class MultiPagePdf {
public static void main(String[] args) {
MultiPagePDFTranscoder transcoder = new MultiPagePDFTranscoder();
try {
final DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
SVGOMDocument doc1 = (SVGOMDocument) impl.createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null);
// 1st rectangle in doc1
Element el = doc1.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "rect");
el.setAttributeNS(null, "width", "60");
el.setAttributeNS(null, "height", "60");
el.setAttributeNS(null, "fill", "none");
el.setAttributeNS(null, "stroke", "blue");
SVGOMSVGElement docEl = (SVGOMSVGElement) doc1.getDocumentElement();
docEl.appendChild(el);
// 2nd rectangle in doc1
Element ell = doc1.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "rect");
ell.setAttributeNS(null, "x", "50");
ell.setAttributeNS(null, "y", "50");
ell.setAttributeNS(null, "width", "25");
ell.setAttributeNS(null, "height", "25");
ell.setAttributeNS(null, "fill", "green");
docEl.appendChild(ell);
final DOMImplementation impl2 = SVGDOMImplementation.getDOMImplementation();
SVGOMDocument doc2 = (SVGOMDocument) impl2.createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null);
// circle in doc2
Element el2 = doc2.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "circle");
el2.setAttributeNS(null, "cx", "130");
el2.setAttributeNS(null, "cy", "100");
el2.setAttributeNS(null, "r", "50");
SVGOMSVGElement docEl2 = (SVGOMSVGElement) doc2.getDocumentElement();
docEl2.appendChild(el2);
OutputStream outputStream = new FileOutputStream(new File("/C:/Users/ah/Documents/simpleMulti.pdf"));
TranscoderOutput transcoderOutput = new TranscoderOutput(outputStream);
Document[] doccs = { doc1, doc2 };
transcoder.transcode(doccs, null, transcoderOutput); // generate PDF doc
} catch (Exception e) {
e.printStackTrace();
}
}
}
I've printed both SVG documents and they look as they should:
1st SVG Document:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="text/ecmascript" zoomAndPan="magnify" contentStyleType="text/css" preserveAspectRatio="xMidYMid meet" version="1.0">
<rect fill="none" width="60" height="60" stroke="blue"/>
<rect fill="green" x="50" width="25" height="25" y="50"/>
</svg>
2nd SVG Document:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="text/ecmascript" zoomAndPan="magnify" contentStyleType="text/css" preserveAspectRatio="xMidYMid meet" version="1.0">
<circle r="50" cx="130" cy="100"/>
</svg>
I found a code that should be doing what I'm after using Apache FOP; I have the following code to generate the PDF:
import java.io.*;
import java.util.*;
import org.apache.batik.transcoder.*;
import org.apache.fop.*;
import org.apache.fop.svg.*;
import org.w3c.dom.*;
public class MultiPagePDFTranscoder extends AbstractFOPTranscoder {
protected PDFDocumentGraphics2D graphics = null;
protected Map<String, Object> params = null;
public MultiPagePDFTranscoder() {
super();
}
protected void transcode(Document[] documents, String uri, TranscoderOutput output) throws TranscoderException {
graphics = new PDFDocumentGraphics2D(isTextStroked());
graphics.getPDFDocument().getInfo().setProducer("Apache FOP Version " + Version.getVersion() + ": PDF Transcoder for Batik");
try {
OutputStream out = output.getOutputStream();
if (!(out instanceof BufferedOutputStream)) {
out = new BufferedOutputStream(out);
}
for (int i = 0; i < documents.length; i++) {
Document document = documents[i];
super.transcode(document, uri, null);
int tmpWidth = 300;
int tmpHeight = 300;
if (i == 0) {
graphics.setupDocument(out, tmpWidth, tmpHeight);
} else {
graphics.nextPage(tmpWidth, tmpHeight);
}
graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
graphics.transform(curTxf);
this.root.paint(graphics);
}
graphics.finish();
} catch (IOException ex) {
throw new TranscoderException(ex);
}
}
}
The PDF file is generated, but I have two problems with it.
The translation and scale of the SVG elements are not correct.
On the second page, the first document is present (I've tried with multiple pages, and all the previous documents are present on the current page).
I use Apache FOP 2.1 with Apache Batik 1.8.
Any help with either problem would be highly appreciated.
I'm also open to other solutions to my overall task (converting SVGs to multi-paged PDF).

I had a similar problem. The way I went about it was to use the SVGConverter app in Batik (org.apache.batik.apps.rasterizer.SVGConverter) to convert the single SVG to PDF then stick them together into one file using PDFBox (org.apache.pdfbox.multipdf.PDFMergerUtility).
Convert SVG to single page PDF(s):
File outputFile = new File(pdfPath);
SVGConverter converter = new SVGConverter();
converter.setDestinationType(DestinationType.PDF);
converter.setSources(new String[] { svgPath });
converter.setDst(outputFile);
converter.execute();
Join the PDFs together:
File pdffile = new File(multipagepdfPath);
PDFMergerUtility pdf = new PDFMergerUtility();
pdf.setDestinationFileName(pdffile.getPath());
pdf.addSource(page1pdffile);
pdf.addSource(page2pdffile);
pdf.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
//The memory settings depend on if you want to use RAM or Temp files.
If you find a better solution please let me know.
The main problem I had was that the converter app is the only one in Batik that converts to pdf and keeps the sizes correctly.

rsvg-convert can turn multiple svg-documents into a single PDF.
rsvg-convert -f pdf -o out.pdf file1.svg file2.svg file3.svg
or for all files in a folder:
rsvg-convert -f pdf -o out.pdf *.svg
Don't know how to do this with Java though. Just a thought...

Related

How to get the drawings from the apache POI XWPFDocument?

I tried to get the drawings from the XWPFDocument by this way(My data.docx only contains one rectangle and it's text).
XWPFDocument wordDocumentObj = new XWPFDocument(new FileInputStream(new File("data.docx")));
Iterator<IBodyElement> bodyElementIterator = wordDocumentObj.getBodyElementsIterator();
while(bodyElementIterator.hasNext()){
IBodyElement element = bodyElementIterator.next();
if (element instanceof XWPFParagraph) {
XWPFParagraph paragrapObj = (XWPFParagraph)element;
for(IRunElement irunObj : paragrapObj.getIRuns()) {
XWPFRun runObj = (XWPFRun)irunObj;
// I read whole the API doc, I think it is the only way to get the drawings
System.out.println(runObj.getCTR().getDrawingList());// No element returned
System.out.println(runObj.getCTR().getDrawingArray());// No element returned
}
}
}
Do you have any idea to get the drawings from the XWPFDocument?
Updated: The XML content of XWPFRun. I tried to extract the word file. There is no image in the /word/* directory:
<xml-fragment >
<mc:AlternateContent>
<mc:Choice Requires="wps">
<w:drawing>
<wp:anchor>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
<wps:wsp>
<wps:txbx>
<w:txbxContent>
<w:p w14:paraId="2744738E" w14:textId="0811E43C" w:rsidR="00832A19" w:rsidRDefault="00832A19" w:rsidP="00832A19">
<w:r>
<w:t>Some text here</w:t>
</w:r>
</w:p>
</w:txbxContent>
</wps:txbx>
</wps:wsp>
</a:graphicData>
</a:graphic>
</wp:anchor>
</w:drawing>
</mc:Choice>
<mc:Fallback>
<w:pict>
<v:rect w14:anchorId="684D682E" id="Rectangle 2" o:spid="_x0000_s1026" style="" fillcolor="#4f81bd [3204]" strokecolor="#243f60 [1604]" strokeweight="2pt">
<v:textbox>
<w:txbxContent>
<w:p w14:paraId="2744738E" w14:textId="0811E43C" w:rsidR="00832A19" w:rsidRDefault="00832A19" w:rsidP="00832A19">
<w:r>
<w:t>Some text here</w:t>
</w:r>
</w:p>
</w:txbxContent>
</v:textbox>
</v:rect>
</w:pict>
</mc:Fallback>
</mc:AlternateContent>
</xml-fragment>
Your provided XML shows, your Word document uses alternate content which was introduced after publishing Office Open XML in 2007. So apache poi does not provide methods to get that content as it only provides methods for Office Open XML according standard ECMA-376. That is because the underlying ooxml-schemas were created from that ECMA-376 standard only.
So the drawing elements in the AlternateContent elements only can be got using XML (XPath) methods directly.
This could look like so:
import java.io.FileInputStream;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlCursor;
import java.util.List;
import java.util.ArrayList;
public class WordGetAllDrawingsFromRuns {
private static List<CTDrawing> getAllDrawings(XWPFRun run) throws Exception {
CTR ctR = run.getCTR();
XmlCursor cursor = ctR.newCursor();
cursor.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//*/w:drawing");
List<CTDrawing> drawings = new ArrayList<CTDrawing>();
while (cursor.hasNextSelection()) {
cursor.toNextSelection();
XmlObject obj = cursor.getObject();
CTDrawing drawing = CTDrawing.Factory.parse(obj.newInputStream());
drawings.add(drawing);
}
return drawings;
}
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument(new FileInputStream("WordDocument.docx"));
for (IBodyElement bodyElement : document.getBodyElements()) {
if (bodyElement instanceof XWPFParagraph) {
XWPFParagraph paragraph = (XWPFParagraph) bodyElement;
for(IRunElement runElement : paragraph.getIRuns()) {
if (runElement instanceof XWPFRun) {
XWPFRun run = (XWPFRun) runElement;
List<CTDrawing> drawings = getAllDrawings(run);
System.out.println(drawings);
}
}
}
}
document.close();
}
}
But next problem will be how to get contents out of the drawing elements then as <wps:wsp><wps:txbx> also is not part of Office Open XML according standard ECMA-376. So ooxml-schemas methods of CTDrawing also are not able to get those. So if the need is then getting the text box contents from the drawing, this also is only possible using XML (XPath) methods directly.
This could look like so then:
private static CTTxbxContent getTextBoxContent(CTDrawing drawing) throws Exception {
XmlCursor cursor = drawing.newCursor();
cursor.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//*/w:txbxContent");
List<CTTxbxContent> txbxContents = new ArrayList<CTTxbxContent>();
while (cursor.hasNextSelection()) {
cursor.toNextSelection();
XmlObject obj = cursor.getObject();
CTTxbxContent txbxContent = CTTxbxContent.Factory.parse(obj.newInputStream());
txbxContents.add(txbxContent);
break;
}
CTTxbxContent txbxContent = null;
if (txbxContents.size() > 0) {
txbxContent = txbxContents.get(0);
}
return txbxContent;
}

Apache FOP - is there a way to embed font programmatically?

When creating a PDF using Apache FOP it is possible to embed a font with configuration file. The problem emerges when the app is a web application and it is necessary to embed a font that is inside WAR file (so treated as resource).
It is not acceptable to use particular container's folder structure to determine where exactly the war is located (when in configuration xml file we set tag to ./, it is set to the base folder of running container like C:\Tomcat\bin).
So the question is: Do anyone know the way to embed a font programatically?
After going through lots of FOP java code I managed to get it to work.
Descriptive version
Main idea is to force FOP to use custom PDFRendererConfigurator that will return desired font list when getCustomFontCollection() is executed.
In order to do it we need to create custom PDFDocumentHandlerMaker that will return custom PDFDocumentHandler (form method makeIFDocumentHandler()) which will in turn return our custom PDFRendererConfigurator (from getConfigurator() method) that, as above, will set out custom font list.
Then just add custom PDFDocumentHandlerMaker to RendererFactory and it will work.
FopFactory > RendererFactory > PDFDocumentHandlerMaker > PDFDocumentHandler > PDFRendererConfigurator
Full code
FopTest.java
public class FopTest {
public static void main(String[] args) throws Exception {
// the XSL FO file
StreamSource xsltFile = new StreamSource(
Thread.currentThread().getContextClassLoader().getResourceAsStream("template.xsl"));
// the XML file which provides the input
StreamSource xmlSource = new StreamSource(
Thread.currentThread().getContextClassLoader().getResourceAsStream("employees.xml"));
// create an instance of fop factory
FopFactory fopFactory = new FopFactoryBuilder(new File(".").toURI()).build();
RendererFactory rendererFactory = fopFactory.getRendererFactory();
rendererFactory.addDocumentHandlerMaker(new CustomPDFDocumentHandlerMaker());
// a user agent is needed for transformation
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
// Setup output
OutputStream out;
out = new java.io.FileOutputStream("employee.pdf");
try {
// Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
// Setup XSLT
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(xsltFile);
// Resulting SAX events (the generated FO) must be piped through to
// FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Start XSLT transformation and FOP processing
// That's where the XML is first transformed to XSL-FO and then
// PDF is created
transformer.transform(xmlSource, res);
} finally {
out.close();
}
}
}
CustomPDFDocumentHandlerMaker.java
public class CustomPDFDocumentHandlerMaker extends PDFDocumentHandlerMaker {
#Override
public IFDocumentHandler makeIFDocumentHandler(IFContext ifContext) {
CustomPDFDocumentHandler handler = new CustomPDFDocumentHandler(ifContext);
FOUserAgent ua = ifContext.getUserAgent();
if (ua.isAccessibilityEnabled()) {
ua.setStructureTreeEventHandler(handler.getStructureTreeEventHandler());
}
return handler;
}
}
CustomPDFDocumentHandler.java
public class CustomPDFDocumentHandler extends PDFDocumentHandler {
public CustomPDFDocumentHandler(IFContext context) {
super(context);
}
#Override
public IFDocumentHandlerConfigurator getConfigurator() {
return new CustomPDFRendererConfigurator(getUserAgent(), new PDFRendererConfigParser());
}
}
CustomPDFRendererConfigurator.java
public class CustomPDFRendererConfigurator extends PDFRendererConfigurator {
public CustomPDFRendererConfigurator(FOUserAgent userAgent, RendererConfigParser rendererConfigParser) {
super(userAgent, rendererConfigParser);
}
#Override
protected FontCollection getCustomFontCollection(InternalResourceResolver resolver, String mimeType)
throws FOPException {
List<EmbedFontInfo> fontList = new ArrayList<EmbedFontInfo>();
try {
FontUris fontUris = new FontUris(Thread.currentThread().getContextClassLoader().getResource("UbuntuMono-Bold.ttf").toURI(), null);
List<FontTriplet> triplets = new ArrayList<FontTriplet>();
triplets.add(new FontTriplet("UbuntuMono", Font.STYLE_NORMAL, Font.WEIGHT_NORMAL));
EmbedFontInfo fontInfo = new EmbedFontInfo(fontUris, false, false, triplets, null, EncodingMode.AUTO, EmbeddingMode.AUTO);
fontList.add(fontInfo);
} catch (Exception e) {
e.printStackTrace();
}
return createCollectionFromFontList(resolver, fontList);
}
}
Yes you can do this. You need to set FOP's first base directory programmatically.
fopFactory = FopFactory.newInstance();
// for image base URL : images from Resource path of project
String serverPath = request.getSession().getServletContext().getRealPath("/");
fopFactory.setBaseURL(serverPath);
// for fonts base URL : .ttf from Resource path of project
fopFactory.getFontManager().setFontBaseURL(serverPath);
Then use FOB font config file.It will use above base path.
Just put your font files in web applications resource folder and refer that path in FOP's font config file.
After Comment : Reading font config programmatically (not preferred & clean way still as requested)
//This is NON tested and PSEUDO code to get understanding of logic
FontUris fontUris = new FontUris(new URI("<font.ttf relative path>"), null);
EmbedFontInfo fontInfo = new EmbedFontInfo(fontUris, "is kerning enabled boolean", "is aldvaned enabled boolean", null, "subFontName");
List<EmbedFontInfo> fontInfoList = new ArrayList<>();
fontInfoList.add(fontInfo);
//set base URL for Font Manager to use relative path of ttf file.
fopFactory.getFontManager().updateReferencedFonts(fontInfoList);
You can get more info for FOP's relative path https://xmlgraphics.apache.org/fop/2.2/configuration.html
The following approach may be useful for those who use PDFTranscoder.
Put the following xml template in the resources:
<?xml version="1.0" encoding="UTF-8"?>
<fop version="1.0">
<fonts>
<font kerning="no" embed-url="IBM_PLEX_MONO_PATH" embedding-mode="subset">
<font-triplet name="IBM Plex Mono" style="normal" weight="normal"/>
</font>
</fonts>
</fop>
Then one can load this xml and replace the line with font (IBM_PLEX_MONO_PATH) with the actual URI of the font from the resource bundle at runtime:
private val fopConfig = DefaultConfigurationBuilder()
.buildFromFile(javaClass.getResourceAsStream("/fonts/fopconf.xml")?.use {
val xml = BufferedReader(InputStreamReader(it)).use { bf ->
bf.readLines()
.joinToString("")
.replace(
"IBM_PLEX_MONO_PATH",
javaClass.getResource("/fonts/IBM_Plex_Mono/IBMPlexMono-Text.ttf")!!.toURI().toString()
)
}
val file = Files.createTempFile("fopconf", "xml")
file.writeText(xml)
file.toFile()
})
Now one can use this config with PDFTranscoder and your custom fonts will be probably rendered and embedded in PDF:
val pdfTranscoder = if (type == PDF) PDFTranscoder() else EPSTranscoder()
ContainerUtil.configure(pdfTranscoder, fopConfig)
val input = TranscoderInput(ByteArrayInputStream(svg.toByteArray()))
ByteArrayOutputStream().use { byteArrayOutputStream ->
val output = TranscoderOutput(byteArrayOutputStream)
pdfTranscoder.transcode(input, output)
byteArrayOutputStream.toByteArray()
}

Set html content as header and footer in iTextPDF using Java

In my application I've to generate a report from the html content. For this purpose I am using the iTextPDF library 5.5.0 with XMLWorker 5.5.0. End user can design the content of report (Header, Footer, Subject) using tinyMC editor. I am storing those HTML content in DB. Please note this HTML content may include images, tables or any thing that tinyMC allows. Now when user tries to generate the report, I am fetching the data from the database and tried to generate the header and footer from HTML using code given below.
/**
* Adds the header and the footer.
* #see com.itextpdf.text.pdf.PdfPageEventHelper#onEndPage(
* com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document)
*/
public void onEndPage(PdfWriter writer, Document document) {
Phrase footerPhrase = new Phrase();
DBFooter = "<table width='200 border='1' cellpadding='1' cellspacing='1'><tbody><tr><td>content1</td><td>content2</td></tr><tr><td> </td><td> </td>
</tr><tr><td>jhvhjvh</td><td><img src='D:\DemoApp\images\User1.png' width='48' height='48' alt='' /></td></tr></tbody></table>
<p style='text-align: center;'>This is the footer</p>"
CSSResolver cssResolver = new StyleAttrCSSResolver();
CssFile cssFile = XMLWorkerHelper.getCSS(new ByteArrayInputStream(CSS.getBytes()));
cssResolver.addCss(cssFile);
// HTML
HtmlPipelineContext htmlContext = new HtmlPipelineContext(null);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
// Pipelines
ElementList elements = new ElementList();
ElementHandlerPipeline pdf = new ElementHandlerPipeline(elements, null);
HtmlPipeline html = new HtmlPipeline(htmlContext, pdf);
CssResolverPipeline css = new CssResolverPipeline(cssResolver, html);
// XML Worker
XMLWorker worker = new XMLWorker(css, true);
XMLParser p = new XMLParser(worker);
try {
p.parse(new ByteArrayInputStream(DBFooter.toString().getBytes()));
} catch (IOException e) {
}
footerPhrase.addAll(elements);
Rectangle rect = writer.getBoxSize("art");
switch (writer.getPageNumber() % 2) {
case 0:
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_RIGHT, header[0], rect.getRight(),
rect.getTop(), 0);
break;
case 1:
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT, header[1], rect.getLeft(),
rect.getTop(), 0);
break;
}
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_CENTER, footerPhrase,
(rect.getLeft() + rect.getRight()) / 2, rect.getBottom() - 50, 0);
}
I've followed this link to add header footer. And changed the onEndPage function as above.
Problem is the above code only prints the "This is the footer" from the content of footer. It's not showing the table. How can I bring the HTML content in Header / Footer.
Please note that I can show html content in report body (including table / images etc.) but not in header / footer.
Managed to add html header and footer in C# using page events and elements handler of itext sharp xml worker
/// <summary>
/// returns pdf in bytes.
/// </summary>
/// <param name="contentsHtml">contents.</param>
/// <param name="headerHtml">header contents.</param>
/// <param name="footerHtml">footer contents.</param>
/// <returns></returns>
public Byte[] GetPDF(string contentsHtml, string headerHtml, string footerHtml)
{
// Create a byte array that will eventually hold our final PDF
Byte[] bytes;
// Boilerplate iTextSharp setup here
// Create a stream that we can write to, in this case a MemoryStream
using (var ms = new MemoryStream())
{
// Create an iTextSharp Document which is an abstraction of a PDF but **NOT** a PDF
using (var document = new Document(PageSize.A4, 40, 40, 120, 120))
{
// Create a writer that's bound to our PDF abstraction and our stream
using (var writer = PdfWriter.GetInstance(document, ms))
{
// Open the document for writing
document.Open();
var headerElements = new HtmlElementHandler();
var footerElements = new HtmlElementHandler();
XMLWorkerHelper.GetInstance().ParseXHtml(headerElements, new StringReader(headerHtml));
XMLWorkerHelper.GetInstance().ParseXHtml(footerElements, new StringReader(footerHtml));
writer.PageEvent = new HeaderFooter(headerElements.GetElements(), footerElements.GetElements());
// Read your html by database or file here and store it into finalHtml e.g. a string
// XMLWorker also reads from a TextReader and not directly from a string
using (var srHtml = new StringReader(contentsHtml))
{
// Parse the HTML
iTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, document, srHtml);
}
document.Close();
}
}
// After all of the PDF "stuff" above is done and closed but **before** we
// close the MemoryStream, grab all of the active bytes from the stream
bytes = ms.ToArray();
}
return bytes;
}
}
page events and elements handler code is here
public partial class HeaderFooter : PdfPageEventHelper
{
private ElementList HeaderElements { get; set; }
private ElementList FooterElements { get; set; }
public HeaderFooter(ElementList headerElements, ElementList footerElements)
{
HeaderElements = headerElements;
FooterElements = footerElements;
}
public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
try
{
ColumnText headerText = new ColumnText(writer.DirectContent);
foreach (IElement e in HeaderElements)
{
headerText.AddElement(e);
}
headerText.SetSimpleColumn(document.Left, document.Top, document.Right, document.GetTop(-100), 10, Element.ALIGN_MIDDLE);
headerText.Go();
ColumnText footerText = new ColumnText(writer.DirectContent);
foreach (IElement e in FooterElements)
{
footerText.AddElement(e);
}
footerText.SetSimpleColumn(document.Left, document.GetBottom(-100), document.Right, document.GetBottom(-40), 10, Element.ALIGN_MIDDLE);
footerText.Go();
}
catch (DocumentException de)
{
throw new Exception(de.Message);
}
}
}
public class HtmlElementHandler : IElementHandler
{
public ElementList Elements { get; set; }
public HtmlElementHandler()
{
Elements = new ElementList();
}
public ElementList GetElements()
{
return Elements;
}
public void Add(IWritable w)
{
if (w is WritableElement)
{
foreach (IElement e in ((WritableElement)w).Elements())
{
Elements.Add(e);
}
}
}
}

Using Flying Saucer to Render Images to PDF In Memory

I'm using Flying Saucer to convert XHTML to a PDF document. I've gotten the code to work with just basic HTML and in-line CSS, however, now I'm attempting to add an image as a sort of header to the PDF. What I'm wondering is if there is any way whatsoever to add the image by reading in an image file as a Java Image object, then adding that somehow to the PDF (or to the XHTML -- like it gets a virtual "url" representing the Image object that I can use to render the PDF). Has anyone ever done anything like this?
Thanks in advance for any help you can provide!
I had to do that last week so hopefully I will be able to answer you right away.
Flying Saucer
The easiest way is to add the image you want as markup in your HTML template before rendering with Flying Saucer. Within Flying Saucer you will have to implement a ReplacedElementFactory so that you can replace any markup before rendering with the image data.
/**
* Replaced element in order to replace elements like
* <tt><div class="media" data-src="image.png" /></tt> with the real
* media content.
*/
public class MediaReplacedElementFactory implements ReplacedElementFactory {
private final ReplacedElementFactory superFactory;
public MediaReplacedElementFactory(ReplacedElementFactory superFactory) {
this.superFactory = superFactory;
}
#Override
public ReplacedElement createReplacedElement(LayoutContext layoutContext, BlockBox blockBox, UserAgentCallback userAgentCallback, int cssWidth, int cssHeight) {
Element element = blockBox.getElement();
if (element == null) {
return null;
}
String nodeName = element.getNodeName();
String className = element.getAttribute("class");
// Replace any <div class="media" data-src="image.png" /> with the
// binary data of `image.png` into the PDF.
if ("div".equals(nodeName) && "media".equals(className)) {
if (!element.hasAttribute("data-src")) {
throw new RuntimeException("An element with class `media` is missing a `data-src` attribute indicating the media file.");
}
InputStream input = null;
try {
input = new FileInputStream("/base/folder/" + element.getAttribute("data-src"));
final byte[] bytes = IOUtils.toByteArray(input);
final Image image = Image.getInstance(bytes);
final FSImage fsImage = new ITextFSImage(image);
if (fsImage != null) {
if ((cssWidth != -1) || (cssHeight != -1)) {
fsImage.scale(cssWidth, cssHeight);
}
return new ITextImageElement(fsImage);
}
} catch (Exception e) {
throw new RuntimeException("There was a problem trying to read a template embedded graphic.", e);
} finally {
IOUtils.closeQuietly(input);
}
}
return this.superFactory.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
}
#Override
public void reset() {
this.superFactory.reset();
}
#Override
public void remove(Element e) {
this.superFactory.remove(e);
}
#Override
public void setFormSubmissionListener(FormSubmissionListener listener) {
this.superFactory.setFormSubmissionListener(listener);
}
}
You will notice that I have hardcoded here /base/folder which is the folder where the HTML file is located as it will be the root url for Flying Saucer for resolving medias. You may change it to the correct location, coming from anywhere you want (Properties for example).
HTML
Within your HTML markup you indicate somewhere a <div class="media" data-src="somefile.png" /> like so:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My document</title>
<style type="text/css">
#logo { /* something if needed */ }
</style>
</head>
<body>
<!-- Header -->
<div id="logo" class="media" data-src="media/logo.png" style="width: 177px; height: 60px" />
...
</body>
</html>
Rendering
And finally you just need to indicate your ReplacedElementFactory to Flying-Saucer when rendering:
String content = loadHtml();
ITextRenderer renderer = new ITextRenderer();
renderer.getSharedContext().setReplacedElementFactory(new MediaReplacedElementFactory(renderer.getSharedContext().getReplacedElementFactory()));
renderer.setDocumentFromString(content.toString());
renderer.layout();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
renderer.createPDF(baos);
// baos.toByteArray();
I have been using Freemarker to generate the HTML from a template and then feeding the result to FlyingSaucer with great success. This is a pretty neat library.
what worked for me is putting it as a embedded image. So converting image to base64 first and then embed it:
byte[] image = ...
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString("<html>\n" +
" <body>\n" +
" <h1>Image</h1>\n" +
" <div><img src=\"data:image/png;base64," + Base64.getEncoder().encodeToString(image) + "\"></img></div>\n" +
" </body>\n" +
"</html>");
renderer.layout();
renderer.createPDF(response.getOutputStream());
Thanks Alex for detailed solution. I'm using this solution and found there is another line to be added to make it work.
public ReplacedElement createReplacedElement(LayoutContext layoutContext, BlockBox blockBox, UserAgentCallback userAgentCallback, int cssWidth, int cssHeight) {
Element element = blockBox.getElement();
....
....
final Image image = Image.getInstance(bytes);
final int factor = ((ITextUserAgent)userAgentCallback).getSharedContext().getDotsPerPixel(); //Need to add this line
image.scaleAbsolute(image.getPlainWidth() * factor, image.getPlainHeight() * factor) //Need to add this line
final FSImage fsImage = new ITextFSImage(image);
....
....
We need to read the DPP from SharedContext and scale the image to display render the image on PDF.
Another suggestion:
We can directly extend ITextReplacedElement instead of implementing ReplacedElementFactory. In that case we can set the ReplacedElementFactory in the SharedContext as follows:
renderer.getSharedContext().setReplacedElementFactory(new MediaReplacedElementFactory(renderer.getOutputDevice());

How can I convert a document to landscape mode using a Java library?

I am writing a program in Java (I am using Ubuntu). I am using Jodconverter to convert the document to PDF. I have to convert the document to landscape mode but I have read that Jodconverter doesn't support orientation changes. I also tried with OpenOffice API but I am facing the same issue.
Is there any Java library that does conversion to landscape?
From a similar question regarding using Jodconverter with an Open Office document:
http://groups.google.com/group/jodconverter/browse_thread/thread/dc96df64c7d60ada/c1692fee92513b7a
Short answer: you can't. The page orientation is a property of the
document (menu Format > Page in Calc), not a PDF export option. So it
should be set already in the XLS document.
Export to PDF and then use a PDF library like PDFbox to rotate the pages by 90 degrees.
Try PDPage.setRotation(int) on all pages (PDDocument.getDocumentCatalog().getAllPages()).
I have found the solution. I have converted document to landscape pdf using open office API for java. Here is the code for the same.
System.out.println("starting...");
String oooExeFolder = "/usr/lib/openoffice/program";
XComponentContext xContext = BootstrapSocketConnector.bootstrap(oooExeFolder);
XMultiComponentFactory xMCF = xContext.getServiceManager();
Object oDesktop = xMCF.createInstanceWithContext("com.sun.star.frame.Desktop", xContext);
XComponentLoader xCLoader = (XComponentLoader) UnoRuntime.queryInterface(XComponentLoader.class, oDesktop);
System.out.println("loading ");
PropertyValue[] printerDesc = new PropertyValue[1];
printerDesc[0] = new PropertyValue();
printerDesc[0].Name = "PaperOrientation";
printerDesc[0].Value = PaperOrientation.LANDSCAPE;
// Create a document
XComponent document = xCLoader.loadComponentFromURL(loadUrl, "_blank", 0, printerDesc);
// Following property will convert doc into requested orientation.
XPrintable xPrintable = (XPrintable) UnoRuntime.queryInterface(XPrintable.class, document);
xPrintable.setPrinter(printerDesc);
PropertyValue[] conversionProperties = new PropertyValue[3];
conversionProperties[1] = new PropertyValue();
conversionProperties[1].Name = "FilterName";
conversionProperties[1].Value = "writer_pdf_Export";//
conversionProperties[0] = new PropertyValue();
conversionProperties[0].Name = "Overwrite ";
conversionProperties[0].Value = new Boolean(true);
System.out.println("closing");
XStorable xstorable = (XStorable) UnoRuntime.queryInterface(XStorable.class, document);
xstorable.storeToURL(storeUrl, conversionProperties);
System.out.println("closing");
XCloseable xcloseable = (XCloseable) UnoRuntime.queryInterface(XCloseable.class, document);
xcloseable.close(false);
Try overriding OfficeDocumentConverter
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager) {
private Map<String, Object> createDefaultLoadProperties() {
Map<String, Object> loadProperties = new HashMap<String, Object>();
loadProperties.put("Hidden", true);
loadProperties.put("ReadOnly", true);
loadProperties.put("UpdateDocMode", UpdateDocMode.QUIET_UPDATE);
return loadProperties;
}
#Override
public void convert(File inputFile, File outputFile, DocumentFormat outputFormat) throws OfficeException {
String inputExtension = FilenameUtils.getExtension(inputFile.getName());
DocumentFormat inputFormat = getFormatRegistry().getFormatByExtension(inputExtension);
inputFormat.setLoadProperties(Collections.singletonMap("PaperOrientation", PaperOrientation.LANDSCAPE));
StandardConversionTask conversionTask = new StandardConversionTask(inputFile, outputFile, outputFormat) {
#Override
protected void modifyDocument(XComponent document) throws OfficeException {
PropertyValue[] printerDesc = OfficeUtils.toUnoProperties(Collections.singletonMap("PaperOrientation", PaperOrientation.LANDSCAPE));
XPrintable xPrintable = cast(XPrintable.class, document);
try {
xPrintable.setPrinter(printerDesc);
} catch (com.sun.star.lang.IllegalArgumentException e) {
logger.error(e.getMessage());
}
super.modifyDocument(document);
}
};
conversionTask.setDefaultLoadProperties(createDefaultLoadProperties());
conversionTask.setInputFormat(inputFormat);
officeManager.execute(conversionTask);
}
};

Categories