Set html content as header and footer in iTextPDF using Java - 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);
}
}
}
}

Related

How to get a landscape orientation for only some pages while converting html to pdf in itext 7?

I convert HTML to PDF using iText7 with the convertToPDF() method of pdfHTML. I would like to change the page orientation for a few specific pages in my PDF document. The content of these pages is dynamic, and we cannot guess how many pages that should be in landscape (i.e. content of dynamic table could take more than one page)
Current situation: I create a custom worker (implements ITagWorker) which landscape the page that following the tag <landscape/>
public byte[] generatePDF(String html) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PdfWriter pdfWriter = new PdfWriter(byteArrayOutputStream);
PdfDocument pdfDocument = new PdfDocument(pdfWriter);
try {
ConverterProperties properties = new ConverterProperties();
properties.setTagWorkerFactory(
new DefaultTagWorkerFactory() {
#Override
public ITagWorker getCustomTagWorker(
IElementNode tag, ProcessorContext context) {
if ("landscape".equalsIgnoreCase(tag.name())) {
return new LandscapeDivTagWorker();
}
return null;
}
} );
MediaDeviceDescription mediaDeviceDescription = new MediaDeviceDescription(MediaType.PRINT);
properties.setMediaDeviceDescription(mediaDeviceDescription);
HtmlConverter.convertToPdf(html, pdfDocument, properties);
} catch (IOException e) {
e.printStackTrace();
}
pdfDocument.close();
return byteArrayOutputStream.toByteArray();
}
The custom worker :
public class LandscapeDivTagWorker implements ITagWorker {
#Override
public void processEnd(IElementNode element, ProcessorContext context) {
}
#Override
public boolean processContent(String content, ProcessorContext context) {
return false;
}
#Override
public boolean processTagChild(ITagWorker childTagWorker, ProcessorContext context) {
return false;
}
#Override
public IPropertyContainer getElementResult() {
return new AreaBreak(new PageSize(PageSize.A4).rotate());
}
}
Is there a way to define all the content that should be displayed in landscape?
Something like:
<p>Display in portrait</p>
<landscape>
<div>
<p>display in landscape</p>
…
<table>
..
</table>
</div>
</landscape>
or with a CSS class :
<p>Display in portrait</p>
<div class="landscape">
<p>display in landscape</p>
…
<table>
..
</table>
</div>
Result => 1 page in portrait and other pages in landscape (All the div content should be in landscape)
PS: I follow this hint Change page orientation for only some pages in the resulting PDF (created out of html) by using a custom CssApplierFactory but the result was the same => just the first page where the landscape class is used was in landscape and the other content of the table was in portrait
Doing it is actually quite tricky, but the whole mechanism is still flexible enough to accommodate this requirement.
We will be working on supporting the following syntax:
<p>Display in portrait</p>
<landscape>
<div>
<p>display in landscape</p>
<p>content</p>
.....
<p>content</p>
</div>
</landscape>
<p> After portrait </p>
First off, we will need to convert the HTML content into elements first and then add those elements into a document instead direct HTML -> PDF conversion. This is needed because in case of HTML there is a separate mechanism for page size handling as dictated by CSS specification and it's not flexible enough to accommodate your requirement so we will be using native iText Layout mechanism for that.
The idea is that apart from customizing the new page size by passing an argument to AreaBreak we will also change the default page size for PdfDocument so that all the consequent pages are created with that custom new page size. For that we will need to pass PdfDocument all along. The high-level code looks as follows:
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFilePath));
ConverterProperties properties = new ConverterProperties();
properties.setTagWorkerFactory(new CustomTagWorkerFactory(pdfDocument));
Document document = new Document(pdfDocument);
List<IElement> elements = HtmlConverter.convertToElements(new FileInputStream(inputHtmlPath), properties);
for (IElement element : elements) {
if (element instanceof IBlockElement) {
document.add((IBlockElement) element);
}
}
pdfDocument.close();
The custom tag worker factory is also almost unchanged - it just passes PdfDocument along to the tag worker:
private static class CustomTagWorkerFactory extends DefaultTagWorkerFactory {
PdfDocument pdfDocument;
public CustomTagWorkerFactory(PdfDocument pdfDocument) {
this.pdfDocument = pdfDocument;
}
#Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if ("landscape".equalsIgnoreCase(tag.name())) {
return new LandscapeDivTagWorker(tag, context, pdfDocument);
}
return null;
}
}
The idea of LandscapeDivTagWorker is to create a Div wrapper and put there the inner content of <landscape> tag but also surround it with AreaBreak elements - the preceding one will force the new page break with landscape orientation and change the default page size for the whole document while the succeeding one will revert everything back - force the split to portrait page size and set default page size to portrait as well. Note that we are also setting a custom renderer for AreaBreak with setNextRenderer to actually set the default page size when it comes to that break:
private static class LandscapeDivTagWorker extends DivTagWorker {
private PdfDocument pdfDocument;
public LandscapeDivTagWorker(IElementNode element, ProcessorContext context, PdfDocument pdfDocument) {
super(element, context);
this.pdfDocument = pdfDocument;
}
#Override
public IPropertyContainer getElementResult() {
IPropertyContainer baseElementResult = super.getElementResult();
if (baseElementResult instanceof Div) {
Div div = new Div();
AreaBreak landscapeAreaBreak = new AreaBreak(new PageSize(PageSize.A4).rotate());
landscapeAreaBreak.setNextRenderer(new DefaultPageSizeChangingAreaBreakRenderer(landscapeAreaBreak, pdfDocument));
div.add(landscapeAreaBreak);
div.add((IBlockElement) baseElementResult);
AreaBreak portraitAreaBreak = new AreaBreak(new PageSize(PageSize.A4));
portraitAreaBreak.setNextRenderer(new DefaultPageSizeChangingAreaBreakRenderer(portraitAreaBreak, pdfDocument));
div.add(portraitAreaBreak);
baseElementResult = div;
}
return baseElementResult;
}
}
The implementation of the custom area break renderer is quite straightforward - we only set the default page size to PdfDocument - the rest is done under the hood by default implementations which we extend from:
private static class DefaultPageSizeChangingAreaBreakRenderer extends AreaBreakRenderer {
private PdfDocument pdfDocument;
private AreaBreak areaBreak;
public DefaultPageSizeChangingAreaBreakRenderer(AreaBreak areaBreak, PdfDocument pdfDocument) {
super(areaBreak);
this.pdfDocument = pdfDocument;
this.areaBreak = areaBreak;
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
pdfDocument.setDefaultPageSize(areaBreak.getPageSize());
return super.layout(layoutContext);
}
}
As a result you will get the page set up similar to the one on the screenshot:

How to retrieve the image of a PdfStampAnnotation

I created a pdf using the following example:
https://developers.itextpdf.com/examples/actions-and-annotations/clone-creating-and-adding-annotations#2260-addstamp.java
#Category(SampleTest.class)
public class AddStamp extends GenericTest {
public static final String DEST = "./target/test/resources/sandbox/annotations/add_stamp.pdf";
public static final String IMG = "./src/test/resources/img/itext.png";
public static final String SRC = "./src/test/resources/pdfs/hello.pdf";
public static void main(String[] args) throws Exception {
File file = new File(DEST);
file.getParentFile().mkdirs();
new AddStamp().manipulatePdf(DEST);
}
#Override
protected void manipulatePdf(String dest) throws Exception {
PdfDocument pdfDoc = new PdfDocument(new PdfReader(SRC), new PdfWriter(DEST));
ImageData img = ImageDataFactory.create(IMG);
float w = img.getWidth();
float h = img.getHeight();
Rectangle location = new Rectangle(36, 770 - h, w, h);
PdfStampAnnotation stamp = new PdfStampAnnotation(location)
.setStampName(new PdfName("ITEXT"));
PdfFormXObject xObj = new PdfFormXObject(new Rectangle(w, h));
PdfCanvas canvas = new PdfCanvas(xObj, pdfDoc);
canvas.addImage(img, 0, 0, false);
stamp.setNormalAppearance(xObj.getPdfObject());
stamp.setFlags(PdfAnnotation.PRINT);
pdfDoc.getFirstPage().addAnnotation(stamp);
pdfDoc.close();
}
}
The pdf is properly created and contains the stamp annotation
I can get the annotation using:
...
PdfStampAnnotation s = (PdfStampAnnotation) pdfDoc.getFirstPage().getAnnotations().get(0);
s.?????
How can I get back the image (itext.png) of the stamp (eg: byte[]) ?
I'm really new to itext and after hours of research I'm stuck at this point...
First of all, you won't get the original image back. PDF support only very few bitmap image formats as they are: JPEG, JPEG2000, certain fax formats, but definitively not PNG. PNGs are converted into the PDF internal bitmap format, and upon extraction can best be converted back to a PNG.
Furthermore, the reason why there is no simple getImage method in the PdfStampAnnotation class is that the appearance of a stamp may be constructed like the contents of a regular page, it may contain text, it may contain vector graphics, it may contain bitmap images, it may contain an arbitrary mixture of those elements. Thus, all you can retrieve from an annotation is its appearance.
If you are sure an annotation contains only an image (or you at least are not interested in anything but the image), you can extract that image using the iText parser framework, e.g. like this:
Map<byte[], String> extractAnnotationImages(PdfStream xObject) {
final Map<byte[], String> result = new HashMap<>();
IEventListener renderListener = new IEventListener() {
#Override
public Set<EventType> getSupportedEvents() {
return Collections.singleton(RENDER_IMAGE);
}
#Override
public void eventOccurred(IEventData data, EventType type) {
if (data instanceof ImageRenderInfo) {
ImageRenderInfo imageRenderInfo = (ImageRenderInfo) data;
byte[] bytes = imageRenderInfo.getImage().getImageBytes();
String extension = imageRenderInfo.getImage().identifyImageFileExtension();
result.put(bytes, extension);
}
}
};
PdfCanvasProcessor processor = new PdfCanvasProcessor(renderListener, Collections.emptyMap());
processor.processContent(xObject.getBytes(), new PdfResources(xObject.getAsDictionary(PdfName.Resources)));
return result;
}
(ExtractAnnotationImage method)
which returns a mapping from image byte arrays to file extension to use.
I used it in this helper method:
void saveAnnotationImages(PdfDocument pdfDocument, String prefix) throws IOException {
for (int pageNumber = 1; pageNumber <= pdfDocument.getNumberOfPages(); pageNumber++) {
PdfPage page = pdfDocument.getPage(pageNumber);
int index = 0;
for (PdfAnnotation annotation : page.getAnnotations()) {
PdfDictionary normal = annotation.getAppearanceObject(PdfName.N);
if (normal instanceof PdfStream) {
Map<byte[], String> images = extractAnnotationImages((PdfStream)normal);
for (Map.Entry<byte[], String> entry : images.entrySet()) {
Files.write(new File(String.format("%s-%s-%s.%s", prefix, pageNumber, index++, entry.getValue())).toPath(), entry.getKey());
}
}
}
}
}
(ExtractAnnotationImage helper method)
to extract all images from annotations from the output of the iText example AddStamp you reference and got one image:
By the way, you'll recognize here that transparency is missing. Transparency in the PDF is modeled via a second image, a mask image, which effectively represents something like an alpha channel. One can retrieve this mask from the ImageRenderInfo.getImage() object.

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

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...

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