//********************************************************************************
// Mozilla GBM plug-in: npgbm.cpp
//
// Requires: GBM.DLL version 1.35 or higher (with multipage support)
//
// Copyright (C) 2006-2008 Heiko Nitzsche
//
//   This software is provided 'as-is', without any express or implied
//   warranty.  In no event will the author be held liable for any damages
//   arising from the use of this software.
//
//   Permission is granted to anyone to use this software for any purpose,
//   including commercial applications, and to alter it and redistribute it
//   freely, subject to the following restrictions:
//
//   1. The origin of this software must not be misrepresented; you must not
//      claim that you wrote the original software. If you use this software
//      in a product, an acknowledgment in the product documentation would be
//      appreciated but is not required.
//   2. Altered source versions must be plainly marked as such, and must not be
//      misrepresented as being the original software.
//   3. This notice may not be removed or altered from any source distribution.
//
// Date    : 02-Feb-2008
// Version : 1.33
// Requires: GBM.DLL version 1.35 or higher (with multipage support)
//
//********************************************************************************
//
// History:
//
//   16-Jul-2006: Initial release
//   30-Jul-2006: Add FULL mode viewer with a load of features
//                * menus, popup menu, scrollers
//                * variable scaled preview
//                * transformations mirror/transpose/rotate
//                * multipage navigation
//                * save the shown page to any GBM supported bitmap file format
//                * uses GBM File Dialog if found, otherwise OS/2 File Dialog is used
//                * English and German localized with autodetection via LANG variable
//
//                Add EMBED mode viewer with reduced functionality of FULL mode viewer
//                * popup menu
//                * transformations mirror
//                * multipage navigation
//                * save the shown page to any GBM supported bitmap file format
//                * uses GBM File Dialog if found, otherwise OS/2 File Dialog is used
//                * printing of embedded bitmaps is supported on Mozilla/Firefox/SeaMonkey
//                * English and German localized with autodetection via LANG variable
//
//                Now supports dynamic lookup of GBM.DLL either via LIBPATH
//                or look into the directory where npgbm.dll is located.
//
//   16-Aug-2006: * Add bitmap information dialog box
//                * Fix for issue that GBM.DLL and GBMDLG.DLL are not found when
//                  LIBPATHSTRICT (or RUN!) is used. Thanks to Rich Walsh for the background
//                  info about the root cause.
//                * Add bldlevel info
//
//   21-Sep-2006: * Improved detection of bitmap file formats
//                * Enable support for XPM format
//
//   09-Sep-2007  * 8bpp grayscale and true color images are now scaled with
//                  a high quality algorithm to improve appearance.
//
//   19-Sep-2007  * Fixed resampling scaler rounding issues
//
//   27-Jan-2008  * Resampling scaler now supports all grayscale bitmaps <=8bpp
//
//   08-Feb-2008  * Move HPS rendering code to common package GbmRenderer
//                  to save memory (now renders directly from cache buffer)
//                  without temporary subrectangle buffer
//                * Allocate memory from high memory for bitmap data to
//                  stretch limit for out-of-memory errors
//                  (requires kernel with high memory support)
//********************************************************************************

#ifdef __IBMCPP__
  #pragma strings( readonly )
#endif

#include <malloc.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "os2defs.h"
#include "ConfigHandler.hpp"
#include "GbmAccessor.hpp"
#include "GbmDialogAccessor.hpp"
#include "GbmDocument.hpp"
#include "GbmBitmapViewer.hpp"
#include "gbm.h"

#ifndef _NPAPI_H_
#include "npapi.h"
#endif

//
// Instance state information about the plugin.
//

typedef struct _PluginInstance PluginInstance;
typedef struct _PluginInstance
{
    NPWindow        * fWindow;
    HWND              hWnd;
    uint16            fMode;
    ConfigHandler   * pConfigReader;
    GbmDocument     * pDocument;
    GbmBitmapViewer * pViewer;
    int               gbmTypeIndex;
    int               progress;
    int               progressMax;
    PluginInstance  * pNext;
} PluginInstance;

