Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

Custom Dialog fails to render Imported Icon properly or consistently

New Here ,
Jan 10, 2025 Jan 10, 2025

Acrobat DC, version 2024.005.20320

 

Alright, I'm losing my mind here and I'm pretty sure I'm running into a bug with how Custom Dialogs render Images. However, I am also questioning my overall direction with this project. I would really appreciate any ideas or help!

 

Overall problem: I have a PDF form that needs up to 13 digital signatures and then a dated stamp with/after the last signature. 

 

Attempted solution: Since the stamp is different for every location, but rarely changes, I've rebuilt the PDF to allow embedding the stamp as a button icon before distribution, then hiding the icon until the Certifying Official digitally signs the form. I've built a Custom Dialog to walk users through importing the icon and loading in the required information. This is supposed to include a preview of the imported stamp. 

 

Current problem: The 'preview' image of the imported stamp does not render correctly and is a different size from the original. The imported stamp's size seems to be based on whether the original file has a transparent background or not. Transparent background is imported as 254x254; while a white background is imported as 81x81. On rare occassions, the 81x81 will render properly, but it's not consistent. In all cases, the button on the PDF renders the image correctly.

Additionally, I've found that the "util.iconStreamFromIcon(...).read()" will only ever return the first 8,192 hexidecimals of the image, regardless of size. Which is irritating, as I'm also trying to determine the stamp's color after the import process.

 

Things I've tried:

  • Changing the Image height/width (incremented from 64 to 400)
  • Changing the "this.offset"
  • Changing the ConvertToPDF compression settings
  • Rebooting

 

Original Transparent Stamp: 339x339

ADPS - Red - Transparent.png

 

Rendering errors:

Adobe - Image Bug 1.pngAdobe - Image Bug 2.pngAdobe - Image Bug 3.pngAdobe - Image Bug 4.pngAdobe - Image Bug 5.pngAdobe - Image Bug 6.pngAdobe - Image Bug 7.png

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Code:

 

function customizeOMMdata()
{
  //region Data loading
  var refDoc = this, intH, intW;
  var intImportResult;
  var DialogResult, IDnum;
  var arrBad = [], intTryAgain, arrOMM, arrOMC, strOMCname, strOMCmsg, intConfirmOMM, chkLockToField;

  var strDialogInstructions = 
  "Filling in the fields below will customize this form to your Office. \n" +
  "\n" +
  "Embedding your All-Purpose Date Stamp will enable fully digital processing. \n" +
  "When the Certifying Official signs, the APDS will appear to certify the form. \n" +
  "\n" +
  "By checking the box on the right side and completing the information below it, \n" +
  "the ADPS can be restricted to display ONLY when the Manager signs."

  //Get current APDS, digest the information into an easier to use object.
  var intMinStampSize = 32, intMaxStampSize = 500;
  //var icoAPDS = getField("OMC.Stamp").buttonGetIcon(0);
  var strmAPDS = util.iconStreamFromIcon(getField("OMC.Stamp").buttonGetIcon(0));
  var objAPDS = {
    //Name: icoAPDS.name,
    Bytes: strmAPDS.read(),
    Height: intH ? intH : Math.min(Math.max(strmAPDS.height, intMinStampSize), intMaxStampSize), //Math.max selects larger of height vs Min size; Math.min selects smaller of height vs Max size.
    Width: intW ? intW : Math.min(Math.max(strmAPDS.width, intMinStampSize), intMaxStampSize)
  }
  var dialogAPDS = {
    type: "image", 
    item_id: "APDS", 
    width: objAPDS.Width,
    height: objAPDS.Height
  };

  //Get OMM info into an object for easy reference, fewer? ops
  var objOMManager = {
    NameFirst: getField("OMC.Auth.Name.First").valueAsString,
    NameLast:  getField("OMC.Auth.Name.Last").valueAsString,
    IDnum: getField("OMC.Auth.IDnum").valueAsString, //valueAsString to remove the number spinner.
    Email: getField("OMC.Auth.Email").valueAsString
  };

  //Same as OMM object, but for the OMC.
  var objOMCenter = {
    Unit:  getField("OMC.Info.Unit").valueAsString,
    OfficeSymbol: getField("OMC.Info.OfficeSymbol").valueAsString,
    State:  getField("OMC.Info.State").valueAsString,
    Email: getField("OMC.Info.Email").valueAsString
  };

  if(objOMManager.IDnum || objOMManager.Email){blnOMMrequired = true}
  else{blnOMMrequired = false};

  //In order to push data into the Dialog, an Object with Keys exactly 4 characters long must be provided.
  var DialogData = {
    "APDS": { width: objAPDS.Width, 
              height: objAPDS.Height, 
              offset: 0, 
              read: bytes => objAPDS.Bytes.substring(this.offset, this.offset += bytes)}, //this function makes no sense, but Acrobat hangs if you don't use it. Substring or Slice both work.
    "Fnme": objOMManager.NameFirst,
    "Lnme": objOMManager.NameLast,
    "nmID": objOMManager.IDnum,
    "Mail": objOMManager.Email,
    "Unit": objOMCenter.Unit,
    "OSym": objOMCenter.OfficeSymbol,
    "Stat": objOMCenter.State,
    "OBox": objOMCenter.Email,
    "OMrq": blnOMMrequired
  };

  //region Dialog Description
  //Literal JS Object with each property describing the look and flow of the dialog. Order matters.
  var objDialogDescription = 
  {name: "Set authorized Manager", first_tab: "Unit", elements:[
    {type: "view", elements:[
      {type: "static_text", alignment: "align_center", mutliline: true, char_height: 12, name: strDialogInstructions},
      {type: "view", align_children: "align_row", elements:[
        {type: "cluster", align_children: "align_center", name: "Embedded All-Purpose Date Stamp", char_width: 21, char_height: 22, elements:[
          dialogAPDS,
          {type: "button", item_id: "embd", name: "Change APDS"}
        ]},
        {type: "cluster", name: "Manager's info:", char_width: 20, char_height: 22, elements:[
          {type: "check_box", item_id: "OMrq", next_tab: "Fnme", name: "Restrict APDS to Manager's signature?"},
          {type: "view", align_children: "align_row", elements:[
            {type: "static_text", name: "First name:"},
            {type: "gap", width: 15},
            {type: "edit_text", item_id: "Fnme", next_tab: "Lnme", char_width: 20}
          ]},
          {type: "view", align_children: "align_row", elements:[
            {type: "static_text", name: "Last name:"},
            {type: "gap", width: 16},
            {type: "edit_text", item_id: "Lnme", next_tab: "nmID", char_width: 20}
          ]},
          {type: "view", align_children: "align_row", elements:[
            {type: "static_text", name: "ID number:"},
            //{type: "gap", width: 0},
            {type: "edit_text", item_id: "nmID", next_tab: "Mail", char_width: 7}
          ]},
          {type: "view", align_children: "align_row", elements:[
            {type: "static_text", name: "Email address:"},
            {type: "gap", width: -2},
            {type: "edit_text", item_id: "Mail", next_tab: "Unit", char_width: 20}
          ]}
        ]},
      ]},
      {type: "cluster", align_children: "align_center", name: "Center info:", elements:[
        {type: "view", align_children: "align_row", elements:[
          //{type: "gap", width: -4},
          {type: "static_text", name: "Unit Abbreviation:"},
          //{type: "gap", char_width: 1},
          {type: "edit_text", item_id: "Unit", next_tab: "OSym", char_width: 6},
          //{type: "gap", width: 34},
          {type: "static_text", name: "Office Symbol:"},
          //{type: "gap", width: 6},
          {type: "edit_text", item_id: "OSym", next_tab: "Stat", char_width: 6},
        ]},
        {type: "view", align_children: "align_row", elements:[
          {type: "gap", width: 6},
          {type: "static_text", name: "State:"},
          {type: "gap", width: 24},
          {type: "edit_text", item_id: "Stat", next_tab: "OBox", char_width: 35},
        ]},
        {type: "view", align_children: "align_row", elements:[
          {type: "gap", width: 6},
          {type: "static_text", name: "Center Email:"},
          //{type: "gap", width: -3},
          {type: "edit_text", item_id: "OBox", next_tab: "Fnme", char_width: 35},
        ]},
      ]}
    ]},
    {type: "ok_cancel_other", ok_name: "Save", other_name: "Remove"}
  ]};

  //region Dialog Execution
  //OMMdata variable holds the result and data from the dialog.
  var OMMdata =
  {
    //Set the default result string
    result: "cancel",
    
    //This adds a method to the OMMdata Object that executes the dialog.
    RunDialog: function(){return app.execDialog(this);},
    
    //This method runs first and takes an object and passes it into the Dialog
    initialize: function(dialog)
    {
      dialog.load(DialogData);
      dialog.enable({
        "Fnme": blnOMMrequired,
        "Lnme": blnOMMrequired,
        "nmID": blnOMMrequired,
        "Mail": blnOMMrequired
      });
    },
    
    //This method takes data from the dialog and adds it back to the Object as properties, when result == `ok`.
    commit: function(dialog)
    {
      var oResults = dialog.store();
      this.FirstName = oResults["Fnme"];
      this.LastName = oResults["Lnme"];
      this.IDnum = oResults["nmID"];
      this.Email = oResults["Mail"];
      this.Unit = oResults["Unit"];
      this.OfficeSymbol = oResults["OSym"];
      this.State = oResults["Stat"];
      this.OMCemail = oResults["OBox"];
    },

    //Change APDS embed function
    "embd": function(dialog)
    {
      intImportResult = refDoc.importIcon({cName: 'APDS'}) //'This' refers to the Dialog at this point. A reference is captured beforehand to work around it.
      switch(intImportResult)
      {
        case 0: //Success: Set the new stamp and scale it to fit
          //Reload image into dialog

          dialog.end("rtry")

          getField("OMC.Stamp").buttonSetIcon(refDoc.getIcon('APDS'));
          getField("OMC.Stamp").buttonScaleWhen = scaleWhen.always;
          getField("OMC.Stamp").buttonPosition = position.overlay;
          getField("OMC.Stamp").buttonSetCaption({cCaption: "DATE"});
          getField("OMC.Stamp").textColor = color.black;
          break;
        case 1: //User canceled
          //if(app.alert({cTitle: "APDS Embed Cancelled", cMsg: "ADPS embedding has been canceled.\n\nWould you like to REMOVE the current ADPS?", nIcon: 2, nType: 2}) === 4){actionHideAPDS();}
          break;
        case -1: //File cannot be opened
          app.alert("The file selected cannot be opened. Try again with a different file.");
          //actionHideAPDS();
          break;
        case -2: //Selected page is invalid. Default is page 0, which should always be present.
        default: 
          //actionHideAPDS()
      }
    },

    //Validates "nmID" field on lost focus. Removes all non-digit characters, then cuts string to 10 characters.
    "nmID": function(dialog)
    {
      dialog.load({"nmID": dialog.store()["nmID"].replace(/\D/g,'').substring(0,10)});
    },

    "OMrq": function(dialog)
    {
      blnOMMrequired = dialog.store()["OMrq"];
      dialog.enable({
        "Fnme": blnOMMrequired,
        "Lnme": blnOMMrequired,
        "nmID": blnOMMrequired,
        "Mail": blnOMMrequired
      });
    },

    //Other button, "remove", function
    other: function(dialog)
    {
      dialog.end("kill") //Closes dialog and returns "kill" >> Limit is 4 characters
    },
    
    //This area defines what the dialog looks like, where it gets its data, and what all the buttons call to.
    description: objDialogDescription
  };
  
  //Outside of the object, the Dialog is called and executed.
  DialogResult = OMMdata.RunDialog()
  
  //region Result Handler
  //A switch conditional is used to handle the output. 
  switch(DialogResult)
  {
    case "ok":
      //Handler removed as it's not relevant to the issue.
    break;
    
    case "kill": //Remove all customizations
      this.resetForm(["OMC.Auth", "OMC.Info", "OMC.To"])
      getField("OMC.To").readonly = false;
      appearanceCustomHighlight(DocCustomHighlight);
      actionHideAPDS();
    break;

    case "rtry":
      customizeOMMdata();
      break;

    case "cancel":
    default:
      return false;
  }
}

 

 

TOPICS
Acrobat SDK and JavaScript , Windows
383
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 10, 2025 Jan 10, 2025

I've written many dialog scripts that display all kinds of images. The only time I see problems like this is when the specified width and height are different from the actual image data width and height.

 

So, to that end, there are two issues in the script.

1) the script contains code that selects a width and hieght that is different from the actual.

2)  There is no reason to recreate the icon stream. The "iconStreamFromIcon" function returns an icon stream, the "stmAPDS" variable. Just use it. 

 

Thom Parker - Software Developer at PDFScripting
Use the Acrobat JavaScript Reference early and often

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Jan 13, 2025 Jan 13, 2025

Thom,

 

Thank you for the response!

 

1) The purpose of 

intH ? intH : Math.min(Math.max(strmAPDS.height, intMinStampSize), intMaxStampSize)

 was to constrain the stamp to a minimum and maximum size, ideally to prevent user foolishness. Since it appears that the Image element of Custom Dialogs cannot scale images, I'll have to switch to a warning or error message. Additionally, I added the intH conditional for testing, so I could rapidly iterate through different sizes to try and find one that worked. Unfortunately, no size renders correctly. Even changing this line to just: "strmAPDS.height" results in the same rendering errors.

 

2) Oh, oops! I've fixed that. I was cobbling it together from examples, since neither is defined in the JS Ref.

 

3) Is there anyway for Javascript to parse through the entire icon? I was hoping to use .read() to feed the entire icon into a script to determine the overall color. Currently, I only get the first 8192 hexidecimals.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 13, 2025 Jan 13, 2025

