/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

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

#include "psputil.hxx"
#include "glyphset.hxx"

#include "vcl/printergfx.hxx"
#include "vcl/printerjob.hxx"
#include "vcl/fontmanager.hxx"
#include "vcl/strhelper.hxx"
#include "vcl/printerinfomanager.hxx"

#include "tools/debug.hxx"
#include "tools/color.hxx"
#include "tools/poly.hxx"

using namespace psp ;

static const sal_Int32 nMaxTextColumn = 80; 

GraphicsStatus::GraphicsStatus() :
        mbArtItalic( false ),
        mbArtBold( false ),
        mnTextHeight( 0 ),
        mnTextWidth( 0 ),
        mfLineWidth( -1 )
{
}

/*
 * non graphics graphics routines
 */

sal_Bool
PrinterGfx::Init (PrinterJob &rPrinterJob)
{
    mpPageHeader = rPrinterJob.GetCurrentPageHeader ();
    mpPageBody   = rPrinterJob.GetCurrentPageBody ();
    mnDepth      = rPrinterJob.GetDepth ();
    mnPSLevel    = rPrinterJob.GetPostscriptLevel ();
    mbColor      = rPrinterJob.IsColorPrinter ();
    
    mnDpi = rPrinterJob.GetResolution();
    rPrinterJob.GetScale (mfScaleX, mfScaleY);
    const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rPrinterJob.GetPrinterName() ) );
    if( mpFontSubstitutes )
        delete const_cast< ::boost::unordered_map<fontID,fontID>* >(mpFontSubstitutes);
    if( rInfo.m_bPerformFontSubstitution )
        mpFontSubstitutes = new ::boost::unordered_map< fontID, fontID >( rInfo.m_aFontSubstitutions );
    else
        mpFontSubstitutes = NULL;
    mbUploadPS42Fonts = rInfo.m_pParser ? ( rInfo.m_pParser->isType42Capable() ? sal_True : sal_False ) : sal_False;

    return sal_True;
}

sal_Bool
PrinterGfx::Init (const JobData& rData)
{
    mpPageHeader    = NULL;
    mpPageBody      = NULL;
    mnDepth         = rData.m_nColorDepth;
    mnPSLevel       = rData.m_nPSLevel ? rData.m_nPSLevel : (rData.m_pParser ? rData.m_pParser->getLanguageLevel() : 2 );
    mbColor         = rData.m_nColorDevice ? ( rData.m_nColorDevice == -1 ? sal_False : sal_True ) : (( rData.m_pParser ?  (rData.m_pParser->isColorDevice() ? sal_True : sal_False ) : sal_True ) );
    int nRes = rData.m_aContext.getRenderResolution();
    mnDpi           = nRes;
    mfScaleX        = (double)72.0 / (double)mnDpi;
    mfScaleY        = (double)72.0 / (double)mnDpi;
    const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rData.m_aPrinterName ) );
    if( mpFontSubstitutes )
        delete const_cast< ::boost::unordered_map<fontID,fontID>* >(mpFontSubstitutes);
    if( rInfo.m_bPerformFontSubstitution )
        mpFontSubstitutes = new ::boost::unordered_map< fontID, fontID >( rInfo.m_aFontSubstitutions );
    else
        mpFontSubstitutes = NULL;
    mbUploadPS42Fonts = rInfo.m_pParser ? ( rInfo.m_pParser->isType42Capable() ? sal_True : sal_False ) : sal_False;
    
    return sal_True;
}

void
PrinterGfx::GetResolution (sal_Int32 &rDpiX, sal_Int32 &rDpiY) const
{
    rDpiX = mnDpi;
    rDpiY = mnDpi;
}

sal_uInt16
PrinterGfx::GetBitCount ()
{
    return mnDepth;
}

PrinterGfx::PrinterGfx() : 
        mpPageHeader (NULL),
        mpPageBody (NULL),
        mnFontID (0),
        mnFallbackID (0),
        mnTextAngle (0),
        mbTextVertical (false),
        mrFontMgr (PrintFontManager::get()),
        mbCompressBmp (sal_True),
        maFillColor (0xff,0,0),
        maTextColor (0,0,0),
        maLineColor (0, 0xff, 0),
        mpFontSubstitutes( NULL ),
        mbStrictSO52Compatibility( false )
{
    maVirtualStatus.mfLineWidth = 1.0;
    maVirtualStatus.mnTextHeight = 12;
    maVirtualStatus.mnTextWidth = 0;

    maGraphicsStack.push_back( GraphicsStatus() );
}

PrinterGfx::~PrinterGfx()
{
    /*
     *  the original reasoning why mpFontSubstitutes is a pointer was
     *  that applications should release all PrinterGfx when printers change
     *  because they are really invalid; the corresponding printers may have
     *  changed their settings or even not exist anymore.
     *
     *  Alas, this is not always done real time. So we keep a local copy of
     *  the font substitutes now in case of bad timing.
     */
    delete const_cast< ::boost::unordered_map<fontID,fontID>* >(mpFontSubstitutes);
}

void
PrinterGfx::Clear()
{
    mpPageHeader                    = NULL;
    mpPageBody                      = NULL;
    mnFontID                        = 0;
    maVirtualStatus                 = GraphicsStatus();
    maVirtualStatus.mnTextHeight    = 12;
    maVirtualStatus.mnTextWidth     = 0;
    maVirtualStatus.mfLineWidth     = 1.0;
    mbTextVertical                  = false;
    maLineColor                     = PrinterColor();
    maFillColor                     = PrinterColor();
    maTextColor                     = PrinterColor();
    mbCompressBmp                   = sal_True;
    mnDpi                           = 300;
    mnDepth                         = 24;
    mnPSLevel                       = 2;
    mbColor                         = sal_True;
    mnTextAngle                     = 0;

    maClipRegion.clear();
    maGraphicsStack.clear();
    maGraphicsStack.push_back( GraphicsStatus() );
}

