I'm using iText 5 to fill existing pdf forms with content and then merge them into a single pdf. I also want to turn on/off layers, but after merging all layers are visible.
This code shows the problem without using existing pdf forms. I would like to hide layer two but it seems not working.
static void Main(string[] args)
{
byte[] pdfPage = CreatePage();
byte[] result = Merge(new byte[][] { pdfPage, pdfPage });
File.WriteAllBytes(#"c:\test1.pdf", result);
}
private static byte[] CreatePage()
{
Document doc = new Document();
MemoryStream ms = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(doc, ms);
doc.Open();
PdfLayer layer1 = new PdfLayer("Layer 1", writer);
PdfLayer layer2 = new PdfLayer("Layer 2", writer);
PdfContentByte cb = writer.DirectContent;
cb.BeginLayer(layer1);
ColumnText.ShowTextAligned(cb, Element.ALIGN_LEFT, new Phrase("Layertext 1"), 100, 700, 0);
cb.EndLayer();
cb.BeginLayer(layer2);
ColumnText.ShowTextAligned(cb, Element.ALIGN_LEFT, new Phrase("Layertext 2"), 100, 600, 0);
cb.EndLayer();
layer1.On = true;
// turn off layer 2
layer2.On = false;
doc.Close();
return ms.ToArray();
}
private static byte[] Merge(byte[][] pages)
{
Document doc = new Document();
MemoryStream ms = null;
using (ms = new MemoryStream())
{
PdfCopy copy = new PdfCopy(doc, ms);
doc.Open();
foreach (byte[] page in pages)
{
PdfReader reader = new PdfReader(new MemoryStream(page));
PdfImportedPage imp = copy.GetImportedPage(reader, 1);
copy.AddPage(imp);
reader.Close();
}
doc.Close();
}
return ms.ToArray();
}
Related
I have an XFA form that I can successfully fill in by extracting the XML modifying and writing back. Works great if you have the full Adobe Acrobat, but fails with Adobe Reader. I have seen various questions on the same thing with answers but they were some time ago so updating an XFA that is readable by Adobe Reader may no longer be doable?
I use this code below and I've utilised the StampingProperties of append as in the iText example but still failing. I'm using iText 7.1.15.
//open file and write to temp one
PdfDocument pdf = new(new PdfReader(FileToProcess), new PdfWriter(NewPDF), new StampingProperties().UseAppendMode());
PdfAcroForm form = PdfAcroForm.GetAcroForm(pdf, true);
XfaForm xfa = form.GetXfaForm();
XElement node = xfa.GetDatasetsNode();
IEnumerable<XNode> list = node.Nodes();
foreach (XNode item in list)
{
if (item is XElement element && "data".Equals(element.Name.LocalName))
{
node = element;
break;
}
}
XmlWriterSettings settings = new() { Indent = true };
using XmlWriter writer = XmlWriter.Create(XMLOutput, settings);
{
node.WriteTo(writer);
writer.Flush();
writer.Close();
}
//We now how to strip an extra xfa line if updating
if(update)
{
string TempXML= CSTrackerHelper.MakePath($"{AppContext.BaseDirectory}Temp", $"{Guid.NewGuid()}.XML");
StreamReader fsin = new(XMLOutput);
StreamWriter fsout = new(TempXML);
string linedata = string.Empty;
int cnt = 0;
while (!fsin.EndOfStream)
{
if (cnt != 3 && linedata != string.Empty)
{
fsout.WriteLine(linedata);
}
linedata = fsin.ReadLine();
cnt++;
}
fsout.Close();
fsin.Close();
XMLOutput = TempXML;
}
xlogger.Info("Populating pdf fields");
//Now loop through our field data and update the XML
XmlDocument xmldoc = new();
xmldoc.Load(XMLOutput);
XmlNamespaceManager xmlnsManager = new(xmldoc.NameTable);
xmlnsManager.AddNamespace("xfa", #"http://www.xfa.org/schema/xfa-data/1.0/");
string[] FieldValues;
string[] MultiNodes;
foreach (KeyValuePair<string, DocumentFieldData> v in DocumentData.FieldData)
{
if (!string.IsNullOrEmpty(v.Value.Field))
{
FieldValues = v.Value.Field.Contains(";") ? v.Value.Field.Split(';') : (new string[] { v.Value.Field });
foreach (string FValue in FieldValues)
{
XmlNodeList aNodes;
if (FValue.Contains("{"))
{
aNodes = xmldoc.SelectNodes(FValue.Substring(0, FValue.LastIndexOf("{")), xmlnsManager);
if (aNodes.Count > 1)
{
//We have a multinode
MultiNodes = FValue.Split('{');
int NodeIndex = int.Parse(MultiNodes[1].Replace("}", ""));
aNodes[NodeIndex].InnerText = v.Value.Data;
}
}
else
{
aNodes = xmldoc.SelectNodes(FValue, xmlnsManager);
if (aNodes.Count >= 1)
{
aNodes[0].InnerText = v.Value.Data;
}
}
}
}
}
xmldoc.Save(XMLOutput);
//Now we've updated the XML apply it to the pdf
xfa.FillXfaForm(new FileStream(XMLOutput, FileMode.Open, FileAccess.Read));
xfa.Write(pdf);
pdf.Close();
FYI I've also tried to set a field directly also with the same results.
PdfReader preader = new PdfReader(source);
PdfDocument pdfDoc=new PdfDocument(preader, new PdfWriter(dest), new StampingProperties().UseAppendMode());
PdfAcroForm pdfForm = PdfAcroForm.GetAcroForm(pdfDoc, true);
XfaForm xform = pdfForm.GetXfaForm();
xform.SetXfaFieldValue("VRM[0].CoverPage[0].Wrap2[0].Table[0].CSID[0]", "Test");
xform.Write(pdfForm);
pdfDoc.Close();
If anyone has any ideas it would be appreciated.
Cheers
I ran into a very similar issue. I was attempting to auto fill an XFA that was password protected while not breaking the certificate or usage rights (it allowed filling). iText7 seems to have made this not possible for legal/practical reasons, however it is still very much possible with iText5. I wrote the following working codeusing iTextSharp (C# version if iText5):
using iTextSharp.text;
using iTextSharp.text.pdf;
string pathToRead = "/Users/home/Desktop/c#pdfParser/encrypted_empty.pdf";
string pathToSave = "/Users/home/Desktop/c#pdfParser/xfa_encrypted_filled.pdf";
string data = "/Users/home/Desktop/c#pdfParser/sample_data.xml";
FillByItextSharp5(pathToRead, pathToSave, data);
static void FillByItextSharp5(string pathToRead, string pathToSave, string data)
{
using (FileStream pdf = new FileStream(pathToRead, FileMode.Open))
using (FileStream xml = new FileStream(data, FileMode.Open))
using (FileStream filledPdf = new FileStream(pathToSave, FileMode.Create))
{
PdfReader.unethicalreading = true;
PdfReader pdfReader = new PdfReader(pdf);
PdfStamper stamper = new PdfStamper(pdfReader, filledPdf, '\0', true);
stamper.AcroFields.Xfa.FillXfaForm(xml, true);
stamper.Close();
pdfReader.Close();
}
}
PdfStamper stamper = new PdfStamper(pdfReader, filledPdf, '\0', true)
you have to use this line.
I have the following code to set background color to one of my fields but for some reason I can not control the transparency of the background. Can someone please take a look at it and let me know what I am doing wrong. Thanks.
using (var newFileStream = new FileStream(fileNameNew, FileMode.Create))
{
// Open existing PDF
var pdfReader = new PdfReader(existingFileStream);
// PdfStamper, which will create
var stamper = new PdfStamper(pdfReader, newFileStream);
var form = stamper.AcroFields;
var fieldKeys = form.Fields.Keys;
foreach (string fieldKey in fieldKeys)
{
if (fieldKey.Equals("Title"))
{
form.SetFieldProperty(fieldKey, "bgcolor", new BaseColor(System.Drawing.Color.FromArgb(20,225,160,0)),null);
form.SetField(fieldKey, "Test");
}
else
{
form.SetField(fieldKey, "REPLACED!");
}
}
stamper.FormFlattening = true;
stamper.Close();
pdfReader.Close();
}
Just in case anyone else faces the same problem
var pdfReader = new PdfReader(existingFileStream);
// PdfStamper, which will create
var stamper = new PdfStamper(pdfReader, newFileStream);
var form = stamper.AcroFields;
var fieldKeys = form.Fields.Keys;
PdfContentByte background;
foreach (string fieldKey in fieldKeys)
{
if (fieldKey.Equals("Title"))
{
//form.SetFieldProperty(fieldKey, "bgColor", new BaseColor(System.Drawing.Color.FromArgb(125,225,160,0)),null);
form.SetField(fieldKey, "Test");
IList<AcroFields.FieldPosition> pos = form.GetFieldPositions(fieldKey);
PdfContentByte contentBtye = stamper.GetOverContent(pos[0].page);
contentBtye.SetColorFill(new BaseColor(200, 50, 50));
contentBtye.Rectangle(pos.FirstOrDefault().position.Left, pos.FirstOrDefault().position.Bottom, pos.FirstOrDefault().position.Width, pos.FirstOrDefault().position.Height);
PdfGState state = new PdfGState();
state.FillOpacity = 0.5f;
contentBtye.SetGState(state);
contentBtye.Fill();
}
else
{
form.SetField(fieldKey, "REPLACED!");
}
}
I would like to parse a PDF and find the logo via known attributes and when I find a match, remove that image and then copy everything else.
I am using the code below to replace an image with a blank white image to remove a logo from PDFs that are to be printed on letterhead. It replaces the image with a white image of the same size. Is there a way to modify this to actually remove the image (and thus save some space, etc.?).
private static void Main(string[] args)
{
ManipulatePdf(#"C:\in.pdf", #"C:\out.pdf");
Console.WriteLine("Finished - press a key");
Console.ReadKey();
}
public static void ManipulatePdf(String src, String dest)
{
Console.WriteLine("Start");
PdfReader reader = new PdfReader(src);
// first read all references and find the one we wish to work on.
PdfDictionary page = reader.GetPageN(1); // all resources are available to every page (?)
PdfDictionary resources = page.GetAsDict(PdfName.RESOURCES);
PdfDictionary xobjects = resources.GetAsDict(PdfName.XOBJECT);
page = reader.GetPageN(1);
resources = page.GetAsDict(PdfName.RESOURCES);
xobjects = resources.GetAsDict(PdfName.XOBJECT);
foreach (PdfName pdfName in xobjects.Keys)
{
PRStream stream = (PRStream) xobjects.GetAsStream(pdfName);
if (stream.Length > 100000)
{
PdfImage image = new PdfImage(MakeBlankImg(), "", null);
Console.WriteLine("Calling replace stream");
ReplaceStream(stream, image);
}
}
PdfStamper stamper = new PdfStamper(reader, new FileStream(dest, FileMode.Create));
stamper.Close();
reader.Close();
}
public static iTextSharp.text.Image MakeBlankImg()
{
Console.WriteLine("Making small blank image");
byte[] array;
using (MemoryStream ms = new MemoryStream())
{
//var drawingImage = image.GetDrawingImage();
using (Bitmap newBi = new Bitmap(1, 1))
{
using (Graphics g = Graphics.FromImage(newBi))
{
g.Clear(Color.White);
g.Flush();
}
newBi.Save(ms, ImageFormat.Jpeg);
}
array = ms.ToArray();
}
Console.WriteLine("Image array is " + array.Length + " bytes.");
return iTextSharp.text.Image.GetInstance(array);
}
public static void ReplaceStream(PRStream orig, PdfStream stream)
{
orig.Clear();
MemoryStream ms = new MemoryStream();
stream.WriteContent(ms);
orig.SetData(ms.ToArray(), false);
Console.WriteLine("Iterating keys");
foreach (KeyValuePair<PdfName, PdfObject> keyValuePair in stream)
{
Console.WriteLine("Key: " + keyValuePair.Key.ToString());
orig.Put(keyValuePair.Key, stream.Get(keyValuePair.Key));
}
}
}
I want to export razor view to pdf by using the itextsharp library. The problem is that some turkish characters such as İ,ı,Ş,ş etc... are missing in the pdf document. The code used to export the pdf is:
public PdfActionResult(object model)
{
Model = model;
}
public override void ExecuteResult(ControllerContext context)
{
IView viewEngineResult;
ViewContext viewContext;
if (ViewName == null)
{
ViewName = context.RouteData.GetRequiredString("action");
}
context.Controller.ViewData.Model = Model;
var workStream = new MemoryStream();
var document = new Document();
PdfWriter writer = PdfWriter.GetInstance(document, workStream);
writer.CloseStream = false;
document.Open();
viewEngineResult = ViewEngines.Engines.FindView(context, ViewName, null).View;
var sb = new StringBuilder();
TextWriter tr = new StringWriter(sb);
viewContext = new ViewContext(context, viewEngineResult, context.Controller.ViewData,
context.Controller.TempData, tr);
viewEngineResult.Render(viewContext, tr);
CultureInfo ci = new CultureInfo("az-Latn-AZ");
Encoding enc = Encoding.GetEncoding(ci.TextInfo.ANSICodePage);
Stream stream = new MemoryStream(enc.GetBytes(sb.ToString()));
XMLWorkerHelper.GetInstance().ParseXHtml(writer, document, stream, null);
document.Close();
new FileContentResult(workStream.ToArray(), "application/pdf").ExecuteResult(context);
}
}
Then I access it as:
public ActionResult StudentPdf(Guid studentId)
{
var model = _studentRepository.GetByIdGuid(studentId);
return new PdfActionResult(model);
}
Thanks for reply
by this way you can print all turkish character.
String htmlText = html.ToString();
Document document = new Document();
string filePath = HostingEnvironment.MapPath("~/Content/Pdf/");
PdfWriter.GetInstance(document, new FileStream(filePath + "\\pdf-"+Name+".pdf", FileMode.Create));
document.Open();
iTextSharp.text.html.simpleparser.HTMLWorker hw = new iTextSharp.text.html.simpleparser.HTMLWorker(document);
FontFactory.Register(Path.Combine(_webHelper.MapPath("~/App_Data/Pdf/arial.ttf")), "Garamond"); // just give a path of arial.ttf
StyleSheet css = new StyleSheet();
css.LoadTagStyle("body", "face", "Garamond");
css.LoadTagStyle("body", "encoding", "Identity-H");
css.LoadTagStyle("body", "size", "12pt");
hw.SetStyleSheet(css);
hw.Parse(new StringReader(htmlText));
Hope this helps
Regards,
Vinit Patel
After a long time of struggling with this not-so-friendly API, I am finally making progress, but now I've come to a really nasty issue.. I have placed "dir" attributes in various places in my html with the value being "rtl".. but the XMLWorker doesn't seem to respect that at all. Does anyone know of a workaround? Here's my method:
public static void Generate<TModel>(string templateFile, TModel model, string outputFile, IEnumerable<string> fonts)
{
string template = System.IO.File.ReadAllText(templateFile);
string result = Razor.Parse(template, model);
using (var fsOut = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
using (var stringReader = new StringReader(result))
{
var document = new Document();
var pdfWriter = PdfWriter.GetInstance(document, fsOut);
pdfWriter.InitialLeading = 12.5f;
document.Open();
var xmlWorkerHelper = XMLWorkerHelper.GetInstance();
var cssResolver = new StyleAttrCSSResolver();
//cssResolver.AddCss(cssFile);
var xmlWorkerFontProvider = new XMLWorkerFontProvider();
foreach (string font in fonts)
{
xmlWorkerFontProvider.Register(font);
}
var cssAppliers = new CssAppliersImpl(xmlWorkerFontProvider);
var htmlContext = new HtmlPipelineContext(cssAppliers);
htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());
PdfWriterPipeline pdfWriterPipeline = new PdfWriterPipeline(document, pdfWriter);
HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, pdfWriterPipeline);
CssResolverPipeline cssResolverPipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
XMLWorker xmlWorker = new XMLWorker(cssResolverPipeline, true);
XMLParser xmlParser = new XMLParser(xmlWorker);
xmlParser.Parse(stringReader);
document.Close();
}
}
I've created a sample to show how to parse and display RTL data using XMLWorker. Download it from here.