I have a PDF document with 3 Fields txt_FirstName, txt_MiddleName and txt_LastName that I write into using iTextSharp.
I have a loop that creates the output file, writes to it, and closes the file.
The first time in the loop the file writes the first name and the middle name.
The second time in the loop the file should have the first name, middle name, and write the last name.
Issue: The problem is, when it goes to the loop the 2nd time around and writes the lastname the first name, and middle names disappear.
Goal: The main thing I want to do is write to the same PDF documents multiple times
Download PDF template: https://www.scribd.com/document/412586469/Testing-Doc
public static string templatePath = "C:\\temp\\template.pdf";
public static string OutputPath = "C:\\Output\\";
private static void Fill_PDF()
{
string outputFile = "output.pdf";
int counter = 1;
for (int i = 0; i < 2; i++)
{
PdfStamper pdfStamper;
PdfReader reader;
reader = new PdfReader(File.ReadAllBytes(templatePath));
PdfReader.unethicalreading = true;
if (File.Exists(OutputPath + outputFile))
{
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Append, FileAccess.Write));
}
else
{
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Create));
}
AcroFields pdfFormFields = pdfStamper.AcroFields;
if (counter == 1)
{
pdfFormFields.SetField("txt_FirstName", "Scooby");
pdfFormFields.SetField("txt_MiddleName", "Dooby");
counter++;
}
else if (counter == 2)
{
pdfFormFields.SetField("txt_LastName", "Doo");
}
pdfStamper.Close();
}
}
This seems like a straightforward bug. The first time through the loop, you load up the blank template and write the first and middle name. The second time through the loop, you load up the blank template again and write only the last name to it, then save to the same filename, overwriting it. If, during the second time through the loop, you want to load the file that already contains the first and middle name, you have to load up the output file you wrote the first time around, not the blank template again. Or if you want to load the blank template again, inside your if (counter == 2) clause, you're going to have to write all 3 names, not just the last name.
I reproduced your bug, and got it working. Here's the code to the first solution I described (minor modification of your code):
public static string templatePath = "C:\\temp\\template.pdf";
public static string OutputPath = "C:\\temp\\output\\";
private static void Fill_PDF()
{
string outputFile = "output.pdf";
int counter = 1;
for (int i = 0; i < 2; i++)
{
PdfStamper pdfStamper;
PdfReader reader = null;
/********** here's the changed part */
if (counter == 1)
{
reader = new PdfReader(File.ReadAllBytes(templatePath));
} else if (counter == 2)
{
reader = new PdfReader(File.ReadAllBytes(OutputPath + outputFile));
}
/************ end changed part */
PdfReader.unethicalreading = true;
if (File.Exists(OutputPath + outputFile))
{
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Append, FileAccess.Write));
}
else
{
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Create));
}
AcroFields pdfFormFields = pdfStamper.AcroFields;
if (counter == 1)
{
pdfFormFields.SetField("txt_FirstName", "Scooby");
pdfFormFields.SetField("txt_MiddleName", "Dooby");
counter++;
}
else if (counter == 2)
{
pdfFormFields.SetField("txt_LastName", "Doo");
}
pdfStamper.Close();
}
}
There are two major issues with the code. #Nick in his answer already pointed out the first: If in your second pass you want to edit a version of your document containing the changes from the first pass, you have to take the output document of the first pass as input of the second pass, not again the original template. He also presented code that fixed this issue.
The second issue is located here:
if (File.Exists(OutputPath + outputFile))
{
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Append, FileAccess.Write));
}
else
{
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Create));
}
If the output file already exists, you append the output of your PdfStamper to it. This is wrong! The output of the PdfStamper already contains the contents of the original PDF (from the PdfReader) as far as they have not being changed. Thus, your code effectively produces a concatenation of the complete output PDF of the first pass and the complete output PDF of the second pass.
PDF is a binary format for which concatenating files like that does not result in a valid PDF file. Thus, a PDF viewer loading your final result tries to repair this double PDF assuming it is a single one. The result may or may not look like you want.
To fix the second issue, simply replace the if{...}else{...} above by the contents of the else branch only:
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Create));
(FileMode.Create is defined as
Specifies that the operating system should create a new file. If the file already exists, it will be overwritten. This requires Write permission. FileMode.Create is equivalent to requesting that if the file does not exist, use CreateNew; otherwise, use Truncate. If the file already exists but is a hidden file, an UnauthorizedAccessException exception is thrown.
Thus, it will also do the required if there already is a file.)
You can recognize the problems of the code with the Append in it by running it a few times and watch the output file grow and grow beyond need. Furthermore, if you open that file in Adobe Reader and close again, Adobe Reader offers to save the changes; the changes are the repair work.
You may have heard about incremental updates of PDFs where changes are appended to the original PDF. But this is different from a mere concatenation, the revisions in the result are linked specially and the offsets are always calculated from the start of the first revision, not from the start of the current revision. Furthermore, incremental updates should only contain changed objects.
iText contains a PdfStamper constructor with 4 parameters, including a final boolean parameter append. Using that constructor and setting append to true makes iText creates incremental updates. But even here you don't use FileMode.Append...
The problem is with using the template file again for the second iteration.
First iteration: works fine as expected!
Second iteration: you are reading the same file and writing only the last name. Finally, the output file created in the first iteration is being replaced.
Fix: After knowing if the output file exists in the location, choose the file source to read like below. This should fix the problem. Checked it personally and it worked!
if (File.Exists(OutputPath + outputFile))
{
reader = new PdfReader(File.ReadAllBytes(OutputPath + outputFile));
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Append, FileAccess.Write));
}
else
{
reader = new PdfReader(File.ReadAllBytes(templatePath));
pdfStamper = new PdfStamper(reader, new FileStream(OutputPath + outputFile,
FileMode.Create));
}
Related
So basically i was struggling with hangman and i couldn't figure out how to solve it the first problem. That's why i thought i might put in another function; a method to add a word to my textfile of words with StreamWriter using FileStream. (I have a StreamReader too, so i can see what random word it would choose.)
Things tried (with all: writer.WriteLine(text)):
I tried using StreamWriter writer = new StreamReader(fs.ToString(), append: true) with FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)
I tried FileMode.Append (because i want to append), but that only works in write-only mode and i need to read text too.
I tried to use the filestream without the ToSting(), but that resulted in "Cannot convert from 'System.IO.FileStream' to 'string'."
I tried to do the same thing in a different program but only with StreamReader and FileStream, but it still didn't change anything in the textfile.
What I actually wish it would do:
I expect that the program will be able to append a string to the textfile while still being able to read the chosen word. (When you start the program a random word out of the textfile is chosen.)
Sorry if this looks messy, it's my first time asking a question on stackoverflow.
So why this is different from the duplicate is because i want to append the file, not write (overwrite) it. I tried the exact same FileStream as the "duplicate", but it only got rid of the error. Now I'm still stuck with a malfunctioning StreamWriter.
Part of the code:
private static FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
private static StreamReader reader = new StreamReader(fs);
private static string[] words = reader.ReadToEnd().Split(',');
public static Random randomizer = new Random();
private static int wordindex = randomizer.Next(0, words.Length);
public string GetWord()
{
return words[wordindex]; //returns random value out of array = random woord
}
`
And using for StreamWriter:
public string AddWordToFile(string word)
{
using (StreamWriter writer = new StreamWriter(fs.ToString(), append: true))
{
//bool ExistsInFile = false;
for (int i = 0; i < words.Length; i++)
{
if (words.Contains(word))
{
//ExistsInFile = true;
return "The word '" + word + "' already exists in the textfile.";
}
}
if (!words.Contains(word))
{
//ExistsInFile = false;
writer.WriteLine("," + word);
writer.Close();
return word + " added.";
}
else
{
return "Whoops, something went wrong :'I";
}
}
}
Another way to do it would be this:
static void ReadAndWrite()
{
string file = File.ReadAllText(#"C:\whatever.txt"); //read C:\whatever.txt
File.AppendAllText(#"C:\whatever.txt", "Text to append"); //append "Text to append" to C:\whatever.txt
}
I've tested this and it works just fine on my PC.
Maybe it helps you.
Edit:
This is much easier and reads every line in a file into a string array:
static void ReadAndWrite()
{
string[] words = File.ReadAllLines(#"C:\whatever.txt");
File.AppendAllText(#"C:\whatever.txt", "Word to append\r\n"); //append "Word to append" in a new Line to C:\whatever.txt
}
To get an array of words, write every word into a new line by also appending \r\n after every word like in the example. This way, you get an array of every word in your file.
I think the problem is that you had two different FileStreams accessing the same file at once which doesn't work.
One way to do this would be to close the FileStreams and StreamWriters right after using them and everytime you Need to Access the file reopen it.
To close the StreamWriter/FileStream, use:
StreamWriter.Close();
or for FileStreams:
FileStream.Close();
You will obviously have to replace FileStream trough the Name of your FileStream object.
I hope I could help you.
I am creating a PDF file in a ASP.NET C# Windows Console Application, using iTextSharp.
I have this one piece of code. If Site is 'LMH', I get a good PDF i can open with Adobe Reader. If not, I get error: There was an error processing a page. There was a problem reading this document (114).
Here is my code:
string ApplicationPath = System.IO.Directory.GetCurrentDirectory();
PdfPTable table = new PdfPTable(4) { TotalWidth = 800.0F, LockedWidth = true };
float[] widths = new[] { 80.0F, 80.0F, 500.0F, 140.0F };
table.SetWidths(widths);
table.HorizontalAlignment = 0;
table.DefaultCell.Border = 0;
if (ActiveProfile.Site == "LMH")
{
Image hmsImage = Image.GetInstance(ApplicationPath + "\\" + "HMS Logo.png");
hmsImage.ScaleToFit(80.0F, 40.0F);
PdfPCell hmslogo = new PdfPCell(hmsImage);
hmslogo.Border = 0;
hmslogo.FixedHeight = 60;
table.AddCell(hmslogo);
}
else
{
Image blankImage = Image.GetInstance(ApplicationPath + "\\" + "emptyLogo.png");
blankImage.ScaleToFit(80.0F, 40.0F);
PdfPCell emptyCell = new PdfPCell(blankImage);
emptyCell.Border = 0;
emptyCell.FixedHeight = 60;
table.AddCell(emptyCell);
}
And the main trunk:
System.IO.FileStream file = new System.IO.FileStream(("C:/") + keyPropertyId + ".pdf", System.IO.FileMode.OpenOrCreate);
Document document = new Document(PageSize.A4.Rotate () , 20, 20, 6, 4);
PdfWriter writer = PdfWriter.GetInstance(document, file );
document.AddTitle(_title);
document.Open();
addHeader(document);
addGeneralInfo(document, keyPropertyId);
addAppliances(document, _LGSRobj);
addFaults(document, _LGSRobj);
addAlarms(document, _LGSRobj);
addFinalCheck(document, _LGSRobj);
addSignatures(document, _LGSRobj);
addFooter(document, writer);
document.Close();
writer .Close ();
file.Close();
All that has changed is a logo. Both logo files have the same dimensions. What can possibly be wrong? BTW. File opens fine using Foxit, but this is not an acceptable solution.
This is the PDF I can't open with Adobe Reader: https://dl.dropboxusercontent.com/u/20086858/1003443.pdf
This is the emptyLogo.png file: https://dl.dropboxusercontent.com/u/20086858/emptylogo.png
This is the logo that works: https://dl.dropboxusercontent.com/u/20086858/HMS%20Logo.png
This is a 'good' version of the pdf with the logo that works: https://dl.dropboxusercontent.com/u/20086858/1003443-good.pdf
It is very suspicious that both the 'good' and the not-so-good version have the identical size in spite of very different image sizes. Comparing them one sees that both files differ completely in their first 192993 bytes but from there on only very little. Furthermore the broken PDF contains EOF markers in this region denoting file ends at indices 140338 and 192993 but the following bytes do not at all look like a clean incremental update.
Cutting the file at the first denoted file end, 140338, one gets the file the OP wanted to have.
Thus:
The code overwrites existing files with new data; if the former file was longer, the remainder of that longer file remains as trash at the end and, therefore, renders the new file broken.
The OP opens the stream like this:
new System.IO.FileStream(("C:/") + keyPropertyId + ".pdf", System.IO.FileMode.OpenOrCreate);
FileMode.OpenOrCreate causes the observed behavior.
The FileMode values are documented here on MSDN, especially:
OpenOrCreate Specifies that the operating system should open a file if it exists; otherwise, a new file should be created.
Create Specifies that the operating system should create a new file. If the file already exists, it will be overwritten. ... FileMode.Create is equivalent to requesting that if the file does not exist, use CreateNew; otherwise, use Truncate.
Thus, use
new System.IO.FileStream(("C:/") + keyPropertyId + ".pdf", System.IO.FileMode.Create);
instead.
I have a list type of array to store data named PointDataOutput. This list store data of spacial point with (i,j).
How can I save these data?
storing method is in another class:
// Storing method
public void ExportPointData(...)
{
theSpace.TheCells[a,b,0].PointDataOutput.Add(theSpace.TheCells[a,b,0].Temperature);
}
int counter = 0;
for (int currentStep = 1; currentStep <= slnParameter.MaxSteps; currentStep++)
{
// data calculation
theSpace.TheCells[a,b,0].Temperature=....;
// calling method of storing
ExportPointData(...)
}
// saving data in file
if (currentStep == MaxSteps)
{
File.Create("data.txt");
FileStream fs1 = new FileStream("data.txt", FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter writer = new StreamWriter(fs1);
foreach (double x in theSpace.TheCells[a, b, 0].PointDataOutput)
{
writer.WriteLine(x);
}
writer.WriteLine(theSpace.TheCells[a, b, 0].PointDataOutput.ToString);
writer.Close();
}
I don't know can I save data each time it was produce or I should wait to complete iteration and then data in the store List should be saved. I tried several times and each time I got error that there is a text file in the same name of mine!
It shows that each time program makes new file and don't save all data in one file. What should I do?
There are a couple of problems with the code:
The line File.Create("data.txt"); is redundant.
You haven't closed FileStream fs1. It is good practice to close a file
once you have finished with it. A good habit is to use the using keyword
whereever possible.
Taking into account the above, here is how I would implement the 'saving data
in file' section:
// saving data in file
if (currentStep == MaxSteps)
{
using (FileStream fs1 = new FileStream("data.txt", FileMode.OpenOrCreate,
FileAccess.Write))
using (StreamWriter writer = new StreamWriter(fs1))
{
foreach (double x in theSpace.TheCells[a, b, 0].PointDataOutput)
{
writer.WriteLine(x);
}
writer.WriteLine(theSpace.TheCells[a, b, 0].PointDataOutput.ToString);
}
}
//File.Create("data.txt"); you don't need this line
FileStream fs1 = new FileStream("data.txt", FileMode.OpenOrCreate, FileAccess.Write);
problem is File.Create overwritten the existing file every time you call this method
Don't use
File.Create("data.txt");
new FileStream("data.txt", FileMode.OpenOrCreate, FileAccess.Write);
Already creates a new file.
I would get rid of the FileStream entirely, you're creating a file then using the StreamWriter which can already do that and allow you to append to it.
string myFileName = "data.txt";
bool doIWantToAppendToTheFile = true;
StreamWriter writer = new StreamWriter(myFileName, doIWantToAppendToTheFile);
This will then add additional text to your file each time, of course if you didn't want to append just set doIWantToAppendToTheFile to false (oh and change the names, I just named them that way to provide some clarity). If you wanted new files each time the write method was called to have individual blobs of data you could append a datetime stamp to the file name eg:
string myFileName = "data" + DateTime.Now.ToString(yyyyMMdd-hhmmss) + ".txt";
So the filename would be data20140220100430.txt
Using the iTextSharp Library I'm able to insert metadata in a PDF file using the various Schemas.
The keywords in the keywords metadata are for my purposes delimited by a comma and enclosed in double quotes. Once the script I've written runs, the keywords are enclosed in triple quotes.
Any ideas on how to avoid this or any advice on working with XMP?
Example of required metadata : "keyword1","keyword2","keyword3"
Example of current metadata : """keyword1"",""keyword2"",""keyword3"""
Coding:
string _keywords = meta_line.Split(',')[1] + ","
+ meta_line.Split(',')[2] + ","
+ meta_line.Split(',')[3] + ","
+ meta_line.Split(',')[4] + ","
+ meta_line.Split(',')[5] + ","
+ meta_line.Split(',')[6] + ","
+ meta_line.Split(',')[7];
_keywords = _keywords.Replace('~', ',');
Console.WriteLine(metaFile);
foreach (string inputFile in Directory.GetFiles(source, "*.pdf", SearchOption.TopDirectoryOnly))
{
if (Path.GetFileName(metaFile) == Path.GetFileName(inputFile))
{
string outputFile = source + #"\output\" + Path.GetFileName(inputFile);
PdfReader reader = new PdfReader(inputFile);
using (FileStream fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
PdfStamper stamper = new PdfStamper(reader, fs);
Dictionary<String, String> info = reader.Info;
stamper.MoreInfo = info;
PdfWriter writer = stamper.Writer;
byte[] buffer = new byte[65536];
System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer, true);
try
{
iTextSharp.text.xml.xmp.XmpSchema dc = new iTextSharp.text.xml.xmp.DublinCoreSchema();
dc.SetProperty(iTextSharp.text.xml.xmp.DublinCoreSchema.TITLE, new iTextSharp.text.xml.xmp.LangAlt(_title));
iTextSharp.text.xml.xmp.XmpArray subject = new iTextSharp.text.xml.xmp.XmpArray(iTextSharp.text.xml.xmp.XmpArray.ORDERED);
subject.Add(_subject);
dc.SetProperty(iTextSharp.text.xml.xmp.DublinCoreSchema.SUBJECT, subject);
iTextSharp.text.xml.xmp.XmpArray author = new iTextSharp.text.xml.xmp.XmpArray(iTextSharp.text.xml.xmp.XmpArray.ORDERED);
author.Add(_author);
dc.SetProperty(iTextSharp.text.xml.xmp.DublinCoreSchema.CREATOR, author);
PdfSchemaAdvanced pdf = new PdfSchemaAdvanced();
pdf.AddKeywords(_keywords);
iTextSharp.text.xml.xmp.XmpWriter xmp = new iTextSharp.text.xml.xmp.XmpWriter(ms);
xmp.AddRdfDescription(dc);
xmp.AddRdfDescription(pdf);
xmp.Close();
int bufsize = buffer.Length;
int bufcount = 0;
foreach (byte b in buffer)
{
if (b == 0) break;
bufcount++;
}
System.IO.MemoryStream ms2 = new System.IO.MemoryStream(buffer, 0, bufcount);
buffer = ms2.ToArray();
foreach (char buff in buffer)
{
Console.Write(buff);
}
writer.XmpMetadata = buffer;
}
catch (Exception ex)
{
throw ex;
}
finally
{
ms.Close();
ms.Dispose();
}
stamper.Close();
// writer.Close();
}
reader.Close();
}
}
The below method didn't add any metadata - not sure why (point 3 in the comments):
iTextSharp.text.xml.xmp.XmpArray keywords = new iTextSharp.text.xml.xmp.XmpArray(iTextSharp.text.xml.xmp.XmpArray.ORDERED);
keywords.Add("keyword1");
keywords.Add("keyword2");
keywords.Add("keyword3");
pdf.SetProperty(iTextSharp.text.xml.xmp.PdfSchema.KEYWORDS, keywords);
I currently don't have the newest iTextSharp version. I have a itextsharp 5.1.1.0. It does not contain PdfSchemaAdvanced class, but it has PdfSchema and its base class XmpSchema. I bet the PdfSchemaAdvanced in your lib also derives from XmpSchema.
The PdfSchema.AddKeyword only does one thing:
base["pdf:Keywords"] = keywords;
and XmpSchema.[].set in turn does:
base[key] = XmpSchema.Escape(value);
so it's very clear that the value is being, well, 'Escaped', to ensure that special characters are not interfering with the storage format.
Now, the Escape function, what what I see, performs simple character-by-character scanning and performs substitutions:
" -> "
& -> &
' -> '
< -> <
> -> >
and that's all. Seems like a typical html-entites processing. At least in my version of the library. So, it would not duplicate the quotes, just change their encoding.
Then, AddRdfDescription seems to simply iterate over the stored keys and just wraps them in tags with no furhter processing. So, it'd emit something like that:
Escaped"Contents&OfThis"Key
as:
<pdf:Keywords>Escaped"Contents&OfThis"Key</pdf:Keywords>
Aside from the AddKeywords method, you should also see AddProperty method. It acts similarly to add-keywords except for the fact that it receives key and does not Escape() its input value.
So, if you are perfectly sure that your _keywords are formatted properly, you might try:
AddProperty("pdf:Keywords", _keywords)
but I discourage you from doing that. At least in my version of itextsharp, the library seems to properly process the 'keywords' and format it safely as RDF.
Heh, you may also try using the PdfSchema class that I just checked instead of the Advanced one. I bet it still is present in the library.
But, in general, I think the problem lies elsewhere.
Double or triple-check the contents of _keywords variable and then also check the binary contents of the generated PDF. Look into it with some hexeditor or simple plain-text editor like Notepad and look for the <pdf:Keywords> tag. Check what it actually contains. It might be all OK and it might be your pdf-metadata-reader that adds those quotes.
I'm generating a new PDF from an existing template that has been created in LibreOffice. It contains one Text Box.
After the code compiles and successfully saves the PDF to a new file, if I open the newly created document in Acrobat Reader XI, it renders correctly, but, even if I don't modify the final document, upon closing the document, it asks "Do you want to save changes to "filename.pdf" before closing?"
I've read other posts on StackOverflow and their official site (iTextSharp), and found a solution, that maybe I'm implementing in a wrongful manner.
public string spdftemplate = #"C:\test\input.pdf";
public string newFile = #"C:\test\output.pdf";
private void FillFormsProperly()
{
PdfReader reader = new PdfReader(spdftemplate);
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
PdfStamper stamper = new PdfStamper(reader, ms);
#region ForTesting
//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();
#endregion ForTesting
AcroFields pdfFormFields = stamper.AcroFields;
foreach (DictionaryEntry de in reader.AcroFields.Fields)
{
pdfFormFields.SetField(de.Key.ToString(), "test"); //"Text Box 1"
}
//string sTmp = "W-4 Completed for " + pdfFormFields.GetField("Text Box 1");
//MessageBox.Show(sTmp, "Finished");
//Flush the PdfStamper's buffer
stamper.FormFlattening = true;
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
using (FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
fs.Write(bytes, 0, bytes.Length);
}
}
The best answer I found was this : creating a pdf from a template in itextsharp and outputting as content disposition.
The above code is my (copy-paste more or less) implementation.
It's obvious that the file is corrupted (but still readable), how can I fix this ?
Your input.pdf contains a form field and the flag /NeedAppearances true. Your output.pdf does not contain a field anymore (obviously... you flattened the form after all) but it still contains that flag /NeedAppearances true.
This flag tells the PDF viewer (Acrobat Reader) to generate appearance streams for some form fields. Thus, the Reader inspects all fields to create the appearances where necessary. Afterwards it removes the flag. Because of this the document then is changed; even if there are no fields, at least the flag removal is a change.
This reminds of an iText issue which has been fixed in February last year in iText:
In some cases, Adobe Reader X asks if you want to "save changes" after closing a flattened PDF form. This was due to the presence of some unnecessary entries in the /AcroForm dictionary (for instance added when the form was created with OOo).
(iText revision 5089, February 29th, 2012, blowagie)
This change has been ported to iTextSharp in iTextSharp revision 323, March 3rd, 2012, psoares33.
Thus, you might want to update the iTextSharp version you use.