Add attachment annotation in pdf and executed within web application jar - annotations

I am implementing adding attachment annotation in pdf using itext annotation, but very funny is that, duing to web application framework restriction, i must put the implementation codes and attachment file within jar file. Here is a brief hierarchy of the implementation:
package example.pdf
Implementation.java
attachment.doc
Main codes:
protected void createAttachment(PdfWriter writer, Rectangle rect, String
templatePath, String fileName) throws IOException, DocumentException {
// Get instruction document
String embed =
getClass().getClassLoader().getResource(templatePath).getFile();
// The fileName here is used to display in the attachment list of the
// document
PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(
writer, embed, fileName, null);
// The fileName here is used to display on the document
PdfAnnotation attachment =
PdfAnnotation.createFileAttachment(writer, rect, fileName, fs);
// Specify the width and height of the icon area
PdfAppearance app = writer.getDirectContent().createAppearance(200, 200);
String wordIcon =
getClass().getClassLoader().getResource(WORD_ICON).getFile();
Image img = Image.getInstance(wordIcon);
img.scaleAbsolute(200, 200);
img.setAbsolutePosition(0, 0);
app.addImage(img);
attachment.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, app);
writer.addAnnotation(attachment);
}
The above codes will be called by the service class:
Rectangle rect = new Rectangle(220, 620, 250, 640);
createAttachment(pdfWriter, rect, "pdf/attachment.doc",
"attachment.doc");
It works normally under my IDE, but once i made them as a jar(including the attachment), it appears the attachment could not be found/attached by the web application. And told me that the file could not be located in system path.
I know it might be the issue that relates to class path detect mechanism(for example class loader and loading method), but it really blocked me to move further since i did not find any possible solution.
The exception:
java.io.FileNotFoundException: C:\Projects\work\test-1.3.6.0-
SNAPSHOT.jar!\pdf\attachment.doc (The system cannot find the path specified)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:90)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:188)
at java.net.URL.openStream(URL.java:1045)
at com.lowagie.text.pdf.PdfFileSpecification.fileEmbedded(Unknown Source)
at com.lowagie.text.pdf.PdfFileSpecification.fileEmbedded(Unknown Source)
at com.lowagie.text.pdf.PdfFileSpecification.fileEmbedded(Unknown Source)
at example.pdf.Implementation.createAttachment

Problem solved.
The only difference is take the input stream instead of filename, the class PdfFileSpecification has such interface for doing so.
InputStream embedInputStream =
getClass().getClassLoader().getResourceAsStream(templatePath);
byte[] buffer = new byte[1024];
embedOutputStream = new ByteArrayOutputStream();
int len = -1;
while ((len = embedInputStream.read(buffer)) != -1) {
embedOutputStream.write(buffer, 0, len);
}
// The fileName here is used to display in the attachment list of the document
PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(
writer, null, fileName, embedOutputStream.toByteArray());
// The fileName here is used to display on the document
PdfAnnotation attachment =
PdfAnnotation.createFileAttachment(writer, rect, fileName, fs);

Related

itextpdf generates blurry pictures

I am using itextpdf html2pdf tool to implement freemarker template to generate pdf
Refer to the official documentation link:
https://kb.itextpdf.com/home/it7kb/ebooks/itext-7-converting-html-to-pdf-with-pdfhtml/chapter-7-frequently-asked-questions-about-pdfhtml/can-pdfhtml-render-base64-images-to-pdf
The front end uses echarts to convert the image to base64.
The picture can be displayed clearly on the browser, but after the pdf is generated, the picture becomes blurry.
Refer to html and pdf image comparison:
html pdf
public static void main(String[] args) throws Exception{
File file = FileUtil.newFile("D:/test.html");
BufferedOutputStream outputStream = FileUtil.getOutputStream("D:/test.pdf");
BufferedInputStream inputStream = FileUtil.getInputStream(file);
ConverterProperties converterProperties=new ConverterProperties();
FontProvider fontProvider=new DefaultFontProvider();
FontProgram fontProgram = FontProgramFactory.createFont(Constant.CONFIG_PATH+File.separator+"simsun.ttc,1");
fontProvider.addFont(fontProgram);
MediaDeviceDescription mediaDeviceDescription = new MediaDeviceDescription(MediaType.SCREEN);
mediaDeviceDescription.setResolution(2000f);
converterProperties.setFontProvider(fontProvider);
converterProperties.setMediaDeviceDescription(mediaDeviceDescription);
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outputStream));
pdfDoc.setDefaultPageSize(new PageSize(1000,1700));
HtmlConverter.convertToPdf(inputStream,pdfDoc,converterProperties);
}

Map System.Drawing.Image to proper PdfName.COLORSPACE and PdfName.FILTER in iTextSharp

I'm using iTextSharp to update an image object in a PDF with a modified System.Drawing.Image. How do I properly set the PdfName.COLORSPACE and PdfName.FILTER based on the System.Drawing.Image? I'm not sure which System.Drawing.Image properties can be used for the mappings.
private void SetImageData(PdfImageObject pdfImage, System.Drawing.Image image, byte[] imageData)
{
PRStream imgStream = (PRStream)pdfImage.GetDictionary();
imgStream.Clear();
imgStream.SetData(imageData, false, PRStream.NO_COMPRESSION);
imgStream.Put(PdfName.TYPE, PdfName.XOBJECT);
imgStream.Put(PdfName.SUBTYPE, PdfName.IMAGE);
imgStream.Put(PdfName.WIDTH, new PdfNumber(image.Width));
imgStream.Put(PdfName.HEIGHT, new PdfNumber(image.Height));
imgStream.Put(PdfName.LENGTH, new PdfNumber(imageData.LongLength));
// Not sure how to properly set these entries based on the image properties
imgStream.Put(PdfName.BITSPERCOMPONENT, 8);
imgStream.Put(PdfName.COLORSPACE, PdfName.DEVICERGB);
imgStream.Put(PdfName.FILTER, PdfName.DCTDECODE);
}
I took the advice of Chris Haas and cheated by writing the System.Drawing.Image to a temporary PDF and then read it back out as a PdfImageObject.
using (MemoryStream ms = new MemoryStream())
{
using (iTextSharp.text.Document doc = new iTextSharp.text.Document())
{
using (iTextSharp.text.pdf.PdfWriter writer = iTextSharp.text.pdf.PdfWriter.GetInstance(doc))
{
doc.Open();
iTextSharp.text.Image image = iTextSharp.text.Image.GetInstance(drawingImage, drawingImage.RawFormat);
image.SimplifyColorspace();
doc.Add(image);
doc.Close();
}
}
... code that opens the mem stream with PdfReader and retrieves the image as PdfImageObj...
}
That worked but seemed like allot of side stepping for a cheat. I stepped through the code in the call to doc.Add(image) and found that eventually a PdfImage object was created from the iTextSharp.text.Image object and the PdfImage object contained all the dictionary entries that I needed. So I decided to cut some corners on the original cheat and come up with this as my final solution:
private void SetImageData(PdfImageObject pdfImageObj, byte[] imageData)
{
iTextSharp.text.Image image = iTextSharp.text.Image.GetInstance(imageData);
image.SimplifyColorSpace();
PdfImage tempPdfImage = new PdfImage(image, "TempImg", null);
PRStream imgStream = (PRStream)pdfImageObj.GetDictionary();
imgStream.Clear();
imgStream.SetDataRaw(imageData);
imgStream.Merge(tempPdfImage);
}

Change page numbers in EvoPDF

Within a pdf it is possible to change page numbering, so the first page would be page 5, etc.
(This has nothing to do with headers and footers, i'm speaking strictly about the page numbers as they appear in the pdf toolbar)
Is it possible to control those numbers with EvoPDF?
Yes, apparently with EVOPDF v5 you can set the number to be displayed on the page using the PageNumberingStartIndex property of the PdfHeaderOptions object (same for Footers). I don't know of any examples using this.
It is not possible to change the page numbering displayed by Adobe Reader using an option in the generated PDF document. What you can do is to make the PDF viewer go to a certain page in PDF document when the document is opened. You can check the Go To a Location in a PDF Page When the Document is Opened Demo . The C# code to implement this feature is:
protected void convertToPdfButton_Click(object sender, EventArgs e)
{
// Create a HTML to PDF converter object with default settings
HtmlToPdfConverter htmlToPdfConverter = new HtmlToPdfConverter();
// Set license key received after purchase to use the converter in licensed mode
// Leave it not set to use the converter in demo mode
htmlToPdfConverter.LicenseKey = "4W9+bn19bn5ue2B+bn1/YH98YHd3d3c=";
Document pdfDocument = null;
try
{
// Convert a HTML page to a PDF document object
pdfDocument = htmlToPdfConverter.ConvertUrlToPdfDocumentObject(urlTextBox.Text);
int goToPageNumber = int.Parse(pageNumberTextBox.Text);
if (goToPageNumber > pdfDocument.Pages.Count)
{
return;
}
// Get destination PDF page
PdfPage goToPage = pdfDocument.Pages[goToPageNumber - 1];
// Get the destination point in PDF page
float goToX = float.Parse(xLocationTextBox.Text);
float goToY = float.Parse(yLocationTextBox.Text);
PointF goToLocation = new PointF(goToX, goToY);
// Get the destination view mode
DestinationViewMode viewMode = SelectedViewMode();
// Create the destination in PDF document
ExplicitDestination goToDestination = new ExplicitDestination(goToPage, goToLocation, viewMode);
// Set the zoom level when the destination is displayed
if (viewMode == DestinationViewMode.XYZ)
goToDestination.ZoomPercentage = int.Parse(zoomLevelTextBox.Text);
// Set the document Go To open action
pdfDocument.OpenAction.Action = new PdfActionGoTo(goToDestination);
// Save the PDF document in a memory buffer
byte[] outPdfBuffer = pdfDocument.Save();
// Send the PDF as response to browser
// Set response content type
Response.AddHeader("Content-Type", "application/pdf");
// Instruct the browser to open the PDF file as an attachment or inline
Response.AddHeader("Content-Disposition", String.Format("attachment; filename=Go_To_Page_Open_Action.pdf; size={0}", outPdfBuffer.Length.ToString()));
// Write the PDF document buffer to HTTP response
Response.BinaryWrite(outPdfBuffer);
// End the HTTP response and stop the current page processing
Response.End();
}
finally
{
// Close the PDF document
if (pdfDocument != null)
pdfDocument.Close();
}
}

creating a pdf from a template in itextsharp and outputting as content disposition.

I would like to open an existing pdf, add some text and then output as content disposition using itext sharp. I have the following code. Where it falls down it is that i want to output as memory stream but need to filestream to open the original file.
Here's what i have. Obviously defining PdfWriter twice won't work.
public static void Create(string path)
{
var Response = HttpContext.Current.Response;
Response.Clear();
Response.ContentType = "application/pdf";
System.IO.MemoryStream m = new System.IO.MemoryStream();
Document document = new Document();
PdfWriter wri = PdfWriter.GetInstance(document, new FileStream(path, FileMode.Create));
PdfWriter.GetInstance(document, m);
document.Open();
document.Add(new Paragraph(DateTime.Now.ToString()));
document.NewPage();
document.Add(new Paragraph("Hello World"));
document.Close();
Response.OutputStream.Write(m.GetBuffer(), 0, m.GetBuffer().Length);
Response.OutputStream.Flush();
Response.OutputStream.Close();
Response.End();
}
You've got a couple of problems that I'll try to walk you through.
First, the Document object is only for working with new PDFs, not modifying existing ones. Basically the Document object is a bunch of wrapper classes that abstract away the underlying parts of the PDF spec and allow you to work with higher level things like paragraphs and reflowable content. These abstractions turn what you think of "paragraphs" into raw commands that write the paragraph one line at a time with no relationship between lines. When working with an existing document there's no safe way to say how to reflow text so these abstractions aren't used.
Instead you want to use the PdfStamper object. When working with this object you have two choices for how to work with potentially overlapping content, either your new text gets written on top of existing content or your text gets written below it. The two methods GetOverContent() or GetUnderContent() of an instantiated PdfStamper object will return a PdfContentByte object that you can then write text with.
There's two main ways to write text, either manually or through a ColumnText object. If you've done HTML you can think of the ColumnText object as using a big fixed-position single row, single column <TABLE>. The advantage of the ColumnText is that you can use the higher level abstractions such as Paragraph.
Below is a full working C# 2010 WinForms app targeting iTextSharp 5.1.2.0 that show off the above. See the code comments for any questions. It should be pretty easy to convert this to ASP.Net.
using System;
using System.IO;
using System.Windows.Forms;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
string existingFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "file1.pdf");
string newFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "file2.pdf");
using (FileStream fs = new FileStream(existingFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (Document doc = new Document(PageSize.LETTER)) {
using (PdfWriter writer = PdfWriter.GetInstance(doc, fs)) {
doc.Open();
doc.Add(new Paragraph("This is a test"));
doc.Close();
}
}
}
//Bind a PdfReader to our first document
PdfReader reader = new PdfReader(existingFile);
//Create a new stream for our output file (this could be a MemoryStream, too)
using (FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
//Use a PdfStamper to bind our source file with our output file
using (PdfStamper stamper = new PdfStamper(reader, fs)) {
//In case of conflict we want our new text to be written "on top" of any existing content
//Get the "Over" state for page 1
PdfContentByte cb = stamper.GetOverContent(1);
//Begin text command
cb.BeginText();
//Set the font information
cb.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1250, false), 16f);
//Position the cursor for drawing
cb.MoveText(50, 50);
//Write some text
cb.ShowText("This was added manually");
//End text command
cb.EndText();
//Create a new ColumnText object to write to
ColumnText ct = new ColumnText(cb);
//Create a single column who's lower left corner is at 100x100 and upper right is at 500x200
ct.SetSimpleColumn(100,100,500,200);
//Add a higher level object
ct.AddElement(new Paragraph("This was added using ColumnText"));
//Flush the text buffer
ct.Go();
}
}
this.Close();
}
}
}
As to your second problem about the FileStream vs MemoryStream, if you look at the method signature for almost every (actually all as far as I know) method within iTextSharp you'll see that they all take a Stream object and not just a FileStream object. Any time you see this, even outside of iTextSharp, this means that you can pass in any subclass of Stream which includes the MemoryStream object, everything else stays the same.
The code below is a slightly modified version of the one above. I've removed most of the comments to make it shorter. The main change is that we're using a MemoryStream instead of a FileStream. Also, when we're done with the PDF when need to close the PdfStamper object before accessing the raw binary data. (The using statment will do this for us automatically later but it also closes the stream so we need to manually do it here.)
One other thing, never, ever use the GetBuffer() method of the MemoryStream. It sounds like what you want (and I have mistakenly used it, too) but instead you want to use ToArray(). GetBuffer() includes uninitialized bytes which usually produces corrupt PDFs. Also, instead of writing to the HTTP Response stream I'm saving the bytes to array first. From a debugging perspective this allows me to finish all of my iTextSharp and System.IO code and make sure that it is correct, then do whatever I want with the raw byte array. In my case I don't have a web server handy so I'm writing them to disk but you could just as easily call Response.BinaryWrite(bytes)
string existingFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "file1.pdf");
string newFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "file2.pdf");
PdfReader reader = new PdfReader(existingFile);
byte[] bytes;
using(MemoryStream ms = new MemoryStream()){
using (PdfStamper stamper = new PdfStamper(reader, ms)) {
PdfContentByte cb = stamper.GetOverContent(1);
ColumnText ct = new ColumnText(cb);
ct.SetSimpleColumn(100,100,500,200);
ct.AddElement(new Paragraph("This was added using ColumnText"));
ct.Go();
//Flush the PdfStamper's buffer
stamper.Close();
//Get the raw bytes of the PDF
bytes = ms.ToArray();
}
}
//Do whatever you want with the bytes
//Below I'm writing them to disk but you could also write them to the output buffer, too
using (FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
fs.Write(bytes, 0, bytes.Length);
}
The second part of your question title says:
"outputting as content disposition"
If that's what you really want you can do this:
Response.AddHeader("Content-Disposition", "attachment; filename=DESIRED-FILENAME.pdf");
Using a MemoryStream is unnecessary, since Response.OutputStream is available. Your example code is calling NewPage() and not trying to add the text to an existing page of your PDF, so here's one way to do what you asked:
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", "attachment; filename=itextTest.pdf");
PdfReader reader = new PdfReader(readerPath);
// store the extra text on the last (new) page
ColumnText ct = new ColumnText(null);
ct.AddElement(new Paragraph("Text on a new page"));
int numberOfPages = reader.NumberOfPages;
int newPage = numberOfPages + 1;
// get all pages from PDF "template" so we can copy them below
reader.SelectPages(string.Format("1-{0}", numberOfPages));
float marginOffset = 36f;
/*
* we use the selected pages above with a PdfStamper to copy the original.
* and no we don't need a MemoryStream...
*/
using (PdfStamper stamper = new PdfStamper(reader, Response.OutputStream)) {
// use the same page size as the __last__ template page
Rectangle rectangle = reader.GetPageSize(numberOfPages);
// add a new __blank__ page
stamper.InsertPage(newPage, rectangle);
// allows us to write content to the (new/added) page
ct.Canvas = stamper.GetOverContent(newPage);
// add text at an __absolute__ position
ct.SetSimpleColumn(
marginOffset, marginOffset,
rectangle.Right - marginOffset, rectangle.Top - marginOffset
);
ct.Go();
}
I think you've already figured out that the Document / PdfWriter combination doesn't work in this situation :) That's the standard method for creating a new PDF document.

iText AddImage() to specific page

I'm having a problem trying to locate a PdfContentByte directly into an specific page. My problem is: I need to add an Image for each page (That works) and need to add a QRCode to each of the pages at the right bottom corner but this works only for the first Page and I don't know how to repeat it on the other ones.
This is my code:
public string GeneratePDFDocument(Atomic.Development.Montenegro.Data.Entities.Document document, Stamp stamp)
{
string filename = #"C:\Users\Sheldon\Desktop\Pdf.Pdf";
FileStream fs = new FileStream(filename, FileMode.Create);
iTextSharp.text.Document pdfDocument = new iTextSharp.text.Document(PageSize.LETTER, PAGE_LEFT_MARGIN, PAGE_RIGHT_MARGIN, PAGE_TOP_MARGIN, PAGE_BOTTOM_MARGIN);
iTextSharp.text.pdf.PdfWriter writer = iTextSharp.text.pdf.PdfWriter.GetInstance(pdfDocument, fs);
pdfDocument.Open();
int count = document.Pages.Count;
foreach (Page page in document.Pages)
{
Image img = Image.GetInstance(page.Image);
img.ScaleToFit(PageSize.LETTER.Width-(PAGE_LEFT_MARGIN + PAGE_RIGHT_MARGIN), PageSize.LETTER.Height-(PAGE_TOP_MARGIN + PAGE_BOTTOM_MARGIN));
pdfDocument.Add(img);
PlaceCodeBar(writer);
}
pdfDocument.Close();
writer.Close();
fs.Close();
return filename;
}
private static void PlaceCodeBar(iTextSharp.text.pdf.PdfWriter writer)
{
String codeText = "TEXT TO ENCODE";
iTextSharp.text.pdf.BarcodePDF417 pdf417 = new iTextSharp.text.pdf.BarcodePDF417();
pdf417.SetText(codeText);
Image img = pdf417.GetImage();
iTextSharp.text.pdf.BarcodeQRCode qrcode = new iTextSharp.text.pdf.BarcodeQRCode(codeText, 1, 1, null);
img = qrcode.GetImage();
iTextSharp.text.pdf.PdfContentByte cb = writer.DirectContent;
cb.SaveState();
cb.BeginText();
img.SetAbsolutePosition(PageSize.LETTER.Width-PAGE_RIGHT_MARGIN-img.ScaledWidth, PAGE_BOTTOM_MARGIN);
cb.AddImage(img);
cb.EndText();
cb.RestoreState();
}
So add it in your foreach (Page...) loop:
foreach (Page page in document.Pages)
{
Image img = Image.GetInstance(page.Image);
img.ScaleToFit(PageSize.LETTER.Width-(PAGE_LEFT_MARGIN + PAGE_RIGHT_MARGIN), PageSize.LETTER.Height-(PAGE_TOP_MARGIN + PAGE_BOTTOM_MARGIN));
pdfDocument.Add(img);
PlaceCodeBar(writer);
}
If this is a second pass on the same PDF (you've closed it then opened it again), use a PdfStamper rather than a PdfWriter. You can then get the direct content of each page rather than the one direct content that is reused (and reset) for each page.
PS: Drop the BeginText() and EndText() calls. Those operators should only be used when actually drawing text/setting fonts/etc. No line art. No images. The SaveState()/RestoreState() are good though. Definitely keep those.
I just figure out how to solve the problem. Just delete the cb.SaveState() and cb.RestoreState() and it put the image on the page is actually active.