I'm using iTextSharp to draw markup graphics in PDF documents, using PdfAnnotation.CreateInk. I'm trying to draw rectangles, passing in an array of five co-ordinates. I know iTextSharp has a dedicated function for drawing rectangles, but I'm trying to use just one method to draw the various kinds of markup lines I need, and it seems that CeateInk should be able to do that.
The problem is that CreateInk is drawing the rectangle with curves rather than corners. I haven't been able to figure out how to change this. This answer suggests that the solution might be to create a PdfAppearance; I haven't yet figured out whether that will work. Here's my code (the first bit converts a list of list of points to an array of arrays, which InkList requires):
public void MarkupInk(List<List<float>> InkList){
float[][] Coords = new float[InkList.Count][];
for (int i = 0; i < InkList.Count; i++) {
float[] thisarr = InkList [i].ToArray ();
Coords[i] = new float[InkList[i].Count];
System.Array.Copy (thisarr, Coords [i], InkList [i].Count);
}
using(MemoryStream ms = new MemoryStream ()){
PdfReader reader = new PdfReader (textmaster.pdfDocArr);
PdfStamper stamper = new PdfStamper (reader, ms);
pagerect = reader.GetPageSize (textmaster.currentfirstpage + 1);
PdfAnnotation an2 = PdfAnnotation.CreateInk (stamper.Writer, pagerect, "", Coords);
an2.Color = strokeColor;
an2.BorderStyle = new PdfBorderDictionary (strokeWeight, PdfBorderDictionary.STYLE_SOLID);
stamper.AddAnnotation (an2, textmaster.currentfirstpage+1);
stamper.Close ();
textmaster.pdfDocArr = ms.ToArray ();
reader.Close ();
}
}
Any suggestions are much appreciated. Thanks!
EDIT: following #mkl's code I now have code that creates PDFAnnotations with appearances. And most of those annotations show up correctly in the viewing applications I use. But there is one odd behavior that I have not been able to fix.
What's happening is that the most recently created annotation does not appear in the viewing applications until I've created another annotation. So if I create annotation A, it's invisible until I create annotation B, at which point annotation A appears and B does not. Creating annotation C causes annotation B to appear, and so on.
This behavior persists even if I close the pdf file and the viewing application and re-load from disk. So the data describing the last-created annotation exists as part of the pdf file, but it doesn't render until I've created a new annotation.
I suspect there's something I'm still missing in the code I'm using to create the annotations and pdfAppearances. Here's code that creates a single line annotation:
public void WriteLineAnnotation(List<float> polyCoords){
using (MemoryStream ms = new MemoryStream ()) {
PdfReader reader = new PdfReader (textmaster.pdfDocArr);
PdfStamper stamper = new PdfStamper (reader, ms) { AnnotationFlattening = true };
pagerect = reader.GetPageSize (textmaster.currentfirstpage + 1);
//Create the pdfAnnotation polyline
PdfAnnotation polyann = PdfAnnotation.CreatePolygonPolyline (stamper.Writer, pagerect, "", false, new PdfArray (polyCoords.ToArray ()));
polyann.Color = strokeColor;
polyann.BorderStyle = new PdfBorderDictionary (strokeWeight, PdfBorderDictionary.STYLE_SOLID);
polyann.Flags = iTextSharp.text.pdf.PdfAnnotation.FLAGS_PRINT;
//create the PdfAppearance and set attributes
PdfAppearance app = PdfAppearance.CreateAppearance (stamper.Writer, pagerect.Width, pagerect.Height);
app.SetColorStroke (strokeColor);
app.MoveTo (polyCoords [0], polyCoords [1]);
app.LineTo (polyCoords [2], polyCoords [3]);
app.Stroke ();
//set the annotation's appearance, add annotation, clean up
polyann.SetAppearance (PdfName.N, app);
stamper.AddAnnotation (polyann, textmaster.currentfirstpage + 1);
stamper.Close ();
reader.Close ();
//create bytearray from memorystream and send to pdf renderer
textmaster.pdfDocArr = ms.ToArray ();
}
}
[/code]
Is there something obvious that I'm missing? Thanks in advance for any help.
Which annotation type to use
I know iTextSharp has a dedicated function for drawing rectangles, but I'm trying to use just one method to draw the various kinds of markup lines I need, and it seems that CeateInk should be able to do that.
Please be aware that iText not merely has separate functions for those different forms, these different functions also create different types of PDF annotations.
In particular the Ink annotation -- which you would like to use for all forms -- is specified as
An ink annotation (PDF 1.3) represents a freehand “scribble” composed of one or more disjoint paths.
(Section 12.5.6.13 - Ink Annotations - ISO 32000-1)
As a freehand “scribble” commonly is not considered to be a sequence of straight lines and sharp corners but instead more soft and rounded; thus, it is only natural that PDF viewers will display an ink annotation given by coordinates of the corners of a rectangle with curves rather than corners.
Of course you can use an appearance stream to provide a visualization of the appearance but that would be a small misuse of this PDF feature.
Instead I would propose you use a different kind of annotation to draw the various kinds of markup lines you need: Polyline annotations. They are specified as:
Polygon annotations (PDF 1.5) display closed polygons on the page. Such polygons may have any number of vertices connected by straight lines. Polyline annotations (PDF 1.5) are similar to polygons, except that the first and last vertex are not implicitly connected.
(Section 12.5.6.9 - Polygon and Polyline Annotations - ISO 32000-1)
iText(Sharp) provides a method for this kind of annotations, too:
/**
* Creates a polygon or -line annotation
* #param writer the PdfWriter
* #param rect the annotation position
* #param contents the textual content of the annotation
* #param polygon if true, the we're creating a polygon annotation, if false, a polyline
* #param vertices an array with the vertices of the polygon or -line
* #since 5.0.2
*/
public static PdfAnnotation CreatePolygonPolyline(
PdfWriter writer, Rectangle rect, String contents, bool polygon, PdfArray vertices)
You might eventually still have to create annotations as not all PDF viewers, in particular so called "pre-viewers", generate appearances but instead count on appearances being provided in PDFs they pre-display...
Examples
Without own appearance
using (PdfReader pdfReader = new PdfReader(inputPath))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream))
{
PdfArray vertices = new PdfArray(new int[]{ 100, 100, 300, 300, 100, 300, 300, 100 });
PdfAnnotation polyLine = PdfAnnotation.CreatePolygonPolyline(pdfStamper.Writer, pdfReader.GetPageSize(1),
"", false, vertices);
polyLine.Color = BaseColor.GREEN;
pdfStamper.AddAnnotation(polyLine, 1);
}
adds this:
in PDF viewers supporting annotations including appearance generation according to the specification.
With own appearance
using (PdfReader pdfReader = new PdfReader(inputPath))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream))
{
Rectangle pageSize = pdfReader.GetPageSize(1);
PdfArray vertices = new PdfArray(new int[] { 100, 100, 300, 300, 100, 300, 300, 100 });
PdfAnnotation polyLine = PdfAnnotation.CreatePolygonPolyline(pdfStamper.Writer, pageSize,
"", false, vertices);
polyLine.Color = BaseColor.GREEN;
PdfAppearance appearance = PdfAppearance.CreateAppearance(pdfStamper.Writer, pageSize.Width, pageSize.Height);
appearance.SetColorStroke(BaseColor.RED);
appearance.MoveTo(100, 100);
appearance.LineTo(300, 300);
appearance.LineTo(100, 300);
appearance.LineTo(300, 100);
appearance.Stroke();
polyLine.SetAppearance(PdfName.N, appearance);
pdfStamper.AddAnnotation(polyLine, 1);
}
adds this
in PDF viewers supporting annotations which bring along their annotation according to the specification.
(I explicitly used a different color in my appearance to make sure the PDF viewer shows my appearance and does not create one itself.)
Related
I need to add stamp or image annotation in a signed PDF but it gives me an "invalid signature" error and, after moving this annotation, it gives me an "at least one signature required validating" error. How can I solve this?
I also found a solution but for text annotation in this question.
It worked fine with text annotation but I cannot do the same for the stamp.
iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(#"D:\\11.jpg");
float w = 100;
float h = 100;
iTextSharp.text.Rectangle location = new iTextSharp.text.Rectangle(36, 770 - h, 36 + w, 770);
PdfAnnotation stamp = PdfAnnotation.CreateStamp(stamper.Writer, location, null, "ITEXT");
img.SetAbsolutePosition(0, 0);
PdfContentByte cb = stamper.GetOverContent(1);
PdfAppearance app = cb.CreateAppearance(100, 100);
app.AddImage(img);
stamp.SetAppearance(PdfName.N, app);
stamper.AddAnnotation(stamp, 1);
This
PdfContentByte cb = stamper.GetOverContent(1);
is the line causing the invalidation of the signature.
Calling stamper.GetOverContent (or stamper.GetUnderContent) already prepares the page contents for the addition of new content. This preparation already is considered a change of page content. As page content changes to a signed PDF are forbidden, this invalidates the signature.
You only use this PdfContentByte cb to create a PdfAppearance for your stamp annotation:
PdfAppearance app = cb.CreateAppearance(100, 100);
Fortunately you don't need cb to create a PdfAppearance, there also is a static PdfAppearance method to do so. Thus, simply replace
PdfContentByte cb = stamper.GetOverContent(1);
PdfAppearance app = cb.CreateAppearance(100, 100);
by
PdfAppearance app = PdfAppearance.CreateAppearance(stamper.Writer, 100, 100);
The wrong way :
var pdfContentByte = st.GetOverContent(1);
Image sigimage = Image.GetInstance(bytes);
pdfContentByte.AddImage(sigimage);
Calling stamper.GetOverContent (or stamper.GetUnderContent) caused the problem. I was adding signature images as an independent content to the PDF pdfContentByte, which is altering the document content, thus invalidating existing signatures.
The solution was to add the images with the stamp like this:
First, allow incremental updates (
PdfStamper st = PdfStamper.CreateSignature(reader, new FileStream(output, FileMode.Create, FileAccess.ReadWrite), '\0', null, true);
Second, add the image with the signature appearance itself
Image sigimage = Image.GetInstance(bytes);
sap.Image = sigimage; // sap is the signature appearance
Last, set the renderer to Description( so you can get both the text and the image you added to the signature appearance
sap.Render = PdfSignatureAppearance.SignatureRender.Description;
Hope this helps
I am trying to manipulate a PDF, it functions as a template. What I am trying is replacing 'placeholders' in the PDF template with my data. So someone makes a PDF template in Scribus for example, and adds an empty image with the name "company_logo". My application sees an image placeholder with the name "company_logo" and it adds the company logo there.
I can browse AcroFields with iTextSharp library and set text in a text field (for example) but AcroFields doesn't list the image placeholder. I've got the feeling that AcroFields is not what I am looking for.
So how can I get a list (or tree) of all objects from the PDF and read their properties (like position, size, contents, etc).
P.S. I do not necessarily need to use iTextSharp, any other PDF lib will do as well. Preferably free.
A little pseudo code to make myself more clear
var object = Pdf.GetObjectById("company_logo");
object.SetValue(myImage);
object.SetPosition(x, y);
From your pseudo-code example, we understand that you want to replace the stream of an object that contains an image. There are several examples on how to do this.
For instance, in the SpecialID example, we create a PDF where we mark a specific image with a special ID. In the ResizeImage example, we track that image based on that special ID and we replace the stream:
object = reader.getPdfObject(i);
if (object == null || !object.isStream())
continue;
stream = (PRStream)object;
if (value.equals(stream.get(key))) {
PdfImageObject image = new PdfImageObject(stream);
BufferedImage bi = image.getBufferedImage();
if (bi == null) continue;
int width = (int)(bi.getWidth() * FACTOR);
int height = (int)(bi.getHeight() * FACTOR);
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
AffineTransform at = AffineTransform.getScaleInstance(FACTOR, FACTOR);
Graphics2D g = img.createGraphics();
g.drawRenderedImage(bi, at);
ByteArrayOutputStream imgBytes = new ByteArrayOutputStream();
ImageIO.write(img, "JPG", imgBytes);
stream.clear();
stream.setData(imgBytes.toByteArray(), false, PRStream.NO_COMPRESSION);
stream.put(PdfName.TYPE, PdfName.XOBJECT);
stream.put(PdfName.SUBTYPE, PdfName.IMAGE);
stream.put(key, value);
stream.put(PdfName.FILTER, PdfName.DCTDECODE);
stream.put(PdfName.WIDTH, new PdfNumber(width));
stream.put(PdfName.HEIGHT, new PdfNumber(height));
stream.put(PdfName.BITSPERCOMPONENT, new PdfNumber(8));
stream.put(PdfName.COLORSPACE, PdfName.DEVICERGB);
}
You will find another example in the book The Best iText Questions on StackOverflow where I answered the following question: PDF Convert to Black And White PNGs
I wrote the ReplaceImage example to show how to replace the image:
public static void replaceStream(PRStream orig, PdfStream stream) throws IOException {
orig.clear();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
stream.writeContent(baos);
orig.setData(baos.toByteArray(), false);
for (PdfName name : stream.getKeys()) {
orig.put(name, stream.get(name));
}
}
As you can see, it isn't as trivial as saying:
var object = Pdf.GetObjectById("company_logo");
object.SetValue(myImage);
As I explained in my comment, this doesn't make sense:
object.SetPosition(x, y);
The objects we're manipulating are streams that are used as Image XObjects. The advantage of having Image XObjects is that they can be reused. For instance: if you have the same logo on every page, then you want to store the bytes of that image only once and reuse the same logo multiple times. This means that the object with the image bytes doesn't know anything about its position. The position is determined in the content stream. It depends on the CTM.
Did you have a look at the scribus scripting capabilities?
Since you create a tamplate in scribus You could also write a short python script which replaces your placeholders with your final data and exports the final PDF.
Since scribus 1.5 it is also possible to call the python scripts from the commandline.
There is an existing table/image. When I write text using pdfcontentbyte, it is written behind this table/image.
I also want to write the text from the right side of this table/column.
The code I'm currently using to produce the image above:
// open the reader
PdfReader reader = new PdfReader(oldFile);
iTextSharp.text.Rectangle size = reader.GetPageSizeWithRotation(1);
Document document = new Document(size);
// open the writer
FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.Write);
PdfWriter writer = PdfWriter.GetInstance(document, fs);
document.Open();
PdfContentByte cb = writer.DirectContent;
BaseFont bf = BaseFont.CreateFont(BaseFont.COURIER_BOLD,BaseFont.CP1252, BaseFont.EMBEDDED);
string text = "WV0501";
cb.BeginText();
// put the alignment and coordinates here
cb.ShowTextAligned(2, text, 155, 655, 0);
cb.EndText();
// create the new page and add it to the pdf
PdfImportedPage page = writer.GetImportedPage(reader, 1);
cb.AddTemplate(page, 0, 0);
document.Close();
fs.Close();
writer.Close();
You don't show us your code, so we have to guess what you are doing wrong.
The content isn't showing:
You probably have this line in your code:
PdfContentByte canvas = pdfStamper.GetUnderContent(page);
If so, you should replace it with this line:
PdfContentByte canvas = pdfStamper.GetOverContent(page);
Update after you showed us your code:
You want to add content to an existing document, yet you are using a combination of Document and PdfWriter. Why are you doing this? Please read chapter 6 of my book where you'll learn about PdfStamper.
Right now, you are adding the text first and then you are covering it with a page from an existing document. This means that the existing page will cover the text.
You can switch this around like this:
// create the new page and add it to the pdf
PdfImportedPage page = writer.GetImportedPage(reader, 1);
cb.AddTemplate(page, 0, 0);
BaseFont bf = BaseFont.CreateFont(BaseFont.COURIER_BOLD,BaseFont.CP1252, BaseFont.EMBEDDED);
string text = "WV0501";
cb.BeginText();
// put the alignment and coordinates here
cb.ShowTextAligned(2, text, 155, 655, 0);
cb.EndText();
Now the text will cover the page, but that doesn't mean your code is better. You should really use PdfStamper instead of PdfWriter:
PdfReader reader = new PdfReader(oldFile);
FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.Write);
PdfStamper stamper = new PdfStamper(reader, fs);
PdfContentByte canvas = stamper.GetOverContent(1);
BaseFont bf = BaseFont.CreateFont(BaseFont.COURIER_BOLD,BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
ColumnText.ShowTextAligned(
canvas,
Element.ALIGN_RIGHT,
new Phrase("WV0501", new Font(bf, 9)),
155, 655, 0
);
stamper.Close();
Don't you agree that this is more elegant?
IMPORTANT:
In your code you use:
BaseFont bf = BaseFont.CreateFont(BaseFont.COURIER_BOLD,BaseFont.CP1252, BaseFont.EMBEDDED);
However, that doesn't make much sense: COURIER_BOLD is one of the Standard Type 1 fonts and because of that the embedded parameter is ignored. I changed this into NOT_EMBEDDED because if you use EMBEDDED developers who read your code and who don't know anything about PDF and iText may get confused. They might ask: Why is the font not getting embedded when the parameter says it should be embedded?
BaseFont bf = BaseFont.CreateFont(BaseFont.COURIER_BOLD,BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
Writing from the right side:
You are defining the alignment using a number: 2. I suggest that you don't use a number in your code, but a constant: ALIGN_RIGHT. This way, we see that you want to right align the text.
The text is right aligned with respect to the coordinates you've defined:
x = 155
y = 655
If you are not happy with the position of your text, you should change these hard-coded coordinates. For instance: increase x and decrease y.
You probably want the text to be relative to the border of a table cell or an image. If that is the case, you should not hard-code the coordinates.
Retrieving the coordinates of an image is discussed in another question on SO. Retrieving the coordinates of a table might very well be impossible. It all depends on the nature of the original PDF.
After rotating to portrait view, how might I ensure all content is visible (within page bounds)?
PDFsharp truncates my page when rotated by +90 degrees but not when rotated by -90 degrees.
PdfDocument outputDocument = new PdfDocument();
XPdfForm old = XPdfForm.FromFile(in_filename);
PdfPage newP = outputDocument.AddPage();
if (old.Page.Orientation == PageOrientation.Landscape)
{
old.Page.Rotate= (old.Page.Rotate - 90) % 360;
old.Page.Orientation = PageOrientation.Portrait;
}
newP.Height = old.Page.Height;
newP.Width = old.Page.Width;
// Get a graphics object for page1
XGraphics gfx = XGraphics.FromPdfPage(newP);
// Draw the page identified by the page number like an image
gfx.DrawImage(old, new XRect(0, 0, old.PointWidth, old.PointHeight));
The above works for a few pdf test cases, but I am skeptical it is coincidental/luck
I am using PDFsharp 1.50 beta.
There is a known problem with PDFsharp 1.50 beta with respect to importing rotated PDFs. That problem is still under investigation.
PDF files come in many different variations, therefore it is very difficult to ensure that code works in all cases.
I ended up doing the following:
(Note, this only had to work for a landscape to portrait)
var output = new PdfDocument();
var outputPage = output.AddPage();
using (var stream = new MemoryStream(Convert.FromBase64String(pdfBase64String)))
{
using (var input = XPdfForm.FromStream(stream))
{
outputPage.Height = input.PointWidth;
outputPage.Width = input.PointHeight;
using (var graphics = XGraphics.FromPdfPage(outputPage))
{
graphics.RotateAtTransform(90, new XPoint(input.PointHeight / 2, input.PointHeight / 2));
graphics.DrawImage(input, new XRect(0, 0, input.PointWidth, input.PointHeight));
}
}
}
I am creating a PDF using iTextSharp 5.4.5 in .NET 4.0 like this:
class Program
{
static void Main(string[] args)
{
string html="<span style='transform: rotate(-90deg)'>Some Text</span>";
byte[] file=PDFGenerator.GeneratePDF(html);
string filename=#"C:\Users\myaccount\Desktop\myfile.pdf";
var v=System.IO.File.Create(filename);
v.Write(file, 0, file.Length);
}
}
public class PDFGenerator
{
public static byte[] GeneratePDF(string html)
{
MemoryStream msOutput = new MemoryStream();
TextReader reader = new StringReader(html);
Document document = new Document(PageSize.A4, 30, 30, 30, 30);
PdfWriter writer = PdfWriter.GetInstance(document, msOutput);
HTMLWorker worker = new HTMLWorker(document);
document.Open();
worker.StartDocument();
worker.Parse(reader);
worker.EndDocument();
worker.Close();
document.Close();
return msOutput.ToArray();
}
}
However, the text in the PDF is not coming out rotated at all. I need the text to be rotated vertically. Is there any CSS or HTML I can enter so that it will rotate?
HTMLWorker has been deprecated for a very long time in favor of XMLWorker and zero work is being done in it. The former doesn't really support CSS while the latter does, too. However, the transform CSS property isn't supported anyway so that doesn't help you. (See this for a list of supported properties.)
Do you have an absolute need for HTML parsing? If not, you can just use the ColumnText class to do what you want. PDFs origins are in the bottom left corner instead of the top left so might need to adjust your math. Also, rotations are in the opposite direction so your -90 becomes just 90.
ColumnText.ShowTextAligned(writer.DirectContent, Element.ALIGN_CENTER, new Phrase("Hello World"), 50, 50, 90);