// ---------------------------------------------------------

typedef struct LOOKUP_TABLE_DEF
{
    const char * mimeType;
    const char * extension;
} LOOKUP_TABLE_ENTRY;

// Keep in sync with npgbm.rc mime type entries !!!

static struct LOOKUP_TABLE_DEF LOOKUP_TABLE [] =
{
    { "image/bmp"               , "BMP" },
    { "image/x-windows-bmp"     , "BMP" },
    { "image/gif"               , "GIF" },
    { "image/pcx"               , "PCX" },
    { "image/x-pcx"             , "PCX" },
    { "image/tiff"              , "TIF" },
    { "image/x-tiff"            , "TIF" },
    { "image/tga"               , "TGA" },
    { "image/iff"               , "IFF" },
    { "image/vid"               , "VID" },
    { "image/x-portable-bitmap" , "PBM" },
    { "image/x-portable-graymap", "PGM" },
    { "image/x-portable-greymap", "PGM" },
    { "image/x-portable-pixmap" , "PPM" },
    { "image/x-portable-anymap" , "PNM" },
    { "image/png"               , "PNG" },
    { "image/kps"               , "KPS" },
    { "image/x-xbm"             , "XBM" },
    { "image/xbm"               , "XBM" },
    { "image/x-xbitmap"         , "XBM" },
    { "image/xpm"               , "XPM" },
    { "image/x-xpm"             , "XPM" },
    { "image/x-xpixmap"         , "XPM" },
    { "image/spr"               , "SPR" },
    { "image/pseg"              , "PSE" },
    { "image/img"               , "IMG" },
    { "image/cvp"               , "CVP" },
    { "image/jpeg"              , "JPG" },
    { "image/pjpeg"             , "JPG" }
};

const int LOOKUP_TABLE_LENGTH = sizeof(LOOKUP_TABLE) /
                                sizeof(LOOKUP_TABLE[0]);

// ---------------------------------------------------------

typedef struct
{
    const char *            name;
    GbmRenderer::ScalerType type;
} SCALER;

static const SCALER SCALER_TABLE[] =
{
    { "simple"         , GbmRenderer::ScalerType_SIMPLE          },
    { "nearestneighbor", GbmRenderer::ScalerType_NEARESTNEIGHBOR },
    { "bilinear"       , GbmRenderer::ScalerType_BILINEAR        },
    { "bell"           , GbmRenderer::ScalerType_BELL            },
    { "bspline"        , GbmRenderer::ScalerType_BSPLINE         },
    { "mitchell"       , GbmRenderer::ScalerType_MITCHELL        },
    { "lanczos"        , GbmRenderer::ScalerType_LANCZOS         }
};
static const int SCALER_TABLE_LENGTH = sizeof(SCALER_TABLE)/sizeof(SCALER_TABLE[0]);

// ---------------------------------------------------------

static HMODULE             DLLInstance     = 0;
static GbmAccessor       * pGbmAccessor    = NULL;
static GbmDialogAccessor * pGbmDlgAccessor = NULL;
static char fullCfgPath[_MAX_PATH+1]       = { 0 };

// ---------------------------------------------------------

// Compiler independent library initialize function

static unsigned libInitialize(unsigned termination)
{
    if ( termination )
    {
        delete pGbmDlgAccessor;
        pGbmDlgAccessor = NULL;

        delete pGbmAccessor;
        pGbmAccessor = NULL;

        memset(fullCfgPath, 0, sizeof(fullCfgPath));
    }
    else
    {
        /* DLL is attaching to process */

        char fullDllName[_MAX_PATH+1] = { 0 };
        char drive[_MAX_DRIVE+1]      = { 0 };
        char dir[_MAX_DIR+1]          = { 0 };

        if (DosQueryModuleName(DLLInstance, sizeof(fullDllName)-1, fullDllName) != 0)
        {
            return 0;
        }
        _splitpath(fullDllName, drive, dir, NULL, NULL);
        _makepath (fullCfgPath, drive, dir, "NPGBM.CFG", NULL);

        if (pGbmDlgAccessor == NULL)
        {
            try
            {
                // try to load GBMDLG.DLL via LIBPATH lookup
                pGbmDlgAccessor = new GbmDialogAccessor();
            }
            catch(...)
            {
            }
            // it is OK if the GbmDialogAccessor cannot be created as it is optional
        }

        if (pGbmAccessor == NULL)
        {
            if (pGbmDlgAccessor == NULL)
            {
                // GBMDLG.DLL was not found. Either it is not installed
                // or, as it depends on GBM.DLL, GBM.DLL is not in LIBPATH.
                // In these cases try to lookup GBM.DLL first from the plugin directory.

                try
                {
                    // try to load via absolute path of NPGBM.DLL (plugin DLL)

                    _makepath(fullDllName, drive, dir, "GBM.DLL", NULL);

                    pGbmAccessor = new GbmAccessor(fullDllName);
                }
                catch(...)
                {
                }
            }

            if (pGbmAccessor == NULL)
            {
                try
                {
                    // try to load via LIBPATH lookup
                    pGbmAccessor = new GbmAccessor();
                }
                catch(...)
                {
                }
                if (pGbmAccessor == NULL)
                {
                    delete pGbmDlgAccessor;
                    pGbmDlgAccessor = NULL;
                    return 0;
                }
            }
        }

    }
    return 1;
}

// ---------------------------------------------------------

//
// This plugin will always be linked statically.
// Thus only the DLL init/term for this mode is implemented.
//

// ---------------------------------------------------------

#ifdef __IBMCPP__

/* _CRT_init is the C run-time environment initialization function.         */
/* It will return 0 to indicate success and -1 to indicate failure.         */
extern "C" int _CRT_init(void);

/* _CRT_term is the C run-time environment termination function.            */
/* It only needs to be called when the C run-time functions are statically  */
/* linked.                                                                  */
extern "C" void _CRT_term(void);

/* __ctordtorInit is the C++ run-time environment initialization function.  */
/* It is required to initialize static objects and destructors              */
extern "C" void __ctordtorInit(void);

/* __ctordtorTerm is the C++ run-time environment termination function.  */
/* It is required to destroy static objects and destructors              */
extern "C" void __ctordtorTerm(void);

/* _DLL_InitTerm() - called by the loader for DLL initialization/termination  */
/* This function must return a non-zero value if successful and a zero value  */
/* if unsuccessful.                                                           */

extern "C" unsigned long _System _DLL_InitTerm( unsigned long hModule, unsigned long termination )
{
   DLLInstance = (HMODULE) hModule;

   switch(termination)
   {
       case 0:
          if (_CRT_init() == -1)
          {
              return 0;
          }
          __ctordtorInit();

          // init our DLL
          if (libInitialize(0) == 0)
          {
              return 0;
          }
          break;

      case 1:
          // terminate our DLL
          if (libInitialize(1) == 0)
          {
              return 0;
          }
          __ctordtorTerm();
          _CRT_term();
          break;

      default:
          return 0;
   }

   /* Indicate success.  Non-zero means success!!! */
   return 1;
}

#endif // ifdef __IBMCPP__

// -------------------------------------------------------
// -------------------------------------------------------

#ifdef __WATCOM_CPLUSPLUS__

extern "C" unsigned _System LibMain( unsigned hmod, unsigned termination )
{
    DLLInstance = (HMODULE) hmod;

    if ( termination )
    {
        // terminate our DLL
        if (libInitialize(1) == 0)
        {
            return 0;
        }
    }
    else
    {
        /* DLL is attaching to process */

        // init our DLL
        if (libInitialize(0) == 0)
        {
            return 0;
        }
    }
    return( 1 );
}
#endif // ifdef __WATCOM_CPLUSPLUS__

// -------------------------------------------------------
// -------------------------------------------------------

// A plugin instance typically will subclass the plugin's client window, so
// it can get Windows messages, (such as paint, palettechanged, keybd, etc).
// To do work associated with a specific plugin instance the WndProc which
// receives the Windows messages, (named "SubClassFunc" herein), needs access
// to the "This" (PluginInstance*) ptr.

//* OS2 bugbug -- we need to reserve 4 bytes of window words to correct this
//  gorp.....

// When Navigator registers the plugin client's window class, (the class for
// the window passed in NPP_SetWindow()), Navigator does not reserve any
// "extra" windows bytes.  If it had, the plugin could simply have stored its
// "This" (PluginInstance*) ptr in the extra bytes.  But Nav did not, and the
// plugin cannot, so a different technique must be used.  The technique used
// is to keep a linked list of PluginInstance structures, and walk the list
// to find which one is associated with the window handle.  Inefficient,
// grungy, complicates the code, etc.  C'est la vie ...

PluginInstance* g_pHeadInstanceList = 0;

// Associate the hWnd with pInstance by setting the hWnd member of the
// PluginInstance struct.  Also, add the PluginInstance struct to the list
// if necessary
static void AssociateInstance(HWND hWnd, PluginInstance* pInstance)
{
    pInstance->hWnd = hWnd;     // redundant, but usefull to get hwnd from
                                // pinstance later.
    BOOL rc = WinSetWindowULong(hWnd, QWL_USER, (ULONG)pInstance);
}

// Find the PluginInstance associated with this hWnd and return it
#if 0
static PluginInstance* GetInstance(HWND hWnd)
{
    return (PluginInstance*)WinQueryWindowULong(hWnd, QWL_USER);
}
#endif

//----------------------------------------------------------------------------
// NPP_Initialize:
//----------------------------------------------------------------------------
NPError NPP_Initialize(void)
{
    return NPERR_NO_ERROR;
}

//----------------------------------------------------------------------------
// NPP_Shutdown:
//----------------------------------------------------------------------------
void NPP_Shutdown(void)
{
}

//----------------------------------------------------------------------------
// NPP_New:
//----------------------------------------------------------------------------
NPError NP_LOADDS NPP_New(NPMIMEType    pluginType,
                          NPP           instance,
                          uint16        mode,
                          int16         argc,
                          char        * argn[],
                          char        * argv[],
                          NPSavedData * saved)
{
    if (instance == NULL)
    {
        return NPERR_INVALID_INSTANCE_ERROR;
    }

    // lookup the file extension valid for the mime type
    int gbmTypeIndex = -1;

    if (pluginType != NULL)
    {
        for (int index = 0; index < LOOKUP_TABLE_LENGTH; index++)
        {
            if (strcmp(LOOKUP_TABLE[index].mimeType, pluginType) == 0)
            {
                gbmTypeIndex = index;
                break;
            }
        }
    }

    // don't try to catch an unknown type even though it might work in some cases
    if (gbmTypeIndex == -1)
    {
        return NPERR_INVALID_INSTANCE_ERROR;
    }

    instance->pdata = NPN_MemAlloc(sizeof(PluginInstance));
    PluginInstance* This = (PluginInstance*) instance->pdata;

    if (This == NULL)
    {
        return NPERR_OUT_OF_MEMORY_ERROR;
    }

    //
    // *Developers*: Initialize fields of your plugin
    // instance data here.  If the NPSavedData is non-
    // NULL, you can use that data (returned by you from
    // NPP_Destroy to set up the new plugin instance.
    //

    This->fWindow      = 0;
    // mode is NP_EMBED, NP_FULL, or NP_BACKGROUND (see npapi.h)
    This->fMode        = mode;
    This->hWnd         = 0;
    This->pNext        = NULL;

    This->gbmTypeIndex = gbmTypeIndex;
    This->progress     = 0;
    This->progressMax  = 0;

    This->pDocument    = NULL;
    This->pViewer      = NULL;

    try
    {
        // create the document handler
        This->pDocument = new GbmDocument(*pGbmAccessor);
        if (This->pDocument == NULL)
        {
            return NPERR_OUT_OF_MEMORY_ERROR;
        }

        // create the window of the mini viewer (client of the provided root window)
        This->pViewer = new GbmBitmapViewer(DLLInstance, *(This->pDocument), pGbmDlgAccessor);
        if (This->pViewer == NULL)
        {
            delete This->pDocument;
            This->pDocument = NULL;
            return NPERR_OUT_OF_MEMORY_ERROR;
        }

        // create the config file handler
        This->pConfigReader = new ConfigHandler();
        if (This->pConfigReader == NULL)
        {
            delete This->pDocument;
            This->pDocument = NULL;
            return NPERR_OUT_OF_MEMORY_ERROR;
        }

        // read the config file if it exists and configure the viewer
        if (This->pConfigReader->readFromFile(fullCfgPath))
        {
            const char * scalerName = This->pConfigReader->getValueOfKey("scaler");
            if (scalerName != NULL)
            {
                for (int i = 0; i < SCALER_TABLE_LENGTH; i++)
                {
                    if (strcmp(SCALER_TABLE[i].name, scalerName) == 0)
                    {
                        This->pViewer->setScalerType(SCALER_TABLE[i].type);
                        break;
                    }
                }
            }
        }
    }
    catch(...)
    {
        delete This->pViewer;
        This->pViewer = NULL;

        delete This->pDocument;
        This->pDocument = NULL;

        delete This->pConfigReader;
        This->pConfigReader = NULL;

        return NPERR_OUT_OF_MEMORY_ERROR;
    }

    return NPERR_NO_ERROR;
}


//-----------------------------------------------------------------------------
// NPP_Destroy:
//-----------------------------------------------------------------------------
NPError NP_LOADDS NPP_Destroy(NPP instance, NPSavedData** save)
{
    if (instance == 0)
    {
        return NPERR_INVALID_INSTANCE_ERROR;
    }

    PluginInstance* This = (PluginInstance*) instance->pdata;

    //
    // *Developers*: If desired, call NP_MemAlloc to create a
    // NPSavedDate structure containing any state information
    // that you want restored if this plugin instance is later
    // recreated.
    //

    if (This != NULL)
    {
        // cleanup

        delete This->pViewer;
        This->pViewer = NULL;

        delete This->pDocument;
        This->pDocument = NULL;

        delete This->pConfigReader;
        This->pConfigReader = NULL;

        This->gbmTypeIndex = -1;
        This->progress     = 0;
        This->progressMax  = 0;

        NPN_MemFree(instance->pdata);
        instance->pdata = 0;
    }

    return NPERR_NO_ERROR;
}


//----------------------------------------------------------------------------
// NPP_SetWindow:
//----------------------------------------------------------------------------
NPError NP_LOADDS NPP_SetWindow(NPP instance, NPWindow* window)
{
    if (instance == 0)
    {
        return NPERR_INVALID_INSTANCE_ERROR;
    }

    PluginInstance* This = (PluginInstance*) instance->pdata;

    //
    // *Developers*: Before setting fWindow to point to the
    // new window, you may wish to compare the new window
    // info to the previous window (if any) to note window
    // size changes, etc.
    //
    if((window->window != 0) && (This->hWnd == 0))
    {
        This->fWindow = window;
        This->hWnd    = (HWND)This->fWindow->window;

        // set the viewers parent window
        This->pViewer->setParentWindow(This->hWnd, (This->fMode == NP_FULL) ? TRUE : FALSE);
        AssociateInstance(This->hWnd, This);
    }
    else
    {
        // if window handle changed
        if(This->hWnd != (HWND)window->window)
        {
            // remember the new window
            This->fWindow = window;

            // remember the new window handle
            This->hWnd = (HWND)This->fWindow->window;

            if(This->hWnd != 0)
            {
                // set the viewers parent window
                This->pViewer->setParentWindow(This->hWnd, (This->fMode == NP_FULL) ? TRUE : FALSE);
                AssociateInstance(This->hWnd, This);
            }
        }
    }

    if (This->hWnd)
    {
        SWP swp;

        const HWND hwndFrame = This->pViewer->getFrameHwnd();

        WinQueryWindowPos(This->hWnd, &swp);
        WinSetWindowPos(hwndFrame, 0, 0, 0, swp.cx, swp.cy, SWP_MOVE | SWP_SIZE);
        WinInvalidateRect(This->hWnd, 0, TRUE);
        WinSetFocus(HWND_DESKTOP, hwndFrame);
    }

    return NPERR_NO_ERROR;
}


//----------------------------------------------------------------------------
// NPP_NewStream:
//----------------------------------------------------------------------------
NPError NP_LOADDS NPP_NewStream(NPP          instance,
                                NPMIMEType   type,
                                NPStream   * stream,
                                NPBool       seekable,
                                uint16     * stype)
{
    if (instance == 0)
    {
        return NPERR_INVALID_INSTANCE_ERROR;
    }

    PluginInstance* This = (PluginInstance*) instance->pdata;

    // ask for the whole file
    // (we might add streaming later by writing our own GBM I/O functions)
    // So far we use the streaming calls to display the loading progress.
    *stype = NP_ASFILE;

    This->progress    = 0;
    This->progressMax = stream->end;

    This->pViewer->setLoadProgress(This->progress, This->progressMax);

    return NPERR_NO_ERROR;
}


//
// *Developers*:
// These next 2 functions are directly relevant in a plug-in which handles the
// data in a streaming manner.  If you want zero bytes because no buffer space
// is YET available, return 0.  As long as the stream has not been written
// to the plugin, Navigator will continue trying to send bytes.  If the plugin
// doesn't want them, just return some large number from NPP_WriteReady(), and
// ignore them in NPP_Write().  For a NP_ASFILE stream, they are still called
// but can safely be ignored using this strategy.
//

int32 STREAMBUFSIZE = 0X0FFFFFFF;   // If we are reading from a file in
                                    // NP_ASFILE mode, we can take any size
                                    // stream in our write call (since we
                                    // ignore it)

//----------------------------------------------------------------------------
// NPP_WriteReady:
//----------------------------------------------------------------------------
int32 NP_LOADDS NPP_WriteReady(NPP instance, NPStream *stream)
{
#if 0
    if (instance != 0)
    {
        PluginInstance* This = (PluginInstance*) instance->pdata;
    }
#endif

    return STREAMBUFSIZE;   // Number of bytes ready to accept in NPP_Write()
}


//----------------------------------------------------------------------------
// NPP_Write:
//----------------------------------------------------------------------------
int32 NP_LOADDS NPP_Write(NPP instance, NPStream *stream,
                          int32 offset, int32     len   , void *buffer)
{
    if (instance != 0)
    {
        PluginInstance* This = (PluginInstance*) instance->pdata;

        This->progress += len;

        This->pViewer->setLoadProgress(This->progress, This->progressMax);
    }

    return len;     // The number of bytes accepted.  Return a
                    // negative number here if, e.g., there was an error
                    // during plugin operation and you want to abort the
                    // stream
}


//----------------------------------------------------------------------------
// NPP_DestroyStream:
//----------------------------------------------------------------------------
NPError NP_LOADDS NPP_DestroyStream(NPP instance, NPStream *stream, NPError reason)
{
    if (instance == 0)
    {
        return NPERR_INVALID_INSTANCE_ERROR;
    }
#if 0
    PluginInstance* This = (PluginInstance*) instance->pdata;
#endif
    return NPERR_NO_ERROR;
}


//----------------------------------------------------------------------------
// NPP_StreamAsFile:
//----------------------------------------------------------------------------
void NP_LOADDS NPP_StreamAsFile(NPP instance, NPStream *stream, const char* fname)
{
    if (instance == 0)
    {
        return;
    }

    PluginInstance* This = (PluginInstance*) instance->pdata;

    try
    {
        if (This->gbmTypeIndex == -1)
        {
            // set bitmap for use in drawing and printing (lookup format via filename)
            This->pDocument->setDocumentFile(fname, NULL,
                                                    FALSE /* force immediate loading of page data */,
                                                    FALSE /* allow also non 24bpp */);
        }
        else
        {
            try
            {
                // set bitmap for use in drawing and printing (lookup format via provided extension)
                This->pDocument->setDocumentFile(fname, LOOKUP_TABLE[This->gbmTypeIndex].extension,
                                                        FALSE /* force immediate loading of page data */,
                                                        FALSE /* allow also non 24bpp */);
            }
            catch(GbmException & ex)
            {
                // fallback to dynamic analysis

                // set bitmap for use in drawing and printing (lookup format via filename)
                This->pDocument->setDocumentFile(fname, NULL,
                                                        FALSE /* force immediate loading of page data */,
                                                        FALSE /* allow also non 24bpp */);
            }
        }
    }
    catch(...)
    {
    }

    // invalidate window to ensure a redraw
    WinInvalidateRect(This->hWnd, 0, TRUE);
}


//----------------------------------------------------------------------------
// NPP_Print:
//----------------------------------------------------------------------------
void NP_LOADDS NPP_Print(NPP instance, NPPrint* printInfo)
{
    if (printInfo == 0)   // trap invalid parm
    {
        return;
    }

    if (instance != 0)
    {
        PluginInstance* This = (PluginInstance*) instance->pdata;

        if (printInfo->mode == NP_FULL)
        {
            //
            // *Developers*: If your plugin would like to take over
            // printing completely when it is in full-screen mode,
            // set printInfo->pluginPrinted to TRUE and print your
            // plugin as you see fit.  If your plugin wants Netscape
            // to handle printing in this case, set printInfo->pluginPrinted
            // to FALSE (the default) and do nothing.  If you do want
            // to handle printing yourself, printOne is true if the
            // print button (as opposed to the print menu) was clicked.
            // On the Macintosh, platformPrint is a THPrint; on Windows,
            // platformPrint is a structure (defined in npapi.h) containing
            // the printer name, port, etc.
            //
            void* platformPrint = printInfo->print.fullPrint.platformPrint;
            NPBool printOne = printInfo->print.fullPrint.printOne;

            printInfo->print.fullPrint.pluginPrinted = FALSE; // Do the default
        }
        else    // If not fullscreen, we must be embedded
        {
            //
            // *Developers*: If your plugin is embedded, or is full-screen
            // but you returned false in pluginPrinted above, NPP_Print
            // will be called with mode == NP_EMBED.  The NPWindow
            // in the printInfo gives the location and dimensions of
            // the embedded plugin on the printed page.  On the Macintosh,
            // platformPrint is the printer port; on Windows, platformPrint
            // is the handle to the printing device context.
            //
            NPWindow* printWindow = &(printInfo->print.embedPrint.window);
            void* platformPrint = printInfo->print.embedPrint.platformPrint;

            // Heiko: The original SDK code did not work and moved the elements totally
            //        outside the visible area. Thus it is changed (and simplified).
            //        Though there seems to be an incompatibility between Mozilla
            //        old Netscape 4.61. The draw position for Netscape is different
            //        than in Mozilla. This print code is written for Mozilla based browsers.

            // get Presentation Space and save it
            HPS  hps    = (HPS)platformPrint;
            LONG saveID = GpiSavePS(hps);

            /* draw using common drawing routine */

            // Netscape and Mozilla use different coordinate systems and
            // thus we have to use different placement algorithms.

            int plugin_major  , plugin_minor;
            int netscape_major, netscape_minor;

            NPN_Version(&plugin_major, &plugin_minor, &netscape_major, &netscape_minor);

            // Mozilla based browser?
            if (netscape_minor > 11 /* New browsers report 16, old Netscape 4.61 reports 11 */)
            {
                SIZEL siz = { 0,0 };
                GpiQueryPS(hps, &siz);

                // calc target rectangle
                RECTL printRect;
                printRect.xLeft   = printWindow->x;
                printRect.yBottom = siz.cy - (printWindow->y + printWindow->height);
                printRect.xRight  = printRect.xLeft   + printWindow->width;
                printRect.yTop    = printRect.yBottom + printWindow->height;

                // get the original bitmap size of the viewer */
                const double cxDraw = printRect.xRight - printRect.xLeft;
                const double cyDraw = printRect.yTop   - printRect.yBottom;

                const int pageWidth  = This->pViewer->getPageWidth();
                const int pageHeight = This->pViewer->getPageHeight();

                const double scaleX = (pageWidth  > 0) ? cxDraw/pageWidth  : 1.0;
                const double scaleY = (pageHeight > 0) ? cyDraw/pageHeight : 1.0;

                // draw full visible window
                This->pViewer->draw(hps, &printRect, scaleX, scaleY);
            }
#if 0 // Not yet finished!!!
            else
            {
                // Older Netscape (2.x to 4.x) seem to have a different coordinate system.
                // The printWindow is obviously in TWIPS rather than PELS (Mozilla) which
                // makes it difficult to output our bitmap.

                // Open issue: How can the printWindow TWIPS coordinates be converted to PELS?

                SIZEL siz = { 0,0 };
                GpiQueryPS(hps, &siz);
                //GpiQueryPageViewport

                // calc target rectangle
                RECTL printRect;
                printRect.xLeft   = printWindow->x;
                printRect.yBottom = printWindow->y;
                printRect.xRight  = printRect.xLeft   + printWindow->width;
                printRect.yTop    = printRect.yBottom + printWindow->height;

                GpiConvert(hps, CVTC_MODEL, CVTC_WORLD, 2, (PPOINTL)&printRect);

                SIZEL sizlsize = { 0, 0 };
                GpiSetPS(hps, &sizlsize, PU_PELS | GPIF_DEFAULT);

                // get the original bitmap size of the viewer */
                const double cxDraw = printRect.xRight - printRect.xLeft;
                const double cyDraw = printRect.yTop   - printRect.yBottom;

                const int pageWidth  = This->pViewer->getPageWidth();
                const int pageHeight = This->pViewer->getPageHeight();

                const double scaleX = (pageWidth  > 0) ? cxDraw/pageWidth  : 1.0;
                const double scaleY = (pageHeight > 0) ? cyDraw/pageHeight : 1.0;

                // draw full visible window
                This->pViewer->draw(hps, &printRect, scaleX, scaleY);
            }
#endif
            GpiRestorePS(hps, saveID);
        }
    }
}


//----------------------------------------------------------------------------
// NPP_HandleEvent:
// Mac-only.
//----------------------------------------------------------------------------
int16 NP_LOADDS NPP_HandleEvent(NPP instance, void* event)
{
    NPBool eventHandled = FALSE;
    if (instance == 0)
    {
        return eventHandled;
    }

    PluginInstance* This = (PluginInstance*) instance->pdata;

    //
    // *Developers*: The "event" passed in is a Macintosh
    // EventRecord*.  The event.what field can be any of the
    // normal Mac event types, or one of the following additional
    // types defined in npapi.h: getFocusEvent, loseFocusEvent,
    // adjustCursorEvent.  The focus events inform your plugin
    // that it will become, or is no longer, the recepient of
    // key events.  If your plugin doesn't want to receive key
    // events, return false when passed at getFocusEvent.  The
    // adjustCursorEvent is passed repeatedly when the mouse is
    // over your plugin; if your plugin doesn't want to set the
    // cursor, return false.  Handle the standard Mac events as
    // normal.  The return value for all standard events is currently
    // ignored except for the key event: for key events, only return
    // true if your plugin has handled that particular key event.
    //

    return eventHandled;
}


