1

I am using this script to loop through a users files and fill out a copy of the details.pdf template and merge them all together as one file.

The problem i have is that the tfTotalPrice field isn't calculating until i manually open the document javascript and change something, like adding an alert. I think it may have something to do with the need for a "/CO" dictionary key, but i don't understand why. I just read that in the ISO32000-1 doc.

static void Main(string[] args)
    {
        int i = 0;
        int partsTotal = args.Count();

        //template page
        string tempDetails = @"C:\temp\Details.pdf";
        //final multi page quote
        string finishedQuote = @"C:\temp\Quote.pdf";

        DateTime processedDate = DateTime.Now;
        string dateProc = processedDate.ToString("MM/dd/yyyy");

        Console.WriteLine("Please Enter Your Customer Name and Press Enter");
        string customer = Console.ReadLine();

        //This will hold the memory streams and be used to merge them all
        List<byte[]> pagesToMerge = new List<byte[]>();

        Document doc = new Document();
        PdfSmartCopy copy = new PdfSmartCopy(doc, new FileStream(finishedQuote, FileMode.Create));

        doc.Open();
        copy.SetMergeFields();
        copy.SetPdfVersion(PdfWriter.PDF_VERSION_1_7);

        List<PdfReader> readers = new List<PdfReader>();

        for (i = 0; i < partsTotal; i++)
        {
            string partFile = args[i].ToString();
            string partFileName = partFile.Substring(partFile.LastIndexOf(@"\") + 1);
            string partFileLocation = partFile.Substring(0, partFile.LastIndexOf(@"\"));
            Console.WriteLine("Processing : " + partFileName);

            PdfReader reader = new PdfReader(RenameFields(tempDetails, i));
            readers.Add(reader);
            copy.AddDocument(reader);

        }

        doc.Close();
        foreach (PdfReader reader in readers)
        {
            reader.Close();
        }


        //Should we open it up?
        bool response = GetYorN();
        if (response == true)
        {
            System.Diagnostics.Process.Start(finishedQuote);
        }

    }

    static byte[] RenameFields(string src, int i)
    {
        //This method creates a new document in memory with newly named unique fields
        MemoryStream baos = new MemoryStream();
        PdfReader reader = new PdfReader(src);
        PdfStamper stamper = new PdfStamper(reader, baos);
        stamper.Writer.SetPdfVersion(PdfWriter.PDF_VERSION_1_7);
        stamper.AcroFields.GenerateAppearances = true;
        stamper.FormFlattening = false;

        AcroFields form = stamper.AcroFields;
        //populate names with the dictionary keys for the current file(stream)
        string[] names = new string[stamper.AcroFields.Fields.Keys.Count];
        stamper.AcroFields.Fields.Keys.CopyTo(names, 0);

        //rename all fields in this file(stream)
        foreach (string name in names)
        {
            stamper.AcroFields.RenameField(name, name + i);
        }

        //Add the calculation action to totalPrice(i)
        AcroFields.Item newTotalField = form.GetFieldItem("tfTotalPrice" + i);
        PdfDictionary newTotalRefDict = (PdfDictionary)PdfReader.GetPdfObject(newTotalField.GetWidgetRef(0));
        PdfString newTotalP = newTotalRefDict.GetAsString(PdfName.T);

        AcroFields.Item newSetupField = stamper.AcroFields.GetFieldItem("tfSetupPrice" + i);
        PdfDictionary newSetupRefDict = (PdfDictionary)PdfReader.GetPdfObject(newSetupField.GetWidgetRef(0));
        PdfString newSetupP = newSetupRefDict.GetAsString(PdfName.T);

        AcroFields.Item newToolField = stamper.AcroFields.GetFieldItem("tfToolPrice" + i);
        PdfDictionary newToolRefDict = (PdfDictionary)PdfReader.GetPdfObject(newToolField.GetWidgetRef(0));
        PdfString newToolP = newToolRefDict.GetAsString(PdfName.T);

        AcroFields.Item newPieceField = stamper.AcroFields.GetFieldItem("tfPiecePrice" + i);
        PdfDictionary newPieceRefDict = (PdfDictionary)PdfReader.GetPdfObject(newPieceField.GetWidgetRef(0));
        PdfString newPieceP = newPieceRefDict.GetAsString(PdfName.T);

        AcroFields.Item newFixtureField = stamper.AcroFields.GetFieldItem("tfFixturePrice" + i);
        PdfDictionary newFixtureRefDict = (PdfDictionary)PdfReader.GetPdfObject(newFixtureField.GetWidgetRef(0));
        PdfString newFixtureP = newFixtureRefDict.GetAsString(PdfName.T);

        //Declare the sum action
        string newJS = "this.getField(\"" + newTotalP + "\").value = (this.getField(\"" + newPieceP + "\").value + this.getField(\"" + newToolP + "\").value + this.getField(\"" + newFixtureP + "\").value + this.getField(\"" + newSetupP + "\").value);";
        PdfAction pdfAction = PdfAction.JavaScript(newJS, stamper.Writer);
        PdfDictionary widgetRefDict = (PdfDictionary)PdfReader.GetPdfObject(newTotalField.GetWidgetRef(0));
        //AA = Additional Action Annotation Dictionary
        PdfDictionary actionDict = widgetRefDict.GetAsDict(PdfName.AA);
        //C = Calculation that lives inside of the AA dictionary
        PdfDictionary calcDict = actionDict.GetAsDict(new PdfName("C"));

        //Plug in the js calc with the new form field names
        calcDict.Put(new PdfName("JS"), new PdfString(newJS));

        //stamper.SetPageAction(PdfWriter.PAGE_OPEN, PdfAction.JavaScript(newJS, stamper.Writer), 1);

        //close up shop and return the new doc in memory
        stamper.Close();
        reader.Close();
        return baos.ToArray();
    }

Any help would be greatly appreciated, i have been stuck on this forever :( -Thank you

Here is a link to the PDFs

knutter539
  • 74
  • 8
  • 1
    iText doesn't invoke the JavaScript in the form, it just fills out the values. Hence it is normal that the calculations aren't executed until the viewer triggers the JavaScript. Renaming fields might also require changing the JavaScript, so your requirement might be harder to meet than you originally imagined. – Bruno Lowagie Mar 01 '18 at 16:30
  • My script changes the javascript for the renamed fields. I verified this by looking at the calculation tab for each totals field. – knutter539 Mar 01 '18 at 16:37
  • OK, great, but if you want the calculations to be executed *before* they are done in the viewer, you'll have to execute that Javascript in your code and adapt the corresponding fields. I know that we've done this for XFA, but we didn't do this for AcroForm fields. – Bruno Lowagie Mar 01 '18 at 16:39
  • I don't think explained this properly sorry. The price fields are updated by the user after itext has created the document. I don't know the quote prices at the time of document creation. – knutter539 Mar 01 '18 at 16:42
  • OK, so you need some action to be triggered "onBlur" (I probably didn't read your question well enough). – Bruno Lowagie Mar 01 '18 at 16:43
  • Yes, so that after the user runs the script, each page is ready to calculate the total price for each page as he goes through and enters in his estimates. – knutter539 Mar 01 '18 at 16:45

1 Answers1

1

So the only way i could get things to calculate properly was replace the existing fields with new ones:

static byte[] RenameFields(string src, int i, string imageName, string customer, string dateProc, string partFileName, string partFileLocation)
    {
        //This method creates a new document in memory with newly named unique fields
        MemoryStream baos = new MemoryStream();
        PdfReader reader = new PdfReader(src);
        PdfStamper stamper = new PdfStamper(reader, baos);
        stamper.Writer.SetPdfVersion(PdfWriter.PDF_VERSION_1_7);
        stamper.AcroFields.GenerateAppearances = true;
        stamper.FormFlattening = false;
        AcroFields form = stamper.AcroFields;

        //Set Fields that don't need to be recreated
        form.SetField("tfCustomer", customer);
        form.SetField("tfDate", dateProc);
        form.SetField("tfFileName", partFileName);
        form.SetField("tfFileLocation", partFileLocation);
        form.SetField("tfQuantity", "300");
        form.SetField("tfLeadTime", "-");
        form.SetField("tfMaterialType", "N/A");
        form.SetField("tfThickness", "N/A");

        //Place Image
        string imageField = "ifPart";
        iTextSharp.text.Rectangle imageRect = form.GetFieldPositions(imageField)[0].position;
        Bitmap imageBitmap = new Bitmap(imageName);
        iTextSharp.text.Image image = iTextSharp.text.Image.GetInstance(imageBitmap, System.Drawing.Imaging.ImageFormat.Jpeg);
        image.ScaleAbsolute(imageRect.Width, imageRect.Height);
        image.SetAbsolutePosition(imageRect.GetLeft(0), imageRect.GetBottom(0));
        PdfContentByte content = stamper.GetOverContent(1);
        content.AddImage(image);

        //populate names with the dictionary keys for the current file(stream)
        string[] names = new string[stamper.AcroFields.Fields.Keys.Count];
        stamper.AcroFields.Fields.Keys.CopyTo(names, 0);

        //rename all fields in this file(stream)
        foreach (string name in names)
        {
            stamper.AcroFields.RenameField(name, name + i);
        }



        //Add the calculation action to totalPrice(i)
        AcroFields.Item newTotalField = form.GetFieldItem("tfTotalPrice" + i);
        PdfDictionary newTotalRefDict = (PdfDictionary)PdfReader.GetPdfObject(newTotalField.GetWidgetRef(0));
        PdfString newTotalP = newTotalRefDict.GetAsString(PdfName.T);
        PdfArray totalRect = newTotalRefDict.GetAsArray(PdfName.RECT);

        AcroFields.Item newSetupField = stamper.AcroFields.GetFieldItem("tfSetupPrice" + i);
        PdfDictionary newSetupRefDict = (PdfDictionary)PdfReader.GetPdfObject(newSetupField.GetWidgetRef(0));
        PdfString newSetupP = newSetupRefDict.GetAsString(PdfName.T);
        PdfArray setupRect = newSetupRefDict.GetAsArray(PdfName.RECT);


        AcroFields.Item newToolField = stamper.AcroFields.GetFieldItem("tfToolPrice" + i);
        PdfDictionary newToolRefDict = (PdfDictionary)PdfReader.GetPdfObject(newToolField.GetWidgetRef(0));
        PdfString newToolP = newToolRefDict.GetAsString(PdfName.T);
        PdfArray toolRect = newToolRefDict.GetAsArray(PdfName.RECT);

        AcroFields.Item newPieceField = stamper.AcroFields.GetFieldItem("tfPiecePrice" + i);
        PdfDictionary newPieceRefDict = (PdfDictionary)PdfReader.GetPdfObject(newPieceField.GetWidgetRef(0));
        PdfString newPieceP = newPieceRefDict.GetAsString(PdfName.T);
        PdfArray pieceRect = newPieceRefDict.GetAsArray(PdfName.RECT);

        AcroFields.Item newFixtureField = stamper.AcroFields.GetFieldItem("tfFixturePrice" + i);
        PdfDictionary newFixtureRefDict = (PdfDictionary)PdfReader.GetPdfObject(newFixtureField.GetWidgetRef(0));
        PdfString newFixtureP = newFixtureRefDict.GetAsString(PdfName.T);
        PdfArray fixtureRect = newFixtureRefDict.GetAsArray(PdfName.RECT);

        //PiecePrice Field
        var llxPiece = Convert.ToDecimal(pieceRect[0].ToString());
        var llyPiece = Convert.ToDecimal(pieceRect[1].ToString());
        var urxPiece = Convert.ToDecimal(pieceRect[2].ToString());
        var uryPiece = Convert.ToDecimal(pieceRect[3].ToString());
        //Remove Old Price Field
        form.RemoveField(newPieceP.ToString());
        //In with the new
        TextField PieceField = new TextField(stamper.Writer, new iTextSharp.text.Rectangle((float)llxPiece, (float)llyPiece, (float)urxPiece, (float)uryPiece), newPieceP.ToString())
        {
            Alignment = 1,
            Text = "0.00"
        };
        PdfFormField fPiece = PieceField.GetTextField();
        fPiece.SetAdditionalActions(PdfName.BL, PdfAction.JavaScript("this.getField('" + newTotalP + "').value = (parseFloat(this.getField('" + newPieceP + "').value) + parseFloat(this.getField('" + newToolP + "').value) + parseFloat(this.getField('" + newFixtureP + "').value) + parseFloat(this.getField('" + newSetupP + "').value));", stamper.Writer));
        fPiece.SetAdditionalActions(PdfName.F, PdfAction.JavaScript("AFNumber_Format(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        fPiece.SetAdditionalActions(PdfName.K, PdfAction.JavaScript("AFNumber_Keystroke(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        stamper.AddAnnotation(fPiece, 1);

        //ToolPrice Field
        var llxTool = Convert.ToDecimal(toolRect[0].ToString());
        var llyTool = Convert.ToDecimal(toolRect[1].ToString());
        var urxTool = Convert.ToDecimal(toolRect[2].ToString());
        var uryTool = Convert.ToDecimal(toolRect[3].ToString());
        //Remove Old Price Field
        form.RemoveField(newToolP.ToString());
        //In with the new
        TextField ToolField = new TextField(stamper.Writer, new iTextSharp.text.Rectangle((float)llxTool, (float)llyTool, (float)urxTool, (float)uryTool), newToolP.ToString())
        {
            Alignment = 1,
            Text = "0.00"
        };
        PdfFormField fTool = ToolField.GetTextField();
        fTool.SetAdditionalActions(PdfName.BL, PdfAction.JavaScript("this.getField('" + newTotalP + "').value = (parseFloat(this.getField('" + newPieceP + "').value) + parseFloat(this.getField('" + newToolP + "').value) + parseFloat(this.getField('" + newFixtureP + "').value) + parseFloat(this.getField('" + newSetupP + "').value));", stamper.Writer));
        fTool.SetAdditionalActions(PdfName.F, PdfAction.JavaScript("AFNumber_Format(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        fTool.SetAdditionalActions(PdfName.K, PdfAction.JavaScript("AFNumber_Keystroke(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        stamper.AddAnnotation(fTool, 1);

        //FixturePrice Field
        var llxFixture = Convert.ToDecimal(fixtureRect[0].ToString());
        var llyFixture = Convert.ToDecimal(fixtureRect[1].ToString());
        var urxFixture = Convert.ToDecimal(fixtureRect[2].ToString());
        var uryFixture = Convert.ToDecimal(fixtureRect[3].ToString());
        //Remove Old Price Field
        form.RemoveField(newFixtureP.ToString());
        //In with the new
        TextField FixtureField = new TextField(stamper.Writer, new iTextSharp.text.Rectangle((float)llxFixture, (float)llyFixture, (float)urxFixture, (float)uryFixture), newFixtureP.ToString())
        {
            Alignment = 1,
            Text = "0.00"
        };
        PdfFormField fFixture = FixtureField.GetTextField();
        fFixture.SetAdditionalActions(PdfName.BL, PdfAction.JavaScript("this.getField('" + newTotalP + "').value = (parseFloat(this.getField('" + newPieceP + "').value) + parseFloat(this.getField('" + newToolP + "').value) + parseFloat(this.getField('" + newFixtureP + "').value) + parseFloat(this.getField('" + newSetupP + "').value));", stamper.Writer));
        fFixture.SetAdditionalActions(PdfName.F, PdfAction.JavaScript("AFNumber_Format(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        fFixture.SetAdditionalActions(PdfName.K, PdfAction.JavaScript("AFNumber_Keystroke(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        stamper.AddAnnotation(fFixture, 1);

        //SetupPrice Field
        var llxSetup = Convert.ToDecimal(setupRect[0].ToString());
        var llySetup = Convert.ToDecimal(setupRect[1].ToString());
        var urxSetup = Convert.ToDecimal(setupRect[2].ToString());
        var urySetup = Convert.ToDecimal(setupRect[3].ToString());
        //Remove Old Price Field
        form.RemoveField(newSetupP.ToString());
        //In with the new
        TextField setupField = new TextField(stamper.Writer, new iTextSharp.text.Rectangle((float)llxSetup, (float)llySetup, (float)urxSetup, (float)urySetup), newSetupP.ToString())
        {
            Alignment = 1,
            Text = "0.00"
        };
        PdfFormField fSetup = setupField.GetTextField();
        fSetup.SetAdditionalActions(PdfName.BL, PdfAction.JavaScript("this.getField('" + newTotalP + "').value = (parseFloat(this.getField('" + newPieceP + "').value) + parseFloat(this.getField('" + newToolP + "').value) + parseFloat(this.getField('" + newFixtureP + "').value) + parseFloat(this.getField('" + newSetupP + "').value));", stamper.Writer));
        fSetup.SetAdditionalActions(PdfName.F, PdfAction.JavaScript("AFNumber_Format(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        fSetup.SetAdditionalActions(PdfName.K, PdfAction.JavaScript("AFNumber_Keystroke(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        stamper.AddAnnotation(fSetup, 1);

        var llxTotal = Convert.ToDecimal(totalRect[0].ToString());
        var llyTotal = Convert.ToDecimal(totalRect[1].ToString());
        var urxTotal = Convert.ToDecimal(totalRect[2].ToString());
        var uryTotal = Convert.ToDecimal(totalRect[3].ToString());
        form.RemoveField(newTotalP.ToString());
        TextField TotalField = new TextField(stamper.Writer, new iTextSharp.text.Rectangle((float)llxTotal, (float)llyTotal, (float)urxTotal, (float)uryTotal), newTotalP.ToString())
        {
            Alignment = 1
        };
        PdfFormField fTotal = TotalField.GetTextField();
        fTotal.SetAdditionalActions(PdfName.BL, PdfAction.JavaScript("this.getField('" + newTotalP + "').value = (parseFloat(this.getField('" + newPieceP + "').value) + parseFloat(this.getField('" + newToolP + "').value) + parseFloat(this.getField('" + newFixtureP + "').value) + parseFloat(this.getField('" + newSetupP + "').value));", stamper.Writer));
        fTotal.SetAdditionalActions(PdfName.F, PdfAction.JavaScript("AFNumber_Format(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        fTotal.SetAdditionalActions(PdfName.K, PdfAction.JavaScript("AFNumber_Keystroke(2, 0, 0, 0, '\u0024', true);", stamper.Writer));
        stamper.AddAnnotation(fTotal, 1);

        stamper.AcroFields.GenerateAppearances = true;

        //close up shop and return the new doc in memory
        stamper.Close();
        reader.Close();
        return baos.ToArray();
    }

Thanks for your help Bruno, onBlur was indeed what i needed.

knutter539
  • 74
  • 8