Convert HTML to PDF with Header and Footer - java

We have header and footer String as HTML content type but how to append both on every page.
In below java method we are passing three parameter htmlcontent, HeaderContent, FooterContent and return number of pages created in html, But where do we have to attach the header and footer content?
public static int generatePDF(String strFileName, String htmlContent,String headerHtml,String footerHtml) throws PDFNetException {
PDFDoc doc = new PDFDoc();
HTML2PDF converter = new HTML2PDF();
int nPages = 0;
try {
converter = new HTML2PDF();
doc = new PDFDoc();
converter.insertFromHtmlString(htmlContent);
try {
if (converter.convert(doc)) {
doc.save(strFileName, SDFDoc.e_linearized, null);
nPages = doc.getPageCount();
}
} catch (Exception ex) {
ex.printStackTrace();
}
} catch (Exception e) {
ex.printStackTrace();
} finally {
converter.destroy();
doc.close();
}
return nPages;
}

One option is to post-process the PDF, by using the Stamper class, to add headers/footers.
See the following sample code on how to use Stamper call
https://www.pdftron.com/documentation/samples/#stamper
The HTML2PDF converter appends pages to the PDFDoc object passed in, so you can do the following.
call HTML2PDF.InsertFromURL(url)
call HTML2PDF.Convert(pdfdoc)
run Stamper on pages x-y stamp
and repeat to keep appending pages to pdfdoc.

Related

How to use iText get pdf rendered with multiple fonts

I'm using iText to parse html to pdf with both english and chinese characters. I'm using
// for pdf rendering
compile group: 'com.itextpdf', name: 'itextpdf', version: '5.5.13.1'
// for pdf rendering
compile group: 'com.itextpdf.tool', name: 'xmlworker', version: '5.5.13.1'
I've already make it possible to get chinese characters parsing not a problem with dependency
// for chinese font in pdf rendering
compile group: 'com.itextpdf', name: 'itext-asian', version: '5.2.0'
and customized font provider
public class StSongProvider extends XMLWorkerFontProvider {
private static final Logger LOG = LoggerFactory.getLogger(StSongProvider.class);
public StSongProvider() {
super(null, null);
}
#Override
public Font getFont(final String fontName, String encoding, float size, final int style) {
BaseFont bfChinese = null;
try {
bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
LOG.error("Not found STSong-Light,maybe com.itextpdf.itext-asian dependency problem");
}
return new Font(bfChinese, size, style);
}
}
and pdf rendring code
public static File html2Pdf(String html, String fileName) {
try {
String path = buildPath(fileName);
// step 1
Document document = new Document(PageSize.A4);
document.setMargins(20, 20, 0, 0);
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(path));
// step 3
document.open();
// step 4
InputStream cssInput = null;
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes(StandardCharsets.UTF_8)), cssInput, new StSongProvider());
// step 5
document.close();
LOG.info("PDF file: {} rendering successfully", path);
return new File(path);
} catch (IOException ex) {
// do something
} catch (DocumentException ex) {
// do something
}
}
But the english characters in the result pdf is not that beautiful without a proper font (all characters are using STSong-Light font). I want to get pdf rendered with chinese characters using STSong-Light and english characters using some fonts that iText supported originally such as Times-Roman.
I found this SO thread makes building a document possible with multiple fonts using FontSelector. But how to make it compatible with pdf creation process? The XMLWorkerHelper.getInstance().parseXHtml api only accept a FontProvider as parameter. Any ideas about this?
The solution is do something on the customized font provider, make it not return only one font, but return font depends on the html cell font-family attribute.
public class StSongProvider extends XMLWorkerFontProvider {
private static final Logger LOG = LoggerFactory.getLogger(StSongProvider.class);
public StSongProvider() {
super(null, null);
}
#Override
public Font getFont(final String fontName, String encoding, float size, final int style) {
BaseFont font = null;
try {
if (StringUtils.equals(fontName, "STSong-Light")) {
font = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} else {
font = BaseFont.createFont(FontFactory.TIMES_ROMAN, FontFactory.defaultEncoding, true);
}
} catch (Exception e) {
// do something
}
return new Font(font, size, style);
}
}
With above font provider, and set the style="font-family:STSong-Light attribute of a html cell contains chinese characters to format them, and other english characters will be well formatted with TIMES_ROMAN;

Itext7 generate pdf with Exception "Pdf indirect object belongs to other PDF document. Copy object to current pdf document."

i want to generate a pdf with itext 7,but some wrong happens to us:
com.itextpdf.kernel.PdfException: Pdf indirect object belongs to other PDF document. Copy object to current pdf document.
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:195) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:185) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:115) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:187) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:115) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:187) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfOutputStream.write(PdfOutputStream.java:115) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfWriter.writeToBody(PdfWriter.java:383) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfWriter.flushObject(PdfWriter.java:289) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfDocument.flushObject(PdfDocument.java:1572) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfObject.flush(PdfObject.java:159) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfObject.flush(PdfObject.java:127) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfObjectWrapper.flush(PdfObjectWrapper.java:94) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfPage.flush(PdfPage.java:495) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfPage.flush(PdfPage.java:454) ~[kernel-7.0.2.jar:na]
at com.itextpdf.kernel.pdf.PdfDocument.close(PdfDocument.java:785) ~[kernel-7.0.2.jar:na]
at com.itextpdf.layout.Document.close(Document.java:120) ~[layout-7.0.2.jar:na]
at com.xcz.afbp.thirdparty.service.impl.GeneratePDFService.generatePDF(GeneratePDFService.java:160) ~[classes/:na]
my generate code :
public void generatePDF(CreditQueryData creditQueryData, Map<String, UserCreditContentView> contentViewMap, List<PackageCreditContentView> needRetrievedCreditContentList, File pdfFile, BigDecimal score) throws Exception {
if (!pdfFile.exists()) {
boolean x = pdfFile.createNewFile();
if (!x) {
LOG.error("生成文件出错" + pdfFile.getPath());
return;
}
}
PdfDocument pdf = new PdfDocument(new PdfWriter(new FileOutputStream(pdfFile)));
Document document = new Document(pdf, PageSize.A4);
document.setRenderer(new DocumentRenderer(document));
pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new WatermarkingEventHandler());
try {
//operate code just add tableA tableB tableC...
} catch (Exception e) {
LOG.info();
} finally {
document.close(); //exception throws here
}
}
my only style code in itext7:
private PdfFont bfChinese = null;
will be init in service constructor invoked:
public GeneratePDFService() {
String PdfFontPath = EnvironmentUtils.getClasspathFilePath("font/MSYH.TTF");
try {
bfChinese = PdfFontFactory.createFont(PdfFontPath, "Identity-H", true);
} catch (Exception e) {
e.printStackTrace();
}
}
i have tried set my font to static,but not works.
this is the place throw exception:
private void write(PdfIndirectReference indirectReference) {
if (document != null && !indirectReference.getDocument().equals(document)) {
throw new PdfException(PdfException.PdfIndirectObjectBelongsToOtherPdfDocument);
}
if (indirectReference.getRefersTo() == null) {
write(PdfNull.PDF_NULL);
} else if (indirectReference.getGenNumber() == 0) {
writeInteger(indirectReference.getObjNumber()).
writeBytes(endIndirectWithZeroGenNr);
} else {
writeInteger(indirectReference.getObjNumber()).
writeSpace().
writeInteger(indirectReference.getGenNumber()).
writeBytes(endIndirect);
}
}
it's means i have two different document ,but i do not know when i have create another document.
Thanks in advance for suggestions.
I have experienced the same problem myself (and it took me hours to discover what I was doing wrong). As it turns out, you can use a specific PdfFont instance for only one document. As soon as you use a PdfFont instance it is linked to that document, and you can no longer use it in another document.
For instance:
class ThisGoesWrong {
protected PdfFont font;
public ThisGoesWrong() {
font = PdfFontFactory.createFont(...);
}
public void createPdf() {
...
Paragraph p = new Paragraph("test").setFont(font);
document.add(p);
...
}
}
The class ThisGoesWrong creates a correct PDF the first time you call createPdf(), but it shows the exception you have when you call it a second time.
I discovered that this solves the problem:
class ThisWorksOK {
public ThisWorksOK() {
}
public void createPdf() {
...
PdfFont font = PdfFontFactory.createFont(...);
Paragraph p = new Paragraph("test").setFont(font);
document.add(p);
...
}
}
I don't know if this is a bug (it certainly feels like a bug), so I will create an internal ticket at iText Group.
To improve preformance you should reuse FontProgram:
private FontProgram bfChinese = null;
public GeneratePDFService() {
String PdfFontPath = EnvironmentUtils.getClasspathFilePath("font/MSYH.TTF");
try {
bfChinese = FontProgramFactory.createFont(PdfFontPath);
} catch (Exception e) {
e.printStackTrace();
}
}
And then:
public void createPdf() {
...
PdfFont font = PdfFontFactory.createFont(bfChinese, "Identity-H", true);
Paragraph p = new Paragraph("test").setFont(font);
document.add(p);
...
}
This is how i solved the issue.
Created an DocumentUtil class
Add a static method
public class DocumentUtils {
public static PdfFont setFont() throws Exception {
return PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN);
}
}
Use the font like:
PDfFont font = DocumentUtil.setFont();

How do I add an ICC to an existing PDF document

I have an existing PDF document that is using CMYK colors. It was created using a specific ICC profile, which I have obtained. The colors are obviously different if I open the document with the profile active than without. From what I can tell using a variety of tools, there is no ICC profile embedded in the document. What I would like to do is embed the ICC profile in the PDF so that it can be opened and viewed with the correct colors by third parties. My understanding is that this is possible to do with the PDF format, but nothing I have tried seems to work.
I wrote a small program using PDFBox based on looking at some examples, but it seems to have no effect. I feel like I am missing a step somewhere.
package com.mapsherpa.tools.addicc;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
import java.io.FileInputStream;
import java.io.IOException;
public class AddICC {
public AddICC() {
// TODO Auto-generated constructor stub
}
public static void main(String[] args) {
AddICC app = new AddICC();
try {
if( args.length != 3) {
app.usage();
} else {
app.doIt(args[0], args[1], args[2]);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
private void doIt(String input, String output, String icc) throws IOException {
// TODO Auto-generated method stub
System.out.printf("Adding %s to %s and saving as %s\n", icc, input, output);
PDDocument doc = null;
try
{
File file = new File(input);
doc = PDDocument.load(file);
PDDocumentCatalog cat = doc.getDocumentCatalog();
PDMetadata metadata = new PDMetadata(doc);
cat.setMetadata(metadata);
InputStream colorProfile = new FileInputStream(icc);
PDOutputIntent oi = new PDOutputIntent(doc, colorProfile);
oi.setInfo("SWOP (Coated), 20%, GCR, None");
oi.setOutputCondition("SWOP (Coated), 20%, GCR, None");
oi.setOutputConditionIdentifier("SWOP (Coated), 20%, GCR, None");
oi.setRegistryName("http://www.color.org");
cat.addOutputIntent(oi);
doc.save(output);
System.out.println("Finished adding color profile");
}
catch (Exception e)
{
System.out.println("Exception processing color profile");
e.printStackTrace();
}
finally
{
if (doc != null) {
doc.close();
}
}
}
private void usage() {
// TODO Auto-generated method stub
System.err.println("Usage: " + this.getClass().getName() + " <input-file> <output-file> <icc-file>");
}
}
I'm not a Java expert but I did manage to get this to run and it seems to do something but I still am not seeing the correct colors and there is no indication using imagemagick or pdfinfo that it has a color profile.
I feel like somehow I should be indicating that the document color space is ICCBased but I can't see any obvious way to do that using the PDFBox API.
Any help would be appreciated (even being told that it won't work!)
EDIT:
I believe that this is working as written in that it adds the required output intent to the document. However, I have also discovered that this is not what I need - I now believe that I need it to add an /ICCBased stream to the PDF - sigh. The updated code below is based on this stackoverflow question's updated createColorSpace function.
private static PDColorSpace createColorSpace( PDDocument doc, ColorSpace cs ) throws IOException
{
PDColorSpace retval = null;
if( cs.isCS_sRGB() )
{
retval = PDDeviceRGB.INSTANCE;
}
else if( cs instanceof ICC_ColorSpace )
{
ICC_ColorSpace ics = (ICC_ColorSpace)cs;
// CREATING MANUALLY THE COS ARR ****************************
COSArray cosArray = new COSArray();
cosArray.add(COSName.ICCBASED);
PDStream pdStream = new PDStream(doc);
cosArray.add(pdStream.getStream());
// USING DIFFERENT CONSTRUTOR *******************************
PDICCBased pdCS = new PDICCBased( cosArray );
retval = pdCS;
COSArray ranges = new COSArray();
for( int i=0; i<cs.getNumComponents(); i++ )
{
ranges.add( new COSFloat( ics.getMinValue( i ) ) );
ranges.add( new COSFloat( ics.getMaxValue( i ) ) );
}
PDStream iccData = pdCS.getPDStream();
OutputStream output = null;
try
{
output = ((COSStream)iccData.getCOSObject()).createFilteredStream();
output.write( ics.getProfile().getData() );
}
finally
{
if( output != null )
{
output.close();
}
}
pdCS.setNumberOfComponents( cs.getNumComponents() );
}
else
{
throw new IOException( "Not yet implemented:" + cs );
}
return retval;
}
private void doIt(String input, String output, String icc) throws IOException {
// TODO Auto-generated method stub
System.out.printf("Adding %s to %s and saving as %s\n", icc, input, output);
PDDocument doc = null;
try
{
File file = new File(input);
doc = PDDocument.load(file);
ICC_ColorSpace iccColorSpace = new ICC_ColorSpace(ICC_Profile.getInstance(icc));
PDColorSpace colorSpace = createColorSpace(doc, iccColorSpace);
doc.save(output);
System.out.println("Finished adding color profile");
}
catch (Exception e)
{
System.out.println("Exception processing color profile");
e.printStackTrace();
}
finally
{
if (doc != null) {
doc.close();
}
}
}
This code now has an exception:
java.io.IOException: Unknown color space number of components:-1
at org.apache.pdfbox.pdmodel.graphics.color.PDICCBased.getAlternateColorSpace(PDICCBased.java:269)
at org.apache.pdfbox.pdmodel.graphics.color.PDICCBased.loadICCProfile(PDICCBased.java:151)
at org.apache.pdfbox.pdmodel.graphics.color.PDICCBased.<init>(PDICCBased.java:89)
at com.mapsherpa.tools.addicc.AddICC.createColorSpace(AddICC.java:65)
at com.mapsherpa.tools.addicc.AddICC.doIt(AddICC.java:109)
at com.mapsherpa.tools.addicc.AddICC.main(AddICC.java:39)
at this line of code:
cosArray.add(pdStream.getStream());
The only difference I can see between this code and the other answer is that I am loading an existing PDF document rather than creating a new empty one.
For testing, I'm using the US Web (Coated) SWOP v2 icc profile from Adobe, but it is the same exception with any profile I test. From my understanding of reading the PDFBox source, it isn't a problem with the profile but with reading the stream from the document (which doesn't have an /ICCBased stream, the whole point of this question :))
EDIT 2: the code above does actually run without exceptions if used with PDFBox 1.8.10 - apparently I had linked in 2.0.0 RC2 without realizing it (total Java newbie).

Render image from servlet in flyingsaucer generated pdf

I'm using flyingsaucer to render an xhtml document to pdf through a servlet which returns the generated pdf document. The xhtml document features an image which is requested from another servlet. The image servlet checks who is logged in before returning the appropriate image. The code below shows how the image is requested:
<img height="140" width="140" src="http://localhost:8080/myapp/servlet/DisplayPic" />
My problem is that the http request for the image is from the pdf renderer and not the logged in user so the image servlet doesn't know who's logged in and therefore the desired image is not returned.
I'm currently using the code below to render the xhtml document:
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(xhtmlDocumentAsString);
renderer.layout();
os = response.getOutputStream();
renderer.createPDF(os);
I need to either maintain the user's session when the image servlet is requested or provide the renderer with the image to use for that specific xhtml element. I think the latter can be done using a ReplacedElementFactory but I haven't been able to dig out any example code that can help me.
I've got this working very nicely now. Here's the code.
In my xhtml document i have:
<div class="profile_picture" style="display:block;width:140px;height:140px;" />
(I'm using a div element instead of img as the factory is only used for block level elements)
I render my document using:
ITextRenderer renderer = new ITextRenderer();
renderer.getSharedContext().setReplacedElementFactory(new ProfileImageReplacedElementFactory(renderer.getSharedContext().getReplacedElementFactory()));
renderer.setDocumentFromString(xhtmlDocumentAsString);
renderer.layout();
os = response.getOutputStream();
renderer.createPDF(os);
And i have my own ReplacedElementFactory as below:
public class ProfileImageReplacedElementFactory implements ReplacedElementFactory {
private final ReplacedElementFactory superFactory;
public ProfileImageReplacedElementFactory(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");
if ("div".equals(nodeName) && className.contains("profile_picture")) {
InputStream input = null;
try {
input = ...;
byte[] bytes = IOUtils.toByteArray(input);
Image image = Image.getInstance(bytes);
FSImage fsImage = new ITextFSImage(image);
if (fsImage != null) {
if ((cssWidth != -1) || (cssHeight != -1)) {
fsImage.scale(cssWidth, cssHeight);
}
return new ITextImageElement(fsImage);
}
} catch (IOException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} catch (BadElementException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} finally {
IOUtils.closeQuietly(input);
}
}
return superFactory.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
}
#Override
public void reset() {
superFactory.reset();
}
#Override
public void remove(Element e) {
superFactory.remove(e);
}
#Override
public void setFormSubmissionListener(FormSubmissionListener listener) {
superFactory.setFormSubmissionListener(listener);
}
}

Failing to extract xml value element using JDOM & Xpath

I have a method (getSingleNodeValue()) which when passed an xpatch expression will extract the value of the specified element in the xml document refered to in 'doc'. Assume doc at this point has been initialised as shown below and xmlInput is the buffer containing the xml content.
SAXBuilder builder = null;
Document doc = null;
XPath xpathInstance = null;
doc = builder.build(new StringReader(xmlInput));
When i call the method, i pass the following xpath xpression
/TOP4A/PERLODSUMDEC/TINPLD1/text()
Here is the method. It basically just takes an xml buffer and uses xpath to extract the value:
public static String getSingleNodeValue(String xpathExpr) throws Exception{
Text list = null;
try {
xpathInstance = XPath.newInstance(xpathExpr);
list = (Text) xpathInstance.selectSingleNode(doc);
} catch (JDOMException e) {
throw new Exception(e);
}catch (Exception e){
throw new Exception(e);
}
return list==null ? "?" : list.getText();
}
The above method always returns "?" i.e. nothing is found so 'list' is null.
The xml document it looks at is
<TOP4A xmlns="http://www.testurl.co.uk/enment/gqr/3232/1">
<HEAD>
<Doc>ABCDUK1234</Doc>
</HEAD>
<PERLODSUMDEC>
<TINPLD1>10109000000000000</TINPLD1>
</PERLODSUMDEC>
</TOP4A>
The same method works with other xml documents so i am not sure what is special about this one. There is no exception so the xml is valid xml. Its just that the method always sets 'list' to null. Any ideas?
Edit
Ok as suggested, here is a simple running program that demonstrates the above
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.xpath.*;
import java.io.IOException;
import java.io.StringReader;
public class XpathTest {
public static String getSingleNodeValue(String xpathExpr, String xmlInput) throws Exception{
Text list = null;
SAXBuilder builder = null;
Document doc = null;
XPath xpathInstance = null;
try {
builder = new SAXBuilder();
doc = builder.build(new StringReader(xmlInput));
xpathInstance = XPath.newInstance(xpathExpr);
list = (Text) xpathInstance.selectSingleNode(doc);
} catch (JDOMException e) {
throw new Exception(e);
}catch (Exception e){
throw new Exception(e);
}
return list==null ? "Nothing Found" : list.getText();
}
public static void main(String[] args){
String xmlInput1 = "<TOP4A xmlns=\"http://www.testurl.co.uk/enment/gqr/3232/1\"><HEAD><Doc>ABCDUK1234</Doc></HEAD><PERLODSUMDEC><TINPLD1>10109000000000000</TINPLD1></PERLODSUMDEC></TOP4A>";
String xpathExpr = "/TOP4A/PERLODSUMDEC/TINPLD1/text()";
XpathTest xp = new XpathTest();
try {
System.out.println(xp.getSingleNodeValue(xpathExpr, xmlInput1));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
When i run the above, the output is
Nothing found
Edit
I have run some further testing and it appears that if i remove the namespace url it does work. Not sure why yet. Is there any way i can tell it to ignore the namespace?
Edit
Please also note that the above is implemented on JDK1.4.1 so i dont have the options for later version of the JDKs. This is the reason why i had to stick with Jdom.
The problem is with XML namespaces: your XPath query starts by selecting a 'TOP4A' element in the default namespace. Your XML file, however, has a 'TOP4A' element in the 'http://www.testurl.co.uk/enment/gqr/3232/1' namespace instead.
Is it an option to remove the xmlns from the XML?

Categories