So, a raster image is composed of rows and columns of pixels. In the Acrobat JavaScript model, a Pixel has 4 components that are 1 byte each ARGB, where A is the alpha channel. The width and height parameters in the icon stream object represent then number of columns and rows of pixels. These parameters are necessary for properly reading the image data and are unrelated to the width and height of the dialog image element.  To generalize, all image formats (JPG, BMP, PNG, et.) specifiy width and height in pixels, cause without it the image data makes no sense.   And this is exactly the reason the images you posted above are repeated and skewed, i.e., your code provided bad dimension parameters for reading the data.  Scaling has nothing to do with it. 

 

And Yes, the raw image data can be read and anaylized, and modified using the read() function. I've done this many times.  There are however limitations on size. Acrobat JavaScript is a lightweight programming environment. It's both slow and memory limited. You are not going to be able to use it to write a tool for manipulating large photos. 

 

 

Thom Parker - Software Developer at PDFScripting
Use the Acrobat JavaScript Reference early and often

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jan 13, 2025 Jan 13, 2025

Ah, I think I don't think I conveyed my meaning completely. Let me try again:

 

1a. I've tested the image with dimensions from 32x32 through 500x500. It has never rendered correctly and consistently. In order to do this quickly, I ran a for loop and incremented intH intW until I aborted it. If I don't define intH or intW, the script defaults to extracting it from the icon.

 

1b. I wanted to use the Height & Width of the icon provided by util.iconStreamFromIcon(...).height & width. That should allow the image to render correctly without requiring a hard-coded size. One very rare occasions, this renders correctly, but always fails after a few repeats.

 

1c. The original images are 339x339 pixels, while util.iconStreamFromIcon(...).height & width returns 254x254 for the transparent background images, but only 81x81 for the white background images. None of these height & width numbers render correctly. Additionally, testing on a different computer has given me completely different height/width values for the same images. 508x508 for transparent and 164x164 for white.

 

1d. I had hoped that the image element had a scaling function or that I could scale the image using Javascript to manipulate the pixels directly before feeding them into the image element. Therefore I set minimum and maximum sizes for the image element using math.max() and math.min() respsectively. 

 

1f. Even with an integer set as the image's width & height, the image renders incorrectly in different ways each time it is rendered. The 7 bad examples I've provided are the most common, but sometimes it's just green static. This is where I am most confused. If the image just has misaligned rows , I'd expect the "bad" render to be the same each time. I would also expect the colors to be correct. My best guess at this point is that Acrobat is taking my ARGB PNG and reading it as a RGB PNG, which is causing the skew and color shift.  This appears to be wrong, as I converted the image to RGB with GIMP and still had render errors.

 

I hope that more clearly explains what I've tried.

 

To get to the point: How do I render a dynamic icon correctly inside an image element in a custom dialog?

 

Side questions:

  1. Regarding your statements about "large photos", what are you defining as "large"?
  2. Is it possible to scale an image element in a custom dialog? 
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 13, 2025 Jan 13, 2025

Main point. Refer to #2 on my first answer. Don't recreate the icon stream, just use the one Acrobat gives you. 

 

replace this code:

 var DialogData = {
    "APDS": util.iconStreamFromIcon(getField("OMC.Stamp").buttonGetIcon(0),
    "Fnme": objOMManager.NameFirst,
    "Lnme": objOMManager.NameLast,
    "nmID": objOMManager.IDnum,
    "Mail": objOMManager.Email,
    "Unit": objOMCenter.Unit,
    "OSym": objOMCenter.OfficeSymbol,
    "Stat": objOMCenter.State,
    "OBox": objOMCenter.Email,
    "OMrq": blnOMMrequired
  };

 

Side Questions

1. I don't know. Never tested the limit. I've loaded 2kx2k images, which is way larger than any practicle use I've had for an image. 

2. No, button images scale, the dialog image object renders pixel for pixel.   

 

Thom Parker - Software Developer at PDFScripting
Use the Acrobat JavaScript Reference early and often

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 13, 2025 Jan 13, 2025
LATEST

About the varied results of width and height when converting icons on different systems. The buttonImportIcon function does not import images 1 to 1. It resizes them according to some methodology I do not understand. I can imagine that different versions might do it differently, but I think thats unlikely. You reported rather wild differences. Something else is going on. If you really want to test this you need to simplify your setup and only test the one thing. 

I've also never had an issue with setting the size of image data, when I got that size from the icon stream.  Which also makes me think something else is happening.  

 

I don't use buttons to import images that are used by the dialog, unless that is the only choice. Mostly I'm placing static images on dialogs, such as a logo. For this I convert the original image with a program I wrote for this specific purpose.  

But I've also made dialogs that create images dynamically.  

 

The point is that there is nothing wrong with Acrobat and the way it handles images in JavaScript. The problem is something in your code. 

 

 

Thom Parker - Software Developer at PDFScripting
Use the Acrobat JavaScript Reference early and often

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines