Add Gradient Shading using Stamper - itext

I have code that uses iTextSharp to add a variety of colored rectangles to an existing PDF document using the PdfStamper object with code like the following:
Dim reader As New PdfReader(byte_contets_of_PDF_file)
Dim stamper As New PdfStamper(reader, someOutputBuffer)
Dim under As PdfContentByte = stamper.GetUnderContent(pageNumber)
under.SetColorFill(BaseColors.RED)
under.Rectangle(x, y, rectWidth, rectHeight)
under.Fill()
This works swimmingly for drawing monochromatic rectangles on an existing PDF document, but I was hoping I could place rectangles that use a color gradient.
My research has turned up examples using the PdfShading and PdfShadingPattern objects, but those require a PdfWriter. To my understanding, the PdfWriter is used for creating new PDFs and not for updating existing PDFs.
Is it possible to add rectangles to an existing PDF document that have a gradient?
Thanks

I assume you have read the answer to this question: How to add a shading pattern to a custom shape
I assume that your problem is that you don't know which parameter to use instead of writer.
If that is the case, use 'stamper.GetOverContent(p)' instead of writer.DirectContent and use stamper.Writer instead of writer:
int p = 1; // or whatever page number applies
PdfContentByte canvas = stamper.GetOverContent(p);
float x = 36;
float y = 740;
float side = 70;
PdfShading axial = PdfShading.SimpleAxial(writer, x, y,
x + side, y, BaseColor.PINK, BaseColor.BLUE);
PdfShadingPattern shading = new PdfShadingPattern(axial);
canvas.SetShadingFill(shading);
canvas.MoveTo(x,y);
canvas.LineTo(x + side, y);
canvas.LineTo(x + (side / 2), (float)(y + (side * Math.sin(Math.PI / 3))));
canvas.ClosePathFillStroke();

Related

ITextSharp - Draw Rectangle top left corner

I'm trying to draw a rectangle to the very top left of a page using ITextSharp (5.5.13). I want to draw in the page margins. However, the rectangle is around 25 pixels too low. How can I draw the rectangle in the top left corner?
Below is how I'm adding the rectangle to the page:
using (PdfReader reader = new PdfReader(inputPdf.FullName))
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(outputPdf.FullName, FileMode.Create)))
{
PdfContentByte contentByte = stamper.GetOverContent(1);
PdfDocument doc = contentByte.PdfDocument;
float X = 0.0f;
float Y = 0.0f;
float Height = Utilities.InchesToPoints(0.50f);
float Width = Utilities.InchesToPoints(0.50f);
float llx = (doc.Left - doc.LeftMargin) + X;
float lly = (doc.Top - doc.TopMargin) - (Height + Y);
float urx = (doc.Left - doc.LeftMargin) + Width + X;
float ury = (doc.Top - doc.TopMargin) - Y;
Rectangle rectangle = new Rectangle(llx, lly, urx, ury)
{
BackgroundColor = BaseColor.BLACK
};
contentByte.Rectangle(rectangle);
}
Below are the debug values for each aforementioned variable:
Whenever you use a PdfStamper, the PdfDocument you can retrieve from its parts does not contain sensible information, it merely is a dummy object.
Thus, don't try to determine the page size from that PdfDocument doc, instead use the appropriate methods or properties of your PdfReader reader, e.g.
/** Gets the crop box without taking rotation into account. This
* is the value of the /CropBox key. The crop box is the part
* of the document to be displayed or printed. It usually is the same
* as the media box but may be smaller. If the page doesn't have a crop
* box the page size will be returned.
* #param index the page number. The first page is 1
* #return the crop box
*/
virtual public Rectangle GetCropBox(int index)

iText gradient issue in landscape

Having used gradients in iText in the past with no problems, suddenly i have an issue with it not working and narrowed it down to the fact that
this time i am working in landscape, (technically PageSize.A4.rotate()). The rotate() makes something happen that prevents the gradient from working as expected (by me).
To illustrate, i've adapted the example of the pink-blue triangle http://developers.itextpdf.com/question/how-add-shading-pattern-custom-shape
public class GradientProblem {
public static void main(String[] args) throws FileNotFoundException, DocumentException {
Document doc = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(doc, new FileOutputStream(new File("gradientProblem.pdf")));
doc.open();
drawSexyTriangle(writer);
doc.setPageSize(PageSize.A4.rotate());
doc.newPage();
drawSexyTriangle(writer);
doc.close();
}
private static void drawSexyTriangle(PdfWriter writer) {
PdfContentByte canvas = writer.getDirectContent();
float x = 36;
float y = 400;
float side = 70;
PdfShading axial = PdfShading.simpleAxial(writer, x, y, x + side, y, BaseColor.PINK, BaseColor.BLUE);
PdfShadingPattern shading = new PdfShadingPattern(axial);
canvas.setShadingFill(shading);
canvas.moveTo(x,y);
canvas.lineTo(x + side, y);
canvas.lineTo(x + (side / 2), (float)(y + (side * Math.sin(Math.PI / 3))));
canvas.closePathFillStroke();
}
}
Notice that the shading of the triangle on the first page is pink-blue, but on the second rotated page is just blue.
is this a bug, or expected behaviour?
if expected, how does the co-ordinate system work, ie what do the x0, y0, x1, y1 parameters to simpleAxial really mean?
The obvious work-around is to construct the appropriate page-size without rotation.
However using rotate() appears to be the advised approach, and when i have gradient code deep inside a re-useable table or cell event i can't really know how the page was constructed (or can i, i just don't know how?)
Any tips or insight would be much appreciated.
PS: tried iText 5.5.8 and 5.5.9
The problem is that while itext content adding functionalities do take the page rotation into account (they translate the given coordinates so that in the rotated page x goes right and y goes up and the origin is in the lower left), the shading pattern definitions (which are not part of the page content but externally defined) don't.
Thus, you have to make the shading definition rotation aware, e.g. like this:
Document doc = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(doc, new FileOutputStream(new File(RESULT_FOLDER, "gradientProblem.pdf")));
doc.open();
drawSexyTriangle(writer, false);
doc.setPageSize(PageSize.A4.rotate());
doc.newPage();
drawSexyTriangle(writer, true);
doc.close();
with
private static void drawSexyTriangle(PdfWriter writer, boolean rotated)
{
PdfContentByte canvas = writer.getDirectContent();
float x = 36;
float y = 400;
float side = 70;
PdfShading axial = rotated ?
PdfShading.simpleAxial(writer, PageSize.A4.getRight() - y, x, PageSize.A4.getRight() - y, x + side, BaseColor.PINK, BaseColor.BLUE)
: PdfShading.simpleAxial(writer, x, y, x + side, y, BaseColor.PINK, BaseColor.BLUE);
PdfShadingPattern shading = new PdfShadingPattern(axial);
canvas.setShadingFill(shading);
canvas.moveTo(x,y);
canvas.lineTo(x + side, y);
canvas.lineTo(x + (side / 2), (float)(y + (side * Math.sin(Math.PI / 3))));
canvas.closePathFillStroke();
}
(DrawGradient.java)

Scale image to fill multiple pages with iText

I am trying to scale an image with iText (on a new PDF document) in order to make it fill the width of the page without streching, so that it could take several pages.
I've found a lot of solutions but they were pretty complicated and I don't really like coding like that. The best solution I've found till now (from another question on SO) is using PdfTable but it always uses a single page, scaling the image.
// Load image from external storage
Image image = Image.getInstance(path + "/img.png");
// Calculate ratio
float width = PageSize.A4.getWidth();
float heightRatio = image.getHeight() * width / image.getWidth();
Document document = new Document();
document.open();
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
PdfPCell c = new PdfPCell(image, true);
c.setBorder(PdfPCell.NO_BORDER);
c.setPadding(0);
// Set image dimensions
c.getImage().scaleToFit(width, heightRatio);
table.addCell(c);
document.add(table);
// Write PDF file
document.close();
Any suggestions?
Ok I finally decided to go the way I didn't want to go, since it seems to be the only way: adding the same image to every page and setting the proper vertical offset to each one. The offset gets calculated as the number of pages left to draw + the gap the remains blank. For each step I decrement the number of pages until there's nothing left to draw.
// Open new PDF file
Document document = new Document();
PdfWriter pdfWriter = PdfWriter.getInstance(document, new FileOutputStream(getSharedDirPath() + File.separator + "file.pdf"));
document.open();
PdfContentByte content = pdfWriter.getDirectContent();
// Load image from external folder
Image image = Image.getInstance(path + "/img.png");
image.scaleAbsolute(PageSize.A4);
image.setAbsolutePosition(0, 0);
float width = PageSize.A4.getWidth();
float heightRatio = image.getHeight() * width / image.getWidth();
int nPages = (int) (heightRatio / PageSize.A4.getHeight());
float difference = heightRatio % PageSize.A4.getHeight();
while (nPages >= 0) {
document.newPage();
content.addImage(image, width, 0, 0, heightRatio, 0, -((--nPages * PageSize.A4.getHeight()) + difference));
}
// Write PDF file
document.close();
Honestly I don't like this solution, I thought it was possible to auto-adjust dimensions as I do in a text editor, but after all it was not very difficult.....it just took me three days to figure out how the whole PDF thing worked.

How to create a multi page TOC using itext?

I need to create TOC in a PDF. It can be of 1 page or multi pages depending on the number of pages in PDF. I have learnt that PdfStamper, PdfAction, PdfAnnotaion can be used to achieve this.
I am currently merging more than one document and creating both bookmarks and TOC for all the documents in JAVA. I have got rid of bookmarks but got stuck on multi-pages TOC.
Also, please explain this line of your code - link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);... What I have understood is you are passing the dimensions of a rectangle on the page on which it will be going after clicking the link (, 36, ct.getYLine(), 559, y, ).. And thus I am facing an issue of not going to correct position of the page on clicking the link, in the case if page size is different from US Letter Portrait.
Here is the snippet -
int tocPages = 1;
Document tocDocument = new Document();
String tocFilename ="toc-filename";
Phrase tocPhrase =
new Phrase("Table of Contents", new Font(Font.FontFamily.HELVETICA, 20, Font.BOLD, BaseColor.BLACK));
PdfWriter writer = PdfWriter.getInstance(tocDocument, new FileOutputStream(tocFilename));
tocDocument.open();
tocDocument.add(new Paragraph(tocPhrase));
PdfReader reader = new PdfReader(tocFilename);
page = copy.getImportedPage(reader, tocPages);
stamp = copy.createPageStamp(page);
float y = 770;
ColumnText ct = new ColumnText(stamp.getOverContent());
ct.setSimpleColumn(36, 36, 559, y);
for (Map.Entry<Integer, String> entry : toc.entrySet()) {
if (y <= 20) {
copy.addPage(page);
copy.newPage(); //(tried with writer.newPage() and tocDocument.newPage(), not working )
page = copy.getImportedPage(reader, ++tocPages);
}
p = new Paragraph(entry.getValue());
p.add(new Chunk(new DottedLineSeparator()));
p.add(String.valueOf(entry.getKey() + 1));
ct.addElement(p);
ct.go();
action = PdfAction.gotoLocalPage("p" + entry.getKey(), false);
link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);
stamp.addAnnotation(link);
y = ct.getYLine();
}
ct.go();
stamp.alterContents();
copy.addPage(page);
tocDocument.close();
reader.close();
com.itextpdf.text.exceptions.InvalidPdfException: PDF header signature not found.
at com.itextpdf.text.pdf.PRTokeniser.getHeaderOffset(PRTokeniser.java:227)
at com.itextpdf.text.pdf.PdfReader.getOffsetTokeniser(PdfReader.java:442)
at com.itextpdf.text.pdf.PdfReader.(PdfReader.java:176)
at com.itextpdf.text.pdf.PdfReader.(PdfReader.java:219)
at com.itextpdf.text.pdf.PdfReader.(PdfReader.java:207)
at com.itextpdf.text.pdf.PdfReader.(PdfReader.java:197)
Excetion at line - PdfReader reader = new PdfReader(tocFilename);
You have adapted your question so that it became a question about this snippet from my answer to Create Index File(TOC) for merged pdf using itext library in java
Paragraph p;
PdfAction action;
PdfAnnotation link;
float y = 770;
ColumnText ct = new ColumnText(stamp.getOverContent());
ct.setSimpleColumn(36, 36, 559, y);
for (Map.Entry<Integer, String> entry : toc.entrySet()) {
p = new Paragraph(entry.getValue());
p.add(new Chunk(new DottedLineSeparator()));
p.add(String.valueOf(entry.getKey()));
ct.addElement(p);
ct.go();
action = PdfAction.gotoLocalPage("p" + entry.getKey(), false);
link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);
stamp.addAnnotation(link);
y = ct.getYLine();
}
ct.go();
Your question was reduced to: please explain this line of your code:
link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);
I have pasted the complete snippet, because this line can not be explained out of context,
In the snippet, we create a link annotation using the PdfAnnotation class. A link annotation is a area somewhere on a page, that triggers an action when clicked.
Which action is triggered in this case? That's what the action object is about, in this case, it jumps to a local page that is defined by a named destination. This is very similar to HTML where you have <a name="dest" /> and Jump to a specific destination on the current page.
When creating a PdfAnotation, you always need a PdfWriter instance. In this case, we are merging documents using a PdfCopy instance named copy. As PdfCopy extends PdfWriter, we can pass the copy instance as a parameter.
Finally, we define the clickable area. This is always a rectangle that is defined using two coordinates: the coordinate of the lower-left corner and the coordinate of the upper-right corner.
In the above snippet, we add paragraphs using ColumnText. ColumnText allows you to get information about the current y position after adding content. For instance, when we do this:
ct.addElement(p);
ct.go();
We can do this to get the current Y-coordinate:
float y = ct.getYLine();
In our code snippet, we keep track of the previous y value (the Y position before we add p) and we use the current value of ct.getYLine() to get the current y position.
This way, I can define the coordinate of the lower-left corner like this:
float llx = 36;
float lly = ct.getYLine();
And the coordinate of the upper-right corner like this:
float urx = 559;
float ury = y;
These are the values that you can see when we construct the link annotation.
I have hardcode the x values. They are based on the fact that I am creating a document with pages of size A4 and margins of half an inch. An A-4 page is 595 user units wide. To the left, I have a margin of 36 user units; to the right I also have a margin of 36 user units, which I have to subtract from the width of the page: 595 - 36 = 559.
If you have a page of which the format is LETTER, you need to adapt these values. However: it's better to calculate them based on the actual value of the MediaBox / CropBox of the existing page. This way, your code keeps working when you're accidentally confronted with documents that have a different page size.
You can read more about the MediaBox and the CropBox in my answer to this question: How to get dimensions of each page of a pdf file

iText - How to stamp image on existing PDF and create an anchor

I have an existing document, onto which I would like to stamp an image at an absolute position.
I am able to do this, but I would also like to make this image clickable: when a user clicks
on the image I would like the PDF to go to the last page of the document.
Here is my code:
PdfReader readerOriginalDoc = new PdfReader("src/main/resources/test.pdf");
PdfStamper stamper = new PdfStamper(readerOriginalDoc,new FileOutputStream("NewStamper.pdf"));
PdfContentByte content = stamper.getOverContent(1);
Image image = Image.getInstance("src/main/resources/images.jpg");
image.scaleAbsolute(50, 20);
image.setAbsolutePosition(100, 100);
image.setAnnotation(new Annotation(0, 0, 0, 0, 3));
content.addImage(image);
stamper.close();
Any idea how to do this ?
You are using a technique that only works when creating documents from scratch.
Please take a look at the AddImageLink example to find out how to add an image and a link to make that image clickable to an existing document:
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
Image img = Image.getInstance(IMG);
float x = 10;
float y = 650;
float w = img.getScaledWidth();
float h = img.getScaledHeight();
img.setAbsolutePosition(x, y);
stamper.getOverContent(1).addImage(img);
Rectangle linkLocation = new Rectangle(x, y, x + w, y + h);
PdfDestination destination = new PdfDestination(PdfDestination.FIT);
PdfAnnotation link = PdfAnnotation.createLink(stamper.getWriter(),
linkLocation, PdfAnnotation.HIGHLIGHT_INVERT,
reader.getNumberOfPages(), destination);
link.setBorder(new PdfBorderArray(0, 0, 0));
stamper.addAnnotation(link, 1);
stamper.close();
}
You already have the part about adding the image right. Note that I create parameters for the position of the image as well as its dimensions:
float x = 10;
float y = 650;
float w = img.getScaledWidth();
float h = img.getScaledHeight();
I use these values to create a Rectangle object:
Rectangle linkLocation = new Rectangle(x, y, x + w, y + h);
This is the location for the link annotation we are creating with the PdfAnnotation class. You need to add this annotation separately using the addAnnotation() method.
You can take a look at the result here: link_image.pdf
If you click on the i icon, you jump to the last page of the document.