Dynamics Ax printing logo’s from batch


Hi,

Edit Microsoft has released a fix for this problem contact support for this

As all of you know the Image class in Dynamics Ax 4.0 and 2009 can only run on client. This poses a problem when you want to print for example invoices with your company logo on it. Having this found out I went to look for an alternative!

I’ve added this code to the top of the PDFViewer class in the writeBitmap(OutputBitmapField _field, OuputSection _section) method

if( isRunningOnServer() &&
    _field.name()       == #FieldLogo)
{
    this.BLOGWriteBitmapOnServer(_field,_section);
    super(_field, _section);
    return;
}
Code language: PHP (php)

For the method BLOGWriteBitmapOnServer(OutputBitmapField _field, OuputSection _section) I have copied everything from the writeBitmap and started by replacing the Image object with a System.Drawing.Image object, you can make a company parameter for this file path.

img = System.Drawing.Image::FromFile(imgStr);
Code language: PHP (php)

After compiling there are a few errors witch I’ve corrected and ended up with this code.

public void BLOGWriteBitmapOnServer(OutputBitmapField _field, OuputSection _section)
{
    #File
    BinData bin = new BinData();
    //Image img;
    container data, imgInfoData;
    str s;
    real x1,x2, y1,y2;
    Struct br;
    int imageObjectNo = 0;
    int newwidth, newheight;
    real pdfPreviewScale = 0.8;
    boolean generateXImage = false;
    container c;
    str fn;
    FileIOPermission writePermission;
    FileIOPermission readPermission;
    boolean grayScale = false;

    System.Drawing.Image    img;
    str                     imgStr;
    int                     widthTemp, heightTemp;
    CompanyInfo             companyInfo = companyInfo::find();
    ;

    new InteropPermission(InteropKind::ClrInterop).assert();

    imgStr  = companyInfo.BLOGCompanyLogoFile;

    br = this.boundingRectTwips(currentPage, _section, _field);

    x1 = br.value('x1'); y1 = br.value('y1');
    x2 = br.value('x2'); y2 = br.value('y2');

    if (_field.type() == 10) // resourceId, DB_RESId
    {
        //img = new Image(_field.resourceId());
        img = System.Drawing.Image::FromFile(imgStr);

        if (resourceIdImageMap.exists(_field.resourceId()))
            imageObjectNo = resourceIdImageMap.lookup(_field.resourceId());
        else
        {
            imageObjectNo = this.nextObjectNo();
            resourceIdImageMap.insert(_field.resourceId(), imageObjectNo);
            generateXImage = true;
        }

        if (debugLevel >= 1)
            info ('Image in resource ' + int2str(_field.resourceId()));
    }
    else if (_field.type() == 7) // queue
    {
        c = _field.value();
        if (c)
        {
            //img = new Image(c);
            img = System.Drawing.Image::FromFile(imgStr);

            imageObjectNo = this.nextObjectNo();
        }
        generateXImage = true;

        if (debugLevel >= 1)
        {
            if (img)
                info ('Image in container');
            else
                info ('No image in container');
        }
    }
    else // string containing filename
    {
        //img = new Image(_field.imageFileName());
        img = System.Drawing.Image::FromFile(imgStr);

        if (stringImageMap.exists(_field.imageFileName()))
            imageObjectNo = stringImageMap.lookup(_field.imageFileName());
        else
        {
            imageObjectNo = this.nextObjectNo();
            stringImageMap.insert(_field.imageFileName(), imageObjectNo);
            generateXImage = true;
        }

        if (debugLevel >= 1)
            info ('File is ' + _field.imageFileName());
    }

    if (img)
    {
        if (generateXImage)
        {
            fn  = System.IO.Path::GetTempFileName();

            widthTemp   = img.get_Width();
            heightTemp  = img.get_Height();

            img.Save(fn);

            // 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.
            //WinAPIServer::deleteFile(fn);

            CodeAccessPermission::revertAssert();
            new InteropPermission(InteropKind::ClrInterop).assert();
            System.IO.File::Delete(fn);

            if (bitmapEncode85)
                s = bin.ascii85Encode();
            else
                s = BinData::dataToString(data);

            objectOffsetMap.insert(imageObjectNo, binBuffer.size());

            this.appendTextToBuffer(int2str(imageObjectNo) + ' 0 obj '
Code language: PHP (php)

This could probably been done much cleaner, but it does the job. ๐Ÿ™‚

, ,

20 responses to “Dynamics Ax printing logo’s from batch”

  1. Kevin,
    This is REALLY nice thanks. I’m having sort of a weird problem with it though. I wrote a process that queues up the invoices that we’ve generated during that day and at night I go through and print a copy of the invoices to a PDF file and then email them out. The problem is that about 10% of the PDF’s are now corrupt. I’ve included an entire bad PDF file below and also the header of a good one. I’m attaching a b+w jpg (our logo) to the pdf. I tried a gif but AX didn’t seem to like that. Any ideas would be great. Thanks.

    Paul

    AX2009 SP1 RU4 + EE localization

    Bad:
    %PDF-1.3
    % Invoice 00477715 – Report
    % Generated by ppica on 2/9/2011 at 20:02:14
    5 0 obj <>
    1 0 obj <> endobj
    2 0 obj <>endobj
    3 0 obj <> endobj
    4 0 obj
    [/PDF /Text /ImageC]
    endobj
    xref
    0 6
    0000000000 65535 f
    0000000286 00000 n
    0000000350 00000 n
    0000000394 00000 n
    0000000450 00000 n
    0000000082 00000 n
    trailer <>
    startxref
    488
    %%EOF

    Good:
    %PDF-1.3
    % Invoice 00477717 – Report
    % Generated by ppica on 2/9/2011 at 20:02:16
    5 0 obj <>
    1 0 obj <> endobj
    2 0 obj <>endobj
    8 0 obj <<
    /Type/XObject /Subtype/Image /ColorSpace /DeviceRGB
    /Name /I8 /BitsPerComponent 8
    /Filter [/ASCII85Decode/DCTDecode] /DecodeParms[null<>]
    /Length 20511
    /Width 479 /Height 367
    >>
    stream

    (followed by the binary data for the jpg and everything else)

    • Hmm we are using a jpg file too, sometimes we get an error ‘Could not convert Glyphs’ but I don’t think its related. I should do some more testing with this code I don’t have any time now ๐Ÿ™‚ If you find a solution for your problem let me know ๐Ÿ™‚

  2. Unfortunately, my solution was pretty lame. Since this is a batch process that sends copies of the invoices out I simply check the file size on the PDF that’s generated and if it’s less that 5k I throw it out and try again until I get a good one and then move on.

    The only thing that seemed to help was putting in a small delay. It’s almost like things were happening too fast within AX and the OS couldn’t keep up with reading in the jpg and then reading in PDF to attach to the email. Adding the delays in there has gotten my success rate up to around 95% as opposed to 90%.

    Still a wonderful piece of code. ๐Ÿ™‚ I can’t thank you enough!

    Paul

  3. Nope, not multi-threaded. ๐Ÿ™ I usually don’t do that unless the job takes a huge amount of time. As it is, even with the delays I’ve added this job (which only runs once a day off hours) takes less than 2 minutes.

    Paul

  4. Meanwhile there exists a hotfix from Microsoft. Contact your Microsoft Support and you’ll get a solution.

  5. I know I’m too late to this party, but will thow in my 2 cents nevertheless…

    In my wanderings through GDI+, when doing a batch of image generation, I found marked performance improvement if I forcibly called for garbage collection between image instances.

    GC.Collect(); // I think was the code

    I’m fairly new to doing DAX programming. I can see “System.GC” in my X++ intellisense, but, it doesn’t allow me to compile the aforementioned line of code.

    • Hi,

      Thanks for your comment ๐Ÿ™‚
      Actually now that I come to think of it, all of the Image instances should be disposed along all node paths.
      *edit* this should call the garbage collector.

      By the way I’ve just downloaded the hotfix and this patches the server so this will probably be a much better solution.

      Regards,

      Kevin

  6. Thanks for the post but i don’t solve my problem.
    I’ve search the hotfix but i didn’t found it.
    can you tell me which hotfix is?
    i work with AX 4.0 and you?
    thank, bye

  7. Hi, Can anyone please guide me how can I re-size an image using the same method described above for image processing. Your response will be highly appreciated.

  8. Kevin,

    This was exactly what I was looking for and struggling with for a quite a while. You have saved me a lot of time and effort to get this to work. The hotfix works like a charm.

    Thanks,
    Bob

  9. Can you tell me what this means “companyInfo.BLOGCompanyLogoFile;” .
    I know its a field of table and its data type must be String but what data it should contain ?
    Can you please let me know immediately as i am facing this issue since long time and have not got any solution.

    • Hi Lalit,

      It’s the path to the image file, why don’t you use the Microsoft hotfix this is a way betters solution. You can find it on partnersource.

      Kind regards,

      Kevin

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.