/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"

#include "psputil.hxx"

#include "printergfx.hxx"
#include "vcl/strhelper.hxx"

namespace psp {

const sal_uInt32 nLineLength = 80;
const sal_uInt32 nBufferSize = 16384;

/*
 * 
 * Bitmap compression / Hex encoding / Ascii85 Encoding
 *
 */

PrinterBmp::~PrinterBmp ()
{ /* dont need this, but C50 does */ }

/* virtual base class */

class ByteEncoder 
{
private:

public:

    virtual void    EncodeByte (sal_uInt8 nByte) = 0;
    virtual         ~ByteEncoder () = 0;
};

ByteEncoder::~ByteEncoder ()
{ /* dont need this, but the C50 does */ }

/* HexEncoder */

class HexEncoder : public ByteEncoder
{
private:

    osl::File*      mpFile;
    sal_uInt32      mnColumn;
    sal_uInt32      mnOffset;
    sal_Char        mpFileBuffer[nBufferSize + 16];

                    HexEncoder (); /* dont use */

public:

    HexEncoder (osl::File* pFile);
    virtual         ~HexEncoder ();
    void            WriteAscii (sal_uInt8 nByte);
    virtual void    EncodeByte (sal_uInt8 nByte);
    void            FlushLine ();
};

HexEncoder::HexEncoder (osl::File* pFile) :
        mpFile (pFile),
        mnColumn (0),
        mnOffset (0)
{}

HexEncoder::~HexEncoder ()
{
    FlushLine ();
    if (mnColumn > 0)
        WritePS (mpFile, "\n");
}

void 
HexEncoder::WriteAscii (sal_uInt8 nByte)
{
    sal_uInt32 nOff = psp::getHexValueOf (nByte, mpFileBuffer + mnOffset);
    mnColumn += nOff;
    mnOffset += nOff;

    if (mnColumn >= nLineLength)
    {
        mnOffset += psp::appendStr ("\n", mpFileBuffer + mnOffset);
        mnColumn = 0;
    }
    if (mnOffset >= nBufferSize)
        FlushLine ();
}

void 
HexEncoder::EncodeByte (sal_uInt8 nByte)
{
    WriteAscii (nByte); 
}

void
HexEncoder::FlushLine ()
{
    if (mnOffset > 0)
    {
        WritePS (mpFile, mpFileBuffer, mnOffset);
        mnOffset = 0;
    }
}

/* Ascii85 encoder, is abi compatible with HexEncoder but writes a ~> to 
   indicate end of data EOD */

class Ascii85Encoder : public ByteEncoder
{
private:

    osl::File*      mpFile;
    sal_uInt32      mnByte;
    sal_uInt8       mpByteBuffer[4];

    sal_uInt32      mnColumn;
    sal_uInt32      mnOffset;
    sal_Char        mpFileBuffer[nBufferSize + 16];

    Ascii85Encoder (); /* dont use */

    inline void     PutByte (sal_uInt8 nByte);
    inline void     PutEOD ();
    void            ConvertToAscii85 ();
    void            FlushLine ();

public:

    Ascii85Encoder (osl::File* pFile);
    virtual         ~Ascii85Encoder ();
    virtual void    EncodeByte (sal_uInt8 nByte);
    void            WriteAscii (sal_uInt8 nByte);
};

Ascii85Encoder::Ascii85Encoder (osl::File* pFile) :
        mpFile (pFile),
        mnByte (0),
        mnColumn (0),
        mnOffset (0)
{}

inline void
Ascii85Encoder::PutByte (sal_uInt8 nByte)
{
    mpByteBuffer [mnByte++] = nByte;
}

inline void
Ascii85Encoder::PutEOD ()
{
    WritePS (mpFile, "~>\n");
}

void
Ascii85Encoder::ConvertToAscii85 ()
{
    if (mnByte < 4)
        std::memset (mpByteBuffer + mnByte, 0, (4 - mnByte) * sizeof(sal_uInt8));

    sal_uInt32 nByteValue =   mpByteBuffer[0] * 256 * 256 * 256
        + mpByteBuffer[1] * 256 * 256
        + mpByteBuffer[2] * 256
        + mpByteBuffer[3];

    if (nByteValue == 0 && mnByte == 4)
    {
        /* special case of 4 Bytes in row */
        mpFileBuffer [mnOffset] = 'z';

        mnOffset += 1;
        mnColumn += 1;
    }
    else
    {
        /* real ascii85 encoding */
        mpFileBuffer [mnOffset + 4] = (nByteValue % 85) + 33;
        nByteValue /= 85;
        mpFileBuffer [mnOffset + 3] = (nByteValue % 85) + 33;
        nByteValue /= 85;
        mpFileBuffer [mnOffset + 2] = (nByteValue % 85) + 33;
        nByteValue /= 85;
        mpFileBuffer [mnOffset + 1] = (nByteValue % 85) + 33;
        nByteValue /= 85;
        mpFileBuffer [mnOffset + 0] = (nByteValue % 85) + 33;

        mnColumn += (mnByte + 1);
        mnOffset += (mnByte + 1);

        /* insert a newline if necessary */
        if (mnColumn > nLineLength)
        {
            sal_uInt32 nEolOff = mnColumn - nLineLength;
            sal_uInt32 nBufOff = mnOffset - nEolOff;
            
            std::memmove (mpFileBuffer + nBufOff + 1, mpFileBuffer + nBufOff, nEolOff);
            mpFileBuffer[ nBufOff ] = '\n';

            mnOffset++;
            mnColumn = nEolOff;
        }
    }

    mnByte = 0;
}

void
Ascii85Encoder::WriteAscii (sal_uInt8 nByte)
{
    PutByte (nByte);
    if (mnByte == 4)
        ConvertToAscii85 ();

    if (mnColumn >= nLineLength)
    {
        mnOffset += psp::appendStr ("\n", mpFileBuffer + mnOffset);
        mnColumn = 0;
    }
    if (mnOffset >= nBufferSize)
        FlushLine ();
}

void
Ascii85Encoder::EncodeByte (sal_uInt8 nByte)
{
    WriteAscii (nByte);
}

void
Ascii85Encoder::FlushLine ()
{
    if (mnOffset > 0)
    {
        WritePS (mpFile, mpFileBuffer, mnOffset);
        mnOffset = 0;
    }
}

Ascii85Encoder::~Ascii85Encoder ()
{
    if (mnByte > 0)
        ConvertToAscii85 ();
    if (mnOffset > 0)
        FlushLine ();
    PutEOD ();
}

/* LZW encoder */

class LZWEncoder : public Ascii85Encoder
{
private:

    struct LZWCTreeNode
    {
        LZWCTreeNode*   mpBrother;      // next node with same parent
        LZWCTreeNode*   mpFirstChild;   // first son
        sal_uInt16      mnCode;         // code for the string
        sal_uInt16      mnValue;        // pixelvalue
    };

    LZWCTreeNode*   mpTable;    // LZW compression data
    LZWCTreeNode*   mpPrefix;   // the compression is as same as the TIFF compression
    sal_uInt16      mnDataSize;
    sal_uInt16      mnClearCode;
    sal_uInt16      mnEOICode;
    sal_uInt16      mnTableSize;
    sal_uInt16      mnCodeSize;
    sal_uInt32      mnOffset;
    sal_uInt32      mdwShift;

    LZWEncoder ();
    void            WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen);

public:

    LZWEncoder (osl::File* pOutputFile);
    ~LZWEncoder ();
        
    virtual void    EncodeByte (sal_uInt8 nByte);
};

LZWEncoder::LZWEncoder(osl::File* pOutputFile) :
        Ascii85Encoder (pOutputFile)
{
    mnDataSize  = 8;

    mnClearCode = 1 << mnDataSize;
    mnEOICode   = mnClearCode + 1;
    mnTableSize = mnEOICode   + 1;
    mnCodeSize  = mnDataSize  + 1;

    mnOffset    = 32;   // free bits in dwShift
    mdwShift    = 0;

    mpTable = new LZWCTreeNode[ 4096 ];

    for (sal_uInt32 i = 0; i < 4096; i++)
    {
        mpTable[i].mpBrother    = NULL;
        mpTable[i].mpFirstChild = NULL;
        mpTable[i].mnCode       = i;
        mpTable[i].mnValue      = (sal_uInt8)mpTable[i].mnCode;
    }

    mpPrefix = NULL;

    WriteBits( mnClearCode, mnCodeSize );
}

LZWEncoder::~LZWEncoder()
{
    if (mpPrefix)
        WriteBits (mpPrefix->mnCode, mnCodeSize);

    WriteBits (mnEOICode, mnCodeSize);

    delete[] mpTable;
}

void 
LZWEncoder::WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen)
{
    mdwShift |= (nCode << (mnOffset - nCodeLen));
    mnOffset -= nCodeLen;
    while (mnOffset < 24)
    {
        WriteAscii ((sal_uInt8)(mdwShift >> 24));
        mdwShift <<= 8;
        mnOffset += 8;
    }
    if (nCode == 257 && mnOffset != 32)
        WriteAscii ((sal_uInt8)(mdwShift >> 24));
}

void 
LZWEncoder::EncodeByte (sal_uInt8 nByte )
{
    LZWCTreeNode*   p;
    sal_uInt16      i;
    sal_uInt8       nV;

    if (!mpPrefix)
    {
        mpPrefix = mpTable + nByte;
    }
    else
    {
        nV = nByte;
        for (p = mpPrefix->mpFirstChild; p != NULL; p = p->mpBrother)
        {
            if (p->mnValue == nV)
                break;
        }

        if (p != NULL)
        {
            mpPrefix = p;
        }
        else
        {
            WriteBits (mpPrefix->mnCode, mnCodeSize);

            if (mnTableSize == 409)
            {
                WriteBits (mnClearCode, mnCodeSize);

                for (i = 0; i < mnClearCode; i++)
                    mpTable[i].mpFirstChild = NULL;

                mnCodeSize = mnDataSize + 1;
                mnTableSize = mnEOICode + 1;
            }
            else
            {
                if(mnTableSize == (sal_uInt16)((1 << mnCodeSize) - 1))
                    mnCodeSize++;

                p = mpTable + (mnTableSize++);
                p->mpBrother = mpPrefix->mpFirstChild;
                mpPrefix->mpFirstChild = p;
                p->mnValue = nV;
                p->mpFirstChild = NULL;
            }

            mpPrefix = mpTable + nV;
        }
    }
}

/*
 *
 * bitmap handling routines
 *
 */

void
PrinterGfx::DrawBitmap (const Rectangle& rDest, const Rectangle& rSrc, 
                        const PrinterBmp& rBitmap)
{
    double fScaleX = (double)rDest.GetWidth() / (double)rSrc.GetWidth();
    double fScaleY = (double)rDest.GetHeight() / (double)rSrc.GetHeight();

    PSGSave ();
    PSTranslate (rDest.BottomLeft());
    PSScale (fScaleX, fScaleY);

    if (mnPSLevel >= 2)
    {
        if (rBitmap.GetDepth() == 1)
        {
            DrawPS2MonoImage (rBitmap, rSrc);
        }
        else
        if (rBitmap.GetDepth() ==  8 && mbColor)
        {
            // if the palette is larger than the image itself print it as a truecolor
            // image to save diskspace. This is important for printing transparent
            // bitmaps that are disassembled into small pieces
            sal_Int32 nImageSz   = rSrc.GetWidth() * rSrc.GetHeight();
            sal_Int32 nPaletteSz = rBitmap.GetPaletteEntryCount(); 
            if ((nImageSz < nPaletteSz) || (nImageSz < 24) )
        	    DrawPS2TrueColorImage (rBitmap, rSrc);
            else
                DrawPS2PaletteImage (rBitmap, rSrc);
        }
        else
        if (rBitmap.GetDepth() == 24 && mbColor)
        {
        	DrawPS2TrueColorImage (rBitmap, rSrc);
        }
        else    
        {
        	DrawPS2GrayImage (rBitmap, rSrc);
        }
    }
    else
    {
        DrawPS1GrayImage (rBitmap, rSrc);
    }

    PSGRestore ();
}

/* XXX does not work XXX */
void
PrinterGfx::DrawBitmap (const Rectangle& rDest, const Rectangle& rSrc, 
                        const PrinterBmp& /*rBitmap*/, const PrinterBmp& /*rTransBitmap*/)
{
    double fScaleX = (double)rDest.GetWidth() / (double)rSrc.GetWidth();
    double fScaleY = (double)rDest.GetHeight() / (double)rSrc.GetHeight();

    PSGSave ();
    PSTranslate (rDest.BottomLeft());
    PSScale (fScaleX, fScaleY); 
    PSGRestore ();
}

/* XXX does not work XXX */
void
PrinterGfx::DrawMask   (const Rectangle& rDest, const Rectangle& rSrc, 
                        const PrinterBmp &/*rBitmap*/, PrinterColor& /*rMaskColor*/)
{
    double fScaleX = (double)rDest.GetWidth() / (double)rSrc.GetWidth();
    double fScaleY = (double)rDest.GetHeight() / (double)rSrc.GetHeight();

    PSGSave ();
    PSTranslate (rDest.BottomLeft());
    PSScale (fScaleX, fScaleY); 
    PSGRestore ();
}

/*
 *
 * Implementation: PS Level 1
 *
 */

void
PrinterGfx::DrawPS1GrayImage (const PrinterBmp& rBitmap, const Rectangle& rArea)
{
    sal_uInt32 nWidth  = rArea.GetWidth();
    sal_uInt32 nHeight = rArea.GetHeight();

    sal_Char  pGrayImage [512];
    sal_Int32 nChar = 0;

    // image header
    nChar += psp::getValueOf (nWidth,                           pGrayImage + nChar);
    nChar += psp::appendStr  (" ",                              pGrayImage + nChar);
    nChar += psp::getValueOf (nHeight,                          pGrayImage + nChar);
    nChar += psp::appendStr  (" 8 ",                            pGrayImage + nChar);
    nChar += psp::appendStr  ("[ 1 0 0 1 0 ",                   pGrayImage + nChar);
    nChar += psp::getValueOf (nHeight,                          pGrayImage + nChar);
    nChar += psp::appendStr  ("]",                              pGrayImage + nChar);
    nChar += psp::appendStr  (" {currentfile ",                 pGrayImage + nChar);
    nChar += psp::getValueOf (nWidth,                           pGrayImage + nChar);
    nChar += psp::appendStr  (" string readhexstring pop}\n",   pGrayImage + nChar);
    nChar += psp::appendStr  ("image\n",                        pGrayImage + nChar);

    WritePS (mpPageBody, pGrayImage);

    // image body
    HexEncoder* pEncoder = new HexEncoder (mpPageBody);

    for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
    {
        for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
        {
            sal_uChar nByte = rBitmap.GetPixelGray (nRow, nColumn);
            pEncoder->EncodeByte (nByte);
        }
    }

    delete pEncoder;    

    WritePS (mpPageBody, "\n");
}

/*
 *
 * Implementation: PS Level 2
 *
 */

void 
PrinterGfx::writePS2ImageHeader (const Rectangle& rArea, psp::ImageType nType)
{
    sal_Int32 nChar = 0;
    sal_Char  pImage [512];

    sal_Int32 nDictType = 0;
    switch (nType)
    {
        case psp::TrueColorImage:  nDictType = 0; break;
        case psp::PaletteImage:    nDictType = 1; break;
        case psp::GrayScaleImage:  nDictType = 2; break;
        case psp::MonochromeImage: nDictType = 3; break;
        default: break;
    }
    sal_Int32 nCompressType = mbCompressBmp ? 1 : 0;

    nChar += psp::getValueOf (rArea.GetWidth(),  pImage + nChar);
    nChar += psp::appendStr  (" ",               pImage + nChar);
    nChar += psp::getValueOf (rArea.GetHeight(), pImage + nChar);
    nChar += psp::appendStr  (" ",               pImage + nChar);
    nChar += psp::getValueOf (nDictType,         pImage + nChar);
    nChar += psp::appendStr  (" ",               pImage + nChar);
    nChar += psp::getValueOf (nCompressType,     pImage + nChar);
    nChar += psp::appendStr  (" psp_imagedict image\n", pImage + nChar);

    WritePS (mpPageBody, pImage);
}

void
PrinterGfx::writePS2Colorspace(const PrinterBmp& rBitmap, psp::ImageType nType)
{
    switch (nType)
    {
        case psp::GrayScaleImage:

            WritePS (mpPageBody, "/DeviceGray setcolorspace\n");
            break;

        case psp::TrueColorImage:

            WritePS (mpPageBody, "/DeviceRGB setcolorspace\n");
            break;

        case psp::MonochromeImage: 
        case psp::PaletteImage:
        {

            sal_Int32 nChar = 0;
            sal_Char  pImage [4096];

            const sal_uInt32 nSize = rBitmap.GetPaletteEntryCount();

            nChar += psp::appendStr ("[/Indexed /DeviceRGB ", pImage + nChar);
            nChar += psp::getValueOf (nSize - 1, pImage + nChar);
            if (mbCompressBmp)
                nChar += psp::appendStr ("\npsp_lzwstring\n", pImage + nChar);
            else
                nChar += psp::appendStr ("\npsp_ascii85string\n", pImage + nChar);
            WritePS (mpPageBody, pImage);

            ByteEncoder* pEncoder = mbCompressBmp ? new LZWEncoder(mpPageBody) 
                                                  : new Ascii85Encoder(mpPageBody);
            for (sal_uInt32 i = 0; i < nSize; i++)
            {
                PrinterColor aColor = rBitmap.GetPaletteColor(i);

                pEncoder->EncodeByte (aColor.GetRed());
                pEncoder->EncodeByte (aColor.GetGreen());
                pEncoder->EncodeByte (aColor.GetBlue());
            }
            delete pEncoder;
          
            WritePS (mpPageBody, "pop ] setcolorspace\n");
        }
        break;
        default: break;
    }
}

void
PrinterGfx::DrawPS2GrayImage (const PrinterBmp& rBitmap, const Rectangle& rArea)
{
    writePS2Colorspace(rBitmap, psp::GrayScaleImage);
    writePS2ImageHeader(rArea, psp::GrayScaleImage); 

    ByteEncoder* pEncoder = mbCompressBmp ? new LZWEncoder(mpPageBody) 
                                          : new Ascii85Encoder(mpPageBody);

    for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
    {
        for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
        {
            sal_uChar nByte = rBitmap.GetPixelGray (nRow, nColumn);
            pEncoder->EncodeByte (nByte);
        }
    }

    delete pEncoder;
}

void
PrinterGfx::DrawPS2MonoImage (const PrinterBmp& rBitmap, const Rectangle& rArea)
{
    writePS2Colorspace(rBitmap, psp::MonochromeImage);
    writePS2ImageHeader(rArea, psp::MonochromeImage);

    ByteEncoder* pEncoder = mbCompressBmp ? new LZWEncoder(mpPageBody) 
                                          : new Ascii85Encoder(mpPageBody);

    for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
    {
        long      nBitPos = 0;
        sal_uChar nBit    = 0; 
        sal_uChar nByte   = 0;

        for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
        {
            nBit   = rBitmap.GetPixelIdx (nRow, nColumn);
            nByte |= nBit << (7 - nBitPos);
            
            if (++nBitPos == 8)
            {
                pEncoder->EncodeByte (nByte);
                nBitPos = 0;
                nByte   = 0;
            }
        }
        // keep the row byte aligned
        if (nBitPos != 0)
            pEncoder->EncodeByte (nByte);
    }

    delete pEncoder;
}

void
PrinterGfx::DrawPS2PaletteImage (const PrinterBmp& rBitmap, const Rectangle& rArea)
{
    writePS2Colorspace(rBitmap, psp::PaletteImage);
    writePS2ImageHeader(rArea, psp::PaletteImage);

    ByteEncoder* pEncoder = mbCompressBmp ? new LZWEncoder(mpPageBody) 
                                          : new Ascii85Encoder(mpPageBody);

    for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
    {
        for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
        {
            sal_uChar nByte = rBitmap.GetPixelIdx (nRow, nColumn);
            pEncoder->EncodeByte (nByte);
        }
    }

    delete pEncoder;
}

void
PrinterGfx::DrawPS2TrueColorImage (const PrinterBmp& rBitmap, const Rectangle& rArea)
{
    writePS2Colorspace(rBitmap, psp::TrueColorImage);
    writePS2ImageHeader(rArea, psp::TrueColorImage);

    ByteEncoder* pEncoder = mbCompressBmp ? new LZWEncoder(mpPageBody) 
                                          : new Ascii85Encoder(mpPageBody);

    for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
    {
        for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
        {
            PrinterColor aColor = rBitmap.GetPixelRGB (nRow, nColumn);
            pEncoder->EncodeByte (aColor.GetRed());
            pEncoder->EncodeByte (aColor.GetGreen());
            pEncoder->EncodeByte (aColor.GetBlue());
        }
    }

    delete pEncoder;
}

} /* namespace psp */