/*
 * clip region handling
 */

void
PrinterGfx::ResetClipRegion()
{
    maClipRegion.clear();
    PSGRestore ();
    PSGSave (); // get "clean" clippath
}

void
PrinterGfx::BeginSetClipRegion( sal_uInt32 )
{
    maClipRegion.clear();
}

sal_Bool
PrinterGfx::UnionClipRegion (sal_Int32 nX,sal_Int32 nY,sal_Int32 nDX,sal_Int32 nDY)
{
    if( nDX && nDY )
        maClipRegion.push_back (Rectangle(Point(nX,nY ), Size(nDX,nDY)));
    return sal_True;
}

sal_Bool
PrinterGfx::JoinVerticalClipRectangles( std::list< Rectangle >::iterator& it,
                                        Point& rOldPoint, sal_Int32& rColumn )
{
    sal_Bool bSuccess = sal_False;

    std::list< Rectangle >::iterator tempit, nextit;
    nextit = it;
    ++nextit;
    std::list< Point > leftside, rightside;
    
    Rectangle aLastRect( *it );
    leftside.push_back( Point( it->Left(), it->Top() ) );
    rightside.push_back( Point( it->Right()+1, it->Top() ) );
    while( nextit != maClipRegion.end() )
    {
        tempit = nextit;
        ++tempit;
        if( nextit->Top() == aLastRect.Bottom()+1 )
        {
            if( 
               ( nextit->Left() >= aLastRect.Left() && nextit->Left() <= aLastRect.Right() ) // left endpoint touches last rectangle
               ||
               ( nextit->Right() >= aLastRect.Left() && nextit->Right() <= aLastRect.Right() ) // right endpoint touches last rectangle
               ||
               ( nextit->Left() <= aLastRect.Left() && nextit->Right() >= aLastRect.Right() ) // whole line touches last rectangle
               )
            {
                if( aLastRect.GetHeight() > 1                           ||
                    abs( aLastRect.Left() - nextit->Left() ) > 2        ||
                    abs( aLastRect.Right() - nextit->Right() ) > 2
                    )
                {
                    leftside.push_back( Point( aLastRect.Left(), aLastRect.Bottom()+1 ) );
                    rightside.push_back( Point( aLastRect.Right()+1, aLastRect.Bottom()+1 ) );
                }
                aLastRect = *nextit;
                leftside.push_back( aLastRect.TopLeft() );
                rightside.push_back( aLastRect.TopRight() );
                maClipRegion.erase( nextit );
            }
        }
        nextit = tempit;
    }
    if( leftside.size() > 1 )
    {
        // push the last coordinates
        leftside.push_back( Point( aLastRect.Left(), aLastRect.Bottom()+1 ) );
        rightside.push_back( Point( aLastRect.Right()+1, aLastRect.Bottom()+1 ) );

        // cool, we can concatenate rectangles
        int nDX = -65536, nDY = 65536;
        int nNewDX = 0, nNewDY = 0;

        Point aLastPoint = leftside.front();
        PSBinMoveTo (aLastPoint, rOldPoint, rColumn);
        leftside.pop_front();
        while( leftside.begin() != leftside.end() )
        {
            Point aPoint (leftside.front());
            leftside.pop_front();
            // may have been the last one
            if( leftside.begin() != leftside.end() )
            {
                nNewDX = aPoint.X() - aLastPoint.X();
                nNewDY = aPoint.Y() - aLastPoint.Y();
                if( nNewDX == 0 && nDX == 0 )
                    continue;
                if( nDX != 0 && nNewDX != 0 &&
                    (double)nNewDY/(double)nNewDX == (double)nDY/(double)nDX )
                    continue;
            }
            PSBinLineTo (aPoint, rOldPoint, rColumn);
            aLastPoint = aPoint;
        }

        aLastPoint = rightside.back();
        nDX = -65536;
        nDY = 65536;
        PSBinLineTo (aLastPoint, rOldPoint, rColumn);
        rightside.pop_back();
        while( rightside.begin() != rightside.end() )
        {
            Point aPoint (rightside.back());
            rightside.pop_back();
            if( rightside.begin() != rightside.end() )
            {
                nNewDX = aPoint.X() - aLastPoint.X();
                nNewDY = aPoint.Y() - aLastPoint.Y();
                if( nNewDX == 0 && nDX == 0 )
                    continue;
                if( nDX != 0 && nNewDX != 0 &&
                    (double)nNewDY/(double)nNewDX == (double)nDY/(double)nDX )
                    continue;
            }
            PSBinLineTo (aPoint, rOldPoint, rColumn);
        }

        tempit = it;
        ++tempit;
        maClipRegion.erase( it );
        it = tempit;
        bSuccess = sal_True;
    }
    return bSuccess;
}

void
PrinterGfx::EndSetClipRegion()
{
    PSGRestore ();
    PSGSave (); // get "clean" clippath

    PSBinStartPath ();
    Point aOldPoint (0, 0);
    sal_Int32 nColumn = 0;

    std::list< Rectangle >::iterator it = maClipRegion.begin();
    while( it != maClipRegion.end() )
    {
        // try to concatenate adjacent rectangles
        // first try in y direction, then in x direction
        if( ! JoinVerticalClipRectangles( it, aOldPoint, nColumn ) )
        {
            // failed, so it is a single rectangle
            PSBinMoveTo (it->TopLeft(),                          aOldPoint, nColumn );
            PSBinLineTo (Point( it->Left(), it->Bottom()+1 ),    aOldPoint, nColumn );
            PSBinLineTo (Point( it->Right()+1, it->Bottom()+1 ), aOldPoint, nColumn );
            PSBinLineTo (Point( it->Right()+1, it->Top() ),      aOldPoint, nColumn );
            ++it;
        }
    }

    PSBinEndPath ();

    WritePS (mpPageBody, "closepath clip newpath\n");
    maClipRegion.clear();
}

/*
 * draw graphic primitives
 */

void
PrinterGfx::DrawRect (const Rectangle& rRectangle )
{
    char pRect [128];
    sal_Int32 nChar = 0;

    nChar  = psp::getValueOf (rRectangle.TopLeft().X(),     pRect);
    nChar += psp::appendStr (" ",                           pRect + nChar);
    nChar += psp::getValueOf (rRectangle.TopLeft().Y(),     pRect + nChar);
    nChar += psp::appendStr (" ",                           pRect + nChar);
    nChar += psp::getValueOf (rRectangle.GetWidth(),        pRect + nChar);
    nChar += psp::appendStr (" ",                           pRect + nChar);
    nChar += psp::getValueOf (rRectangle.GetHeight(),       pRect + nChar);
    nChar += psp::appendStr (" ",                           pRect + nChar);

    if( maFillColor.Is() )
    {
        PSSetColor (maFillColor);
        PSSetColor ();
        WritePS (mpPageBody, pRect, nChar);
        WritePS (mpPageBody, "rectfill\n");
    }
    if( maLineColor.Is() )
    {
        PSSetColor (maLineColor);
        PSSetColor ();
        PSSetLineWidth ();
        WritePS (mpPageBody, pRect, nChar);
        WritePS (mpPageBody, "rectstroke\n");
    }
}

void
PrinterGfx::DrawLine (const Point& rFrom, const Point& rTo)
{
    if( maLineColor.Is() )
    {
        PSSetColor (maLineColor);
        PSSetColor ();
        PSSetLineWidth ();
        
        PSMoveTo (rFrom);
        PSLineTo (rTo);
        WritePS (mpPageBody, "stroke\n" );
    }
}

void
PrinterGfx::DrawPixel (const Point& rPoint, const PrinterColor& rPixelColor)
{
    if( rPixelColor.Is() )
    {
        PSSetColor (rPixelColor);
        PSSetColor ();

        PSMoveTo (rPoint);
        PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()));
        PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()+1));
        PSLineTo (Point (rPoint.X (), rPoint.Y ()+1));
        WritePS (mpPageBody, "fill\n" );
    }
}

void
PrinterGfx::DrawPolyLine (sal_uInt32 nPoints, const Point* pPath)
{
    if( maLineColor.Is() && nPoints && pPath )
    {
        PSSetColor (maLineColor);
        PSSetColor ();
        PSSetLineWidth ();

        PSBinCurrentPath (nPoints, pPath);
        
        WritePS (mpPageBody, "stroke\n" );
    }       
}

void
PrinterGfx::DrawPolygon (sal_uInt32 nPoints, const Point* pPath)
{
    // premature end of operation
    if (!(nPoints > 1) || (pPath == NULL) || !(maFillColor.Is() || maLineColor.Is()))
        return;

    // setup closed path
    Point aPoint( 0, 0 );
    sal_Int32 nColumn( 0 );
        
    PSBinStartPath();
    PSBinMoveTo( pPath[0], aPoint, nColumn );
    for( unsigned int n = 1; n < nPoints; n++ )
        PSBinLineTo( pPath[n], aPoint, nColumn );
    if( pPath[0] != pPath[nPoints-1] )
        PSBinLineTo( pPath[0], aPoint, nColumn );
    PSBinEndPath();

    // fill the polygon first, then draw the border, note that fill and
    // stroke reset the currentpath

    // if fill and stroke, save the current path
    if( maFillColor.Is() && maLineColor.Is())
        PSGSave();

    if (maFillColor.Is ())
    {
        PSSetColor (maFillColor);
        PSSetColor ();
        WritePS (mpPageBody, "eofill\n");
    }

    // restore the current path
    if( maFillColor.Is() && maLineColor.Is())
        PSGRestore();

    if (maLineColor.Is ())
    {
        PSSetColor (maLineColor);
        PSSetColor ();
        PSSetLineWidth ();
        WritePS (mpPageBody, "stroke\n");
    }
}

void
PrinterGfx::DrawPolyPolygon (sal_uInt32 nPoly, const sal_uInt32* pSizes, const Point** pPaths )
{
    // sanity check
    if ( !nPoly || !pPaths || !(maFillColor.Is() || maLineColor.Is()))
        return;


    // setup closed path
    for( unsigned int i = 0; i < nPoly; i++ )
    {
        Point aPoint( 0, 0 );
        sal_Int32 nColumn( 0 );
        
        PSBinStartPath();
        PSBinMoveTo( pPaths[i][0], aPoint, nColumn );
        for( unsigned int n = 1; n < pSizes[i]; n++ )
            PSBinLineTo( pPaths[i][n], aPoint, nColumn );
        if( pPaths[i][0] != pPaths[i][pSizes[i]-1] )
                PSBinLineTo( pPaths[i][0], aPoint, nColumn );
        PSBinEndPath();
    }

    // if eofill and stroke, save the current path
    if( maFillColor.Is() && maLineColor.Is())
        PSGSave();

    // first draw area
    if( maFillColor.Is() )
    {
        PSSetColor (maFillColor);
        PSSetColor ();
        WritePS (mpPageBody, "eofill\n");
    }

    // restore the current path
    if( maFillColor.Is() && maLineColor.Is())
        PSGRestore();

    // now draw outlines
    if( maLineColor.Is() )
    {
        PSSetColor (maLineColor);
        PSSetColor ();
        PSSetLineWidth ();
        WritePS (mpPageBody, "stroke\n");
    }
}

