Tuesday, May 20, 2014

AX 2009 - Reduce file size of PDF generated from report

AX 4.0 and 2009 doesn't have a good PDF generation engine. The problems are basically 2:

  1. Duplicated images
  2. Fonts included on the PDF

Duplicated images

If you include an image on the report and this image is repeated across pages, the image will be included N times (depending on the number of the pages)

Company logos are usually included in every report, and that will cause a drastic increase on the file size of the generated PDF.

To solve this problem, some changes has to be made to the class PDFViewer.

Add this method to the class:


 int FindOrAddToCache(str data, int imageObjectNo)  
 {  
   int hashCode;  
   System.String netStr = data;  
   ;  
   hashCode = netStr.GetHashCode();  
     
   if (resourceIdImageMap.exists(hashCode))  
     imageObjectNo = resourceIdImageMap.lookup(hashCode);  
   else  
   {  
     resourceIdImageMap.insert(hashCode, imageObjectNo);  
   }  
   return imageObjectNo;  
 }  

That method will build an hash out of the image, and thus will be possible to reuse the latter without increasing the file size.

To do that the following modification will be needed on the method writeBitmap (I will not include all the method since it's too long. So, declare an int variable named newImageObjectNo, add the following code starting approximately at line 140 and close the else bracket at the end) 



       // revert previous assertion  
       CodeAccessPermission::revertAssert();  
       // assert read permissions  
       readPermission = new FileIOPermission(fn, #io_read);  
       readPermission.assert();  
       // BP deviation documented (note that the file io assert IS included above)  
       bin.loadFile(fn);  
       data = bin.getData();  
       // Get rid of the temporary file.  
       WinAPI::deleteFile(fn);  
       if (bitmapEncode85)  
         s = bin.ascii85Encode();  
       else  
         s = BinData::dataToString(data);  
       // Begin modify  
       newImageObjectNo = this.ABM_FindOrAddToCache(s, imageObjectNo);  
       if(newImageObjectNo != imageObjectNo)  
       {  
         imageObjectNo = newImageObjectNo;  
         nextObjectNumber -= 1;  
       }  
       else  
       {  
       // End modify  

That would do the trick for the duplicated images!

Fonts included on the PDF

AX lacks the possibility to only include a subset of the characters used in the report.

This cause the whole font to be included in the generated PDF, causing a huge increase of the file size.

To solve this problem you can avoid to embed fonts on generated PDF (but who does not have the font installed on his system will not be able to view it)

Or you can use one of these fonts (please note that if you use those on a report, the characters are case sensitive):

Courier
Courier-Bold
Courier-Oblique
Courier-BoldOblique
Helvetica
Helvetica-Bold
Helvetica-Oblique
Helvetica-BoldOblique
Times
Times-Roman
Times-Bold
Times-Italic
Times-BoldItalic
ZapfDingbats

Those fonts are default fonts that should be readed in every PDF reader, even if someone does not have the font installed on the system.

When you consider to use a predefined font then you have to do the following code change in the endReport method of PDFViewer class:
...
// Delete the fonts stored in the fontdescrs map
mapEnumerator = fontDescrs.getEnumerator();
while (mapEnumerator.moveNext())
{
    font = mapEnumerator.currentValue();
//->Begin
    if(font.isTrueType())
//<-End
        font.deleteFont();
}

The above piece of code is taken from here

Please note that if you plan to use one of the default fonts, you can only use ASCII characters on your reports! This should be usually enough, but there are some character like "€" that are not available.

Some unsafe characters are:

€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ

You can force the PDF generation engine to substitute those characters with something else, like € with EUR, if you change the method \Classes\PDFViewer\writeStringField like that:


   fn = this.getFontName(currentPage, _section, _field);  
   if (fn != fontNameLast)  
   {  
     fontNameLast = fn;  
     fontIdLast = this.addFont(fn);  
     fontDescrLast = fontDescrs.lookup(fontIdLast);  
   }  
   fontId = fontIdLast;  
   fontDescr = fontDescrLast;  
   fontsOnPage.add(fontId);  
   // Begin modify  
   if(this.isPredefinedFont(fn))  
     _fieldValue = strReplace(_fieldValue, "€", "EUR");  
   // End modify  


Following these tricks the generated PDF file should be viewed on windows, mac, linux and android, and without images the file size will be less than 10Kb

No comments: