I have a PDF template created in LibreOffice and I\'m filling it in using AcroFields. In some rare cases I\'d like to hide a particular field, thus I remove it using RemoveF
Removing individual drawing objects can get a little tricky but its not impossible. The hardest part is deciding which objects that you want to remove. Below is some sample code targetting iTextSharp 4.1.6 that first creates a PDF with two rectangles and then creates a second PDF based on the first with one of the rectangles removed. You'll need to apply your logic to figure out which rectangle you want to remove. Its possible that you don't actually have rectangles but lines that happen to form a rectangle, in that case you'll need to modify the code a bit, too.
This first bit just creates a basic PDF on the desktop with two rectangles:
//Create a file on the desktop with two rectangles
var file1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "File1.pdf");
using (var fs = new FileStream(file1, FileMode.Create, FileAccess.Write, FileShare.None)) {
var doc = new Document();
var writer = PdfWriter.GetInstance(doc, fs);
doc.Open();
var cb = writer.DirectContent;
//Draw two rectangles
cb.SaveState();
cb.SetColorStroke(iTextSharp.text.Color.RED);
cb.Rectangle(40, 60, 200, 100);
cb.Stroke();
cb.RestoreState();
cb.SaveState();
cb.SetColorStroke(iTextSharp.text.Color.BLUE);
cb.Rectangle(500, 80, 90, 50);
cb.Stroke();
cb.RestoreState();
doc.Close();
}
This next part is the more complicated part. I encourage you to do a Console.WriteLine(tokenizer.StringValue);
inside of the while
loop to see all of the PDF commands. You'll notice that they use RPN syntax which can take a little getting used. See the comments in the code for more questions.
//Bind a reader to our first file
var reader = new PdfReader(file1);
//Get the first page (this would normally be done in a loop)
var page = reader.GetPageN(1);
//Get the "contents" of that page
var objectReference = (PdfIndirectReference)page.Get(PdfName.CONTENTS);
//Get the actual stream of the "contents"
var stream = (PRStream)PdfReader.GetPdfObject(objectReference);
//Get the raw bytes of the stream
var streamBytes = PdfReader.GetStreamBytes(stream);
//Convert the bytes to actual PDF tokens/commands
var tokenizer = new PRTokeniser(new RandomAccessFileOrArray(streamBytes));
//We're going to re-append each token to this below buffer and remove the ones that we don't want
List<string> newBuf = new List<string>();
//Loop through each PDf token
while (tokenizer.NextToken()) {
//Add them to our master buffer
newBuf.Add(tokenizer.StringValue);
//The "Other" token is used for most commands, so if we're on "Other" and the current command is "re" which is rectangle
if (
tokenizer.TokenType == PRTokeniser.TK_OTHER && //The "Other" token is used for most commands
newBuf[newBuf.Count - 1] == "re" && //re is the rectangle command
newBuf[newBuf.Count - 5] == "40" //PDFs use RPN syntax so the red rectangle command was "40 60 200 100 re"
) {
newBuf.RemoveRange(newBuf.Count - 5, 5); //If the above conditions were met remove the last 5 commands
}
}
//Convert our array to a string with newlines between each token, convert that to an ASCII byte array and push that back into the stream (erasing the current contents)
stream.SetData(System.Text.Encoding.ASCII.GetBytes(String.Join("\n", newBuf.ToArray())));
//Create a new file with the rectangle removed
var file2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "File2.pdf");
using (var fs = new FileStream(file2, FileMode.Create, FileAccess.Write, FileShare.None)) {
//Bind a stamper to our read above which has the altered stream
var stamper = new PdfStamper(reader, fs);
//Loop through each page
int total = reader.NumberOfPages;
for (int i = 1; i <= total; i++) {
//Push the content over page by page
reader.SetPageContent(i, reader.GetPageContent(i));
}
stamper.Close();
}
reader.Close();