/*
 * Bezier Polygon Drawing methods.
 */

void
PrinterGfx::DrawPolyLineBezier (sal_uInt32 nPoints, const Point* pPath, const sal_uInt8* pFlgAry)
{
    const sal_uInt32 nBezString= 1024;
    sal_Char pString[nBezString];
    
    if ( nPoints > 1 && maLineColor.Is() && pPath )
    {
        PSSetColor (maLineColor);
        PSSetColor ();
        PSSetLineWidth ();

        snprintf(pString, nBezString, "%li %li moveto\n", pPath[0].X(), pPath[0].Y());
        WritePS(mpPageBody, pString);
        
        // Handle the drawing of mixed lines mixed with curves 
        // - a normal point followed by a normal point is a line
        // - a normal point followed by 2 control points and a normal point is a curve
        for (unsigned int i=1; i<nPoints;)
        {
            if (pFlgAry[i] != POLY_CONTROL) //If the next point is a POLY_NORMAL, we're drawing a line
            {
                snprintf(pString, nBezString, "%li %li lineto\n", pPath[i].X(), pPath[i].Y());
                i++;
            }
            else //Otherwise we're drawing a spline
            {
                if (i+2 >= nPoints)               
                    return; //Error: wrong sequence of contol/normal points somehow
                if ((pFlgAry[i] == POLY_CONTROL) && (pFlgAry[i+1] == POLY_CONTROL) &&
                    (pFlgAry[i+2] != POLY_CONTROL))
                {
                    snprintf(pString, nBezString, "%li %li %li %li %li %li curveto\n",
                             pPath[i].X(), pPath[i].Y(), 
                             pPath[i+1].X(), pPath[i+1].Y(), 
                             pPath[i+2].X(), pPath[i+2].Y());
                }
                else
                {
                    OSL_FAIL( "PrinterGfx::DrawPolyLineBezier: Strange output" );
                }
                i+=3;
            }
            WritePS(mpPageBody, pString);
        }

        // now draw outlines
        WritePS (mpPageBody, "stroke\n");
    }
}

void
PrinterGfx::DrawPolygonBezier (sal_uInt32 nPoints, const Point* pPath, const sal_uInt8* pFlgAry)
{
    const sal_uInt32 nBezString = 1024;
    sal_Char pString[nBezString];
    // premature end of operation
    if (!(nPoints > 1) || (pPath == NULL) || !(maFillColor.Is() || maLineColor.Is()))
        return;
    
    snprintf(pString, nBezString, "%li %li moveto\n", pPath[0].X(), pPath[0].Y());
    WritePS(mpPageBody, pString); //Move to the starting point for the PolyPoygon
    for (unsigned int i=1; i < nPoints;) 
    {
        if (pFlgAry[i] != POLY_CONTROL)
        {
            snprintf(pString, nBezString, "%li %li lineto\n", pPath[i].X(), pPath[i].Y());
            WritePS(mpPageBody, pString);
            i++;
        }
        else
        {
            if (i+2 >= nPoints)               
                return; //Error: wrong sequence of contol/normal points somehow
            if ((pFlgAry[i] == POLY_CONTROL) && (pFlgAry[i+1] == POLY_CONTROL) &&
                    (pFlgAry[i+2] != POLY_CONTROL))
            {
                snprintf(pString, nBezString, "%li %li %li %li %li %li curveto\n",
                        pPath[i].X(), pPath[i].Y(), 
                        pPath[i+1].X(), pPath[i+1].Y(), 
                        pPath[i+2].X(), pPath[i+2].Y());
                WritePS(mpPageBody, pString);
            }
            else
            {
                OSL_FAIL( "PrinterGfx::DrawPolygonBezier: Strange output" );
            }
            i+=3;
        }
    }

    // if fill and stroke, save the current path
    if( maFillColor.Is() && maLineColor.Is())
        PSGSave();

    if (maFillColor.Is ())
    {
        PSSetColor (maFillColor);
        PSSetColor ();
        WritePS (mpPageBody, "eofill\n");
    }

    // restore the current path
    if( maFillColor.Is() && maLineColor.Is())
        PSGRestore();    
}

void
PrinterGfx::DrawPolyPolygonBezier (sal_uInt32 nPoly, const sal_uInt32 * pPoints, const Point* const * pPtAry, const sal_uInt8* const* pFlgAry)
{
    const sal_uInt32 nBezString = 1024;
    sal_Char pString[nBezString];
    if ( !nPoly || !pPtAry || !pPoints || !(maFillColor.Is() || maLineColor.Is()))
        return;
    
    
    for (unsigned int i=0; i<nPoly;i++)
    {
        sal_uInt32 nPoints = pPoints[i];
        // sanity check
        if( nPoints == 0 || pPtAry[i] == NULL )
            continue;
        
        snprintf(pString, nBezString, "%li %li moveto\n", pPtAry[i][0].X(), pPtAry[i][0].Y()); //Move to the starting point
        WritePS(mpPageBody, pString);
        for (unsigned int j=1; j < nPoints;)
        {
            // if no flag array exists for this polygon, then it must be a regular
            // polygon without beziers
            if ( ! pFlgAry[i] || pFlgAry[i][j] != POLY_CONTROL)
            {
                snprintf(pString, nBezString, "%li %li lineto\n", pPtAry[i][j].X(), pPtAry[i][j].Y());
                WritePS(mpPageBody, pString);
                j++;
            }
            else
            {
                if (j+2 >= nPoints)
                    break; //Error: wrong sequence of contol/normal points somehow
                if ((pFlgAry[i][j] == POLY_CONTROL) && (pFlgAry[i][j+1] == POLY_CONTROL) && (pFlgAry[i][j+2] != POLY_CONTROL))
                {
                    snprintf(pString, nBezString, "%li %li %li %li %li %li curveto\n",
                            pPtAry[i][j].X(), pPtAry[i][j].Y(), 
                            pPtAry[i][j+1].X(), pPtAry[i][j+1].Y(), 
                            pPtAry[i][j+2].X(), pPtAry[i][j+2].Y());
                    WritePS(mpPageBody, pString);
                }
                else
                {
                    OSL_FAIL( "PrinterGfx::DrawPolyPolygonBezier: Strange output" );
                }
                j+=3;
            }
        }
    }
    
    // if fill and stroke, save the current path
    if( maFillColor.Is() && maLineColor.Is())
        PSGSave();

    if (maFillColor.Is ())
    {
        PSSetColor (maFillColor);
        PSSetColor ();
        WritePS (mpPageBody, "eofill\n");
    }

    // restore the current path
    if( maFillColor.Is() && maLineColor.Is())
        PSGRestore();    
}


/*
 * postscript generating routines
 */
void
PrinterGfx::PSGSave ()
{
    WritePS (mpPageBody, "gsave\n" );
    GraphicsStatus aNewState;
    if( maGraphicsStack.begin() != maGraphicsStack.end() )
        aNewState = maGraphicsStack.front();
    maGraphicsStack.push_front( aNewState );
}

void
PrinterGfx::PSGRestore ()
{
    WritePS (mpPageBody, "grestore\n" );
    if( maGraphicsStack.begin() == maGraphicsStack.end() )
        WritePS (mpPageBody, "Error: too many grestores\n" );
    else
        maGraphicsStack.pop_front();
}

void
PrinterGfx::PSSetLineWidth ()
{
    if( currentState().mfLineWidth != maVirtualStatus.mfLineWidth )
    {
        char pBuffer[128];
        sal_Int32 nChar = 0;

        currentState().mfLineWidth = maVirtualStatus.mfLineWidth;
        nChar  = psp::getValueOfDouble (pBuffer, maVirtualStatus.mfLineWidth, 5);
        nChar += psp::appendStr (" setlinewidth\n", pBuffer + nChar);
        WritePS (mpPageBody, pBuffer, nChar);
    }
}

void
PrinterGfx::PSSetColor ()
{
    PrinterColor& rColor( maVirtualStatus.maColor );

    if( currentState().maColor != rColor )
    {
        currentState().maColor = rColor;

        char pBuffer[128];
        sal_Int32 nChar = 0;

        if( mbColor )
        {   
            nChar  = psp::getValueOfDouble (pBuffer, 
                                            (double)rColor.GetRed() / 255.0, 5);
            nChar += psp::appendStr (" ", pBuffer + nChar);
            nChar += psp::getValueOfDouble (pBuffer + nChar, 
                                            (double)rColor.GetGreen() / 255.0, 5);  
            nChar += psp::appendStr (" ", pBuffer + nChar);
            nChar += psp::getValueOfDouble (pBuffer + nChar, 
                                            (double)rColor.GetBlue() / 255.0, 5);
            nChar += psp::appendStr (" setrgbcolor\n", pBuffer + nChar );
        }
        else
        {
            Color aColor( rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() );
            sal_uInt8 nCol = aColor.GetLuminance();
            nChar  = psp::getValueOfDouble( pBuffer, (double)nCol / 255.0, 5 );
            nChar += psp::appendStr( " setgray\n", pBuffer + nChar );
        }

        WritePS (mpPageBody, pBuffer, nChar);
    }
}

void
PrinterGfx::PSSetFont ()
{
    GraphicsStatus& rCurrent( currentState() );
    if( maVirtualStatus.maFont			!= rCurrent.maFont			||
        maVirtualStatus.mnTextHeight	!= rCurrent.mnTextHeight	||
        maVirtualStatus.maEncoding      != rCurrent.maEncoding		||
        maVirtualStatus.mnTextWidth     != rCurrent.mnTextWidth		||
        maVirtualStatus.mbArtBold		!= rCurrent.mbArtBold		||
        maVirtualStatus.mbArtItalic		!= rCurrent.mbArtItalic
        )
    {
        rCurrent.maFont              = maVirtualStatus.maFont;
        rCurrent.maEncoding          = maVirtualStatus.maEncoding;
        rCurrent.mnTextWidth         = maVirtualStatus.mnTextWidth;
        rCurrent.mnTextHeight        = maVirtualStatus.mnTextHeight;
        rCurrent.mbArtItalic		 = maVirtualStatus.mbArtItalic;
        rCurrent.mbArtBold			 = maVirtualStatus.mbArtBold;

        sal_Int32 nTextHeight = rCurrent.mnTextHeight;
        sal_Int32 nTextWidth  = rCurrent.mnTextWidth ? rCurrent.mnTextWidth 
                                                     : rCurrent.mnTextHeight;
        
        sal_Char  pSetFont [256];
        sal_Int32 nChar = 0;

        // postscript based fonts need reencoding 
        if (   (   rCurrent.maEncoding == RTL_TEXTENCODING_MS_1252) 
            || (   rCurrent.maEncoding == RTL_TEXTENCODING_ISO_8859_1)
            || (   rCurrent.maEncoding >= RTL_TEXTENCODING_USER_START 
                && rCurrent.maEncoding <= RTL_TEXTENCODING_USER_END)
           )
        {
            rtl::OString aReencodedFont = 
                        psp::GlyphSet::GetReencodedFontName (rCurrent.maEncoding, 
                                                                rCurrent.maFont);

            nChar += psp::appendStr  ("(",          pSetFont + nChar); 
            nChar += psp::appendStr  (aReencodedFont.getStr(), 
                                                    pSetFont + nChar);
            nChar += psp::appendStr  (") cvn findfont ",
                                                    pSetFont + nChar); 
        }
        else
        // tt based fonts mustn't reencode, the encoding is implied by the fontname
        // same for symbol type1 fonts, dont try to touch them
        {
            nChar += psp::appendStr  ("(",          pSetFont + nChar); 
            nChar += psp::appendStr  (rCurrent.maFont.getStr(), 
                                                    pSetFont + nChar);
            nChar += psp::appendStr  (") cvn findfont ", 
                                                    pSetFont + nChar);
        }

        if( ! rCurrent.mbArtItalic )
        {
            nChar += psp::getValueOf (nTextWidth,   pSetFont + nChar);
            nChar += psp::appendStr  (" ",          pSetFont + nChar);
            nChar += psp::getValueOf (-nTextHeight, pSetFont + nChar);
            nChar += psp::appendStr  (" matrix scale makefont setfont\n", pSetFont + nChar);
        }
        else // skew 15 degrees to right
        {
            nChar += psp::appendStr  ( " [",		pSetFont + nChar);
            nChar += psp::getValueOf (nTextWidth,	pSetFont + nChar);
            nChar += psp::appendStr  (" 0 ",        pSetFont + nChar);
            nChar += psp::getValueOfDouble (pSetFont + nChar, 0.27*(double)nTextWidth, 3 );
            nChar += psp::appendStr  ( " ",			pSetFont + nChar);
            nChar += psp::getValueOf (-nTextHeight, pSetFont + nChar);
            
            nChar += psp::appendStr  (" 0 0] makefont setfont\n", pSetFont + nChar);
        }

        WritePS (mpPageBody, pSetFont);
    }
}

void
PrinterGfx::PSRotate (sal_Int32 nAngle)
{
    sal_Int32 nPostScriptAngle = -nAngle;
    while( nPostScriptAngle < 0 )
        nPostScriptAngle += 3600;

    if (nPostScriptAngle == 0)
        return;

    sal_Int32 nFullAngle  = nPostScriptAngle / 10;
    sal_Int32 nTenthAngle = nPostScriptAngle % 10;

    sal_Char  pRotate [48];
    sal_Int32 nChar = 0;

    nChar  = psp::getValueOf (nFullAngle,  pRotate);
    nChar += psp::appendStr (".",          pRotate + nChar);
    nChar += psp::getValueOf (nTenthAngle, pRotate + nChar);
    nChar += psp::appendStr (" rotate\n",  pRotate + nChar);

    WritePS (mpPageBody, pRotate);
}

void
PrinterGfx::PSPointOp (const Point& rPoint, const sal_Char* pOperator)
{
    sal_Char  pPSCommand [48];
    sal_Int32 nChar = 0;

    nChar  = psp::getValueOf (rPoint.X(), pPSCommand);
    nChar += psp::appendStr  (" ",        pPSCommand + nChar);
    nChar += psp::getValueOf (rPoint.Y(), pPSCommand + nChar);
    nChar += psp::appendStr  (" ",        pPSCommand + nChar);
    nChar += psp::appendStr  (pOperator,  pPSCommand + nChar);
    nChar += psp::appendStr  ("\n",       pPSCommand + nChar);

    DBG_ASSERT (nChar < 48, "Buffer overflow in PSPointOp");

    WritePS (mpPageBody, pPSCommand);
}

void
PrinterGfx::PSTranslate (const Point& rPoint)
{
    PSPointOp (rPoint, "translate");
}

void
PrinterGfx::PSMoveTo (const Point& rPoint)
{
    PSPointOp (rPoint, "moveto");
}

void
PrinterGfx::PSLineTo (const Point& rPoint)
{
    PSPointOp (rPoint, "lineto");
}

void
PrinterGfx::PSRMoveTo (sal_Int32 nDx, sal_Int32 nDy)
{
    Point aPoint(nDx, nDy);
    PSPointOp (aPoint, "rmoveto");
}

/* get a compressed representation of the path information */

#define DEBUG_BINPATH 0

void
PrinterGfx::PSBinLineTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn)
{
#if (DEBUG_BINPATH == 1)
    PSLineTo (rCurrent);
#else
    PSBinPath (rCurrent, rOld, lineto, nColumn);
#endif
}

void
PrinterGfx::PSBinMoveTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn)
{
#if (DEBUG_BINPATH == 1)
    PSMoveTo (rCurrent);
#else
    PSBinPath (rCurrent, rOld, moveto, nColumn);
#endif
}   

void
PrinterGfx::PSBinStartPath ()
{
#if (DEBUG_BINPATH == 1)
    WritePS (mpPageBody, "% PSBinStartPath\n");
#else
    WritePS (mpPageBody, "readpath\n" );
#endif
}

void
PrinterGfx::PSBinEndPath ()
{
#if (DEBUG_BINPATH == 1)
    WritePS (mpPageBody, "% PSBinEndPath\n");
#else
    WritePS (mpPageBody, "~\n");
#endif
}

void
PrinterGfx::PSBinCurrentPath (sal_uInt32 nPoints, const Point* pPath)
{
    // create the path
    Point     aPoint (0, 0);
    sal_Int32 nColumn = 0;

    PSBinStartPath ();
    PSBinMoveTo (*pPath, aPoint, nColumn);
    for (unsigned int i = 1; i < nPoints; i++)
        PSBinLineTo (pPath[i], aPoint, nColumn);
    PSBinEndPath ();
}

void
PrinterGfx::PSBinPath (const Point& rCurrent, Point& rOld, 
                       pspath_t eType, sal_Int32& nColumn)
{
    sal_Char  pPath[48];
    sal_Int32 nChar;

    // create the hex representation of the dx and dy path shift, store the field
    // width as it is needed for the building the command
    sal_Int32 nXPrec = getAlignedHexValueOf (rCurrent.X() - rOld.X(), pPath + 1);
    sal_Int32 nYPrec = getAlignedHexValueOf (rCurrent.Y() - rOld.Y(), pPath + 1 + nXPrec);
    pPath [ 1 + nXPrec + nYPrec ] = 0;

    // build the command, it is a char with bit represention 000cxxyy 
    // c represents the char, xx and yy repr. the field width of the dx and dy shift,
    // dx and dy represent the number of bytes to read after the opcode
    sal_Char cCmd = (eType == lineto ? (sal_Char)0x00 : (sal_Char)0x10);
    switch (nYPrec)
    {
        case 2: break;
        case 4: cCmd |= 0x01;   break;
        case 6: cCmd |= 0x02;   break;
        case 8: cCmd |= 0x03;   break;
        default:    OSL_FAIL("invalid x precision in binary path");
    }
    switch (nXPrec)
    {
        case 2: break;
        case 4: cCmd |= 0x04;   break;
        case 6: cCmd |= 0x08;   break;
        case 8: cCmd |= 0x0c;   break;
        default:    OSL_FAIL("invalid y precision in binary path");
    }
    cCmd += 'A';
    pPath[0] = cCmd;

    // write the command to file, 
    // line breaking at column nMaxTextColumn (80) 
    nChar = 1 + nXPrec + nYPrec;
    if ((nColumn + nChar) > nMaxTextColumn)
    {
        sal_Int32 nSegment = nMaxTextColumn - nColumn;

        WritePS (mpPageBody, pPath, nSegment);
        WritePS (mpPageBody, "\n", 1);
        WritePS (mpPageBody, pPath + nSegment, nChar - nSegment);

        nColumn  = nChar - nSegment;
    }
    else
    {
        WritePS (mpPageBody, pPath, nChar);

        nColumn += nChar;
    }

    rOld = rCurrent;
}

void
PrinterGfx::PSScale (double fScaleX, double fScaleY)
{
    sal_Char  pScale [48];
    sal_Int32 nChar = 0;

    nChar  = psp::getValueOfDouble (pScale, fScaleX, 5);
    nChar += psp::appendStr        (" ", pScale + nChar);
    nChar += psp::getValueOfDouble (pScale + nChar, fScaleY, 5);
    nChar += psp::appendStr        (" scale\n", pScale + nChar);

    WritePS (mpPageBody, pScale);
}

/* psshowtext helper routines: draw an hex string for show/xshow */
void
PrinterGfx::PSHexString (const sal_uChar* pString, sal_Int16 nLen)
{
    sal_Char pHexString [128];
    sal_Int32 nChar = 0;

    nChar = psp::appendStr ("<", pHexString);
    for (int i = 0; i < nLen; i++)
    {
        if (nChar >= (nMaxTextColumn - 1))
        {
            nChar += psp::appendStr ("\n", pHexString + nChar);
            WritePS (mpPageBody, pHexString, nChar);
            nChar = 0;
        }
        nChar += psp::getHexValueOf ((sal_Int32)pString[i], pHexString + nChar);
    }

    nChar += psp::appendStr (">\n", pHexString + nChar);
    WritePS (mpPageBody, pHexString, nChar);
}

/* psshowtext helper routines: draw an array for xshow ps operator */
void
PrinterGfx::PSDeltaArray (const sal_Int32 *pArray, sal_Int16 nEntries)
{
    sal_Char pPSArray [128];
    sal_Int32 nChar = 0;
    
    nChar  = psp::appendStr  ("[", pPSArray + nChar);
    nChar += psp::getValueOf (pArray[0], pPSArray + nChar); 

    for (int i = 1; i < nEntries; i++)
    {
        if (nChar >= (nMaxTextColumn - 1))
        {
            nChar += psp::appendStr ("\n", pPSArray + nChar);
            WritePS (mpPageBody, pPSArray, nChar);
            nChar = 0;
        }

        nChar += psp::appendStr  (" ", pPSArray + nChar);
        nChar += psp::getValueOf (pArray[i] - pArray[i-1], pPSArray + nChar); 
    }

    nChar  += psp::appendStr (" 0]\n", pPSArray + nChar);
    WritePS (mpPageBody, pPSArray);
}

/* the DrawText equivalent, pDeltaArray may be NULL. For Type1 fonts or single byte
 * fonts in general nBytes and nGlyphs is the same. For printer resident Composite
 * fonts it may be different (these fonts may be SJIS encoded for example) */
void
PrinterGfx::PSShowText (const sal_uChar* pStr, sal_Int16 nGlyphs, sal_Int16 nBytes,
                        const sal_Int32* pDeltaArray)
{
    PSSetColor (maTextColor);
    PSSetColor ();
    PSSetFont  ();
    // rotate the user coordinate system
    if (mnTextAngle != 0)
    {
        PSGSave ();
        PSRotate (mnTextAngle);
    }

    sal_Char pBuffer[256];
    if( maVirtualStatus.mbArtBold )
    {
        sal_Int32 nLW = maVirtualStatus.mnTextWidth;
        if( nLW == 0 )
            nLW = maVirtualStatus.mnTextHeight;
        else
            nLW = nLW < maVirtualStatus.mnTextHeight ? nLW : maVirtualStatus.mnTextHeight;
        psp::getValueOfDouble( pBuffer, (double)nLW / 30.0 );
    }
    // dispatch to the drawing method
    if (pDeltaArray == NULL)
    {
        PSHexString (pStr, nBytes);
            
        if( maVirtualStatus.mbArtBold )
        {
            WritePS( mpPageBody, pBuffer );
            WritePS( mpPageBody, " bshow\n" );
        }
        else
            WritePS (mpPageBody, "show\n");
    }
    else
    {
        PSHexString (pStr, nBytes);
        PSDeltaArray (pDeltaArray, nGlyphs - 1);
        if( maVirtualStatus.mbArtBold )
        {
            WritePS( mpPageBody, pBuffer );
            WritePS( mpPageBody, " bxshow\n" );
        }
        else
            WritePS (mpPageBody, "xshow\n");
    }

    // restore the user coordinate system   
    if (mnTextAngle != 0)
        PSGRestore ();
}

void
PrinterGfx::PSComment( const sal_Char* pComment )
{
    const sal_Char* pLast = pComment;
    while( pComment && *pComment )
    {
        while( *pComment && *pComment != '\n' && *pComment != '\r' )
            pComment++;
        if( pComment - pLast > 1 )
        {
            WritePS( mpPageBody, "% ", 2 );
            WritePS( mpPageBody, pLast, pComment - pLast );
            WritePS( mpPageBody, "\n", 1 );
        }
        if( *pComment )
            pLast = ++pComment;
    }
}

sal_Bool
PrinterGfx::DrawEPS( const Rectangle& rBoundingBox, void* pPtr, sal_uInt32 nSize )
{
    if( nSize == 0 )
        return sal_True;
    if( ! mpPageBody )
        return sal_False;

    sal_Bool bSuccess = sal_False;

    // first search the BoundingBox of the EPS data
    SvMemoryStream aStream( pPtr, nSize, STREAM_READ );
    aStream.Seek( STREAM_SEEK_TO_BEGIN );
    ByteString aLine;

    ByteString aDocTitle;
    double fLeft = 0, fRight = 0, fTop = 0, fBottom = 0;
    bool bEndComments = false;
    while( ! aStream.IsEof()
           && ( ( fLeft == 0 && fRight == 0 && fTop == 0 && fBottom == 0 ) ||
                ( aDocTitle.Len() == 0 && bEndComments == false ) )
           )
    {
        aStream.ReadLine( aLine );
        if( aLine.Len() > 1 && aLine.GetChar( 0 ) == '%' )
        {
            char cChar = aLine.GetChar(1);
            if( cChar == '%' )
            {
                if( aLine.CompareIgnoreCaseToAscii( "%%BoundingBox:", 14 ) == COMPARE_EQUAL )
                {
                    aLine = WhitespaceToSpace( aLine.GetToken( 1, ':' ) );
                    if( aLine.Len() && aLine.Search( "atend" ) == STRING_NOTFOUND )
                    {
                        fLeft   = StringToDouble( GetCommandLineToken( 0, aLine ) );
                        fBottom = StringToDouble( GetCommandLineToken( 1, aLine ) );
                        fRight  = StringToDouble( GetCommandLineToken( 2, aLine ) );
                        fTop    = StringToDouble( GetCommandLineToken( 3, aLine ) );
                    }
                }
                else if( aLine.CompareIgnoreCaseToAscii( "%%Title:", 8 ) == COMPARE_EQUAL )
                    aDocTitle = WhitespaceToSpace( aLine.Copy( 8 ) );
                else if( aLine.CompareIgnoreCaseToAscii( "%%EndComments", 13 ) == COMPARE_EQUAL )
                    bEndComments = true;
            }
            else if( cChar == ' ' || cChar == '\t' || cChar == '\r' || cChar == '\n' )
                bEndComments = true;
        }
        else
            bEndComments = true;
    }

    static sal_uInt16 nEps = 0;
    if( ! aDocTitle.Len() )
        aDocTitle = ByteString::CreateFromInt32( (sal_Int32)(nEps++) );

    if( fLeft != fRight && fTop != fBottom )
    {
        double fScaleX = (double)rBoundingBox.GetWidth()/(fRight-fLeft);
        double fScaleY = -(double)rBoundingBox.GetHeight()/(fTop-fBottom);
        Point aTranslatePoint( (int)(rBoundingBox.Left()-fLeft*fScaleX),
                               (int)(rBoundingBox.Bottom()+1-fBottom*fScaleY) );
        // prepare EPS
        WritePS( mpPageBody,
                 "/b4_Inc_state save def\n"
                 "/dict_count countdictstack def\n"
                 "/op_count count 1 sub def\n"
                 "userdict begin\n"
                 "/showpage {} def\n"
                 "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin\n"
                 "10 setmiterlimit [] 0 setdash newpath\n"
                 "/languagelevel where\n"
                 "{pop languagelevel\n"
                 "1 ne\n"
                 "  {false setstrokeadjust false setoverprint\n"
                 "  } if\n"
                 "}if\n" );
        // set up clip path and scale
        BeginSetClipRegion( 1 );
        UnionClipRegion( rBoundingBox.Left(), rBoundingBox.Top(), rBoundingBox.GetWidth(), rBoundingBox.GetHeight() );
        EndSetClipRegion();
        PSTranslate( aTranslatePoint );
        PSScale( fScaleX, fScaleY );

        // DSC requires BeginDocument
        WritePS( mpPageBody, "%%BeginDocument: " );
        WritePS( mpPageBody, aDocTitle );
        WritePS( mpPageBody, "\n" );

        // write the EPS data
        sal_uInt64 nOutLength;
        mpPageBody->write( pPtr, nSize, nOutLength );
        bSuccess = nOutLength == nSize;

        // corresponding EndDocument
        if( ((char*)pPtr)[ nSize-1 ] != '\n' )
            WritePS( mpPageBody, "\n" );
        WritePS( mpPageBody, "%%EndDocument\n" );

        // clean up EPS
        WritePS( mpPageBody,
                 "count op_count sub {pop} repeat\n"
                 "countdictstack dict_count sub {end} repeat\n"
                 "b4_Inc_state restore\n" );
    }
    return bSuccess;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
