/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    faxtiff.c

Abstract:

    Functions to compress the bitmap bits using CCITT Group3 2-dimensional coding
    and output the resulting data as TIFF-F file.

Environment:

        Windows NT fax driver, kernel mode

Revision History:

        01/23/96 -davidx-
                Created it.

        mm/dd/yy -author-
                description

NOTE:

    Please refer to faxtiff.h for a description of
    the structure of our TIFF output file.

--*/

#include "faxdrv.h"
#include "faxtiff.h"
#include "faxtable.h"



BOOL
WriteData(
    PDEVDATA    pdev,
    PVOID       pbuf,
    DWORD       cbbuf
    )

/*++

Routine Description:

    Output a buffer of data to the spooler

Arguments:

    pdev - Points to our DEVDATA structure
    pbuf - Points to data buffer
    cbbuf - Number of bytes in the buffer

Return Value:

    TRUE if successful, FALSE otherwise.

--*/

{
    DWORD   cbwritten;

    //
    // Stop if the document has been cancelled.
    //

    if (pdev->flags & PDEV_CANCELLED)
        return FALSE;

    //
    // Send output to spooler directly
    //

    if (! WritePrinter(pdev->hPrinter, pbuf, cbbuf, &cbwritten) || cbbuf != cbwritten) {

        Error(("WritePrinter failed\n"));
        pdev->flags |= PDEV_CANCELLED;
        return FALSE;
    }

    pdev->fileOffset += cbbuf;
    return TRUE;
}



PDWORD
CalcXposeMatrix(
    VOID
    )

/*++

Routine Description:

    Generate the transpose matrix for rotating landscape bitmaps

Arguments:

    NONE

Return Value:

    Pointer to the generated transpose matrix
    NULL if there is an error

--*/

{
    static DWORD templateData[16] = {

        /* 0000 */  0x00000000,
        /* 0001 */  0x00000001,
        /* 0010 */  0x00000100,
        /* 0011 */  0x00000101,
        /* 0100 */  0x00010000,
        /* 0101 */  0x00010001,
        /* 0110 */  0x00010100,
        /* 0111 */  0x00010101,
        /* 1000 */  0x01000000,
        /* 1001 */  0x01000001,
        /* 1010 */  0x01000100,
        /* 1011 */  0x01000101,
        /* 1100 */  0x01010000,
        /* 1101 */  0x01010001,
        /* 1110 */  0x01010100,
        /* 1111 */  0x01010101
    };

    PDWORD  pdwXpose, pTemp;
    INT     index;

    //
    // First check if the transpose matrix has been generated already
    //

    if (pdwXpose = MemAlloc(sizeof(DWORD) * 2 * (1 << BYTEBITS))) {

        for (index=0, pTemp=pdwXpose; index < (1 << BYTEBITS); index++, pTemp++) {

            pTemp[0] = templateData[index >> 4];
            pTemp[1 << BYTEBITS] = templateData[index & 0xf];
        }
    }

    return pdwXpose;
}



BOOL
OutputPageBitmap(
    PDEVDATA    pdev,
    PBYTE       pBitmapData
    )

/*++

Routine Description:

    Output a completed page bitmap to the spooler

Arguments:

    pdev - Points to our DEVDATA structure
    pBitmapData - Points to bitmap data

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    LONG    bmpWidth, bmpHeight;
    BOOL    result;
    DWORD   compressedBytes;

    Verbose(("Sending page %d...\n", pdev->pageCount));
    Assert(pdev->pCompBits == NULL);

    //
    // For portrait output, encode the entire bitmap in one shot
    // For landscape output, we need to rotate the bitmap here:
    //  Generate the transpose matrix and allocate a
    //  temporary buffer large enough to hold 8 scanlines
    //

    if (IsLandscapeMode(pdev)) {

        bmpWidth = pdev->imageSize.cy;
        bmpHeight = pdev->imageSize.cx;

    } else {

        bmpWidth = pdev->imageSize.cx;
        bmpHeight = pdev->imageSize.cy;
    }

    //
    // Initialize fax encodier
    //

    if (! InitFaxEncoder(pdev, bmpWidth, bmpHeight))
        return FALSE;

    if (! IsLandscapeMode(pdev)) {

        LONG    dwordCount;
        PDWORD  pBits;

        //
        // Invert the entire page bitmap in memory
        //

        Assert(bmpWidth % DWORDBITS == 0);
        dwordCount = (bmpWidth * bmpHeight) / DWORDBITS;
        pBits = (PDWORD) pBitmapData;

        while (dwordCount--)
            *pBits++ ^= 0xffffffff;

        //
        // Compress the page bitmap
        //

        result = EncodeFaxData(pdev, pBitmapData, bmpWidth, bmpHeight);

        //
        // Restore the original page bitmap
        //

        dwordCount = (bmpWidth * bmpHeight) / DWORDBITS;
        pBits = (PDWORD) pBitmapData;

        while (dwordCount--)
            *pBits++ ^= 0xffffffff;

        if (! result) {

            FreeCompBitsBuffer(pdev);
            return FALSE;
        }

    } else {

        register PDWORD pdwXposeHigh, pdwXposeLow;
        register DWORD  dwHigh, dwLow;
        PBYTE           pBuffer, pbCol;
        LONG            deltaNew;

        //
        // Calculate the transpose matrix for fast bitmap rotation
        //

        if (!(pdwXposeHigh = CalcXposeMatrix()) || !(pBuffer = MemAllocZ(bmpWidth))) {

            MemFree(pdwXposeHigh);
            FreeCompBitsBuffer(pdev);
            return FALSE;
        }

        pdwXposeLow = pdwXposeHigh + (1 << BYTEBITS);

        //
        // During each iteration thru the following loop, we will process
        // one byte column and generate 8 rotated scanlines.
        //

        Assert(bmpHeight % BYTEBITS == 0);
        Assert(bmpWidth  % DWORDBITS == 0);

        deltaNew = bmpWidth / BYTEBITS;
        pbCol = pBitmapData + (bmpHeight / BYTEBITS - 1);

        do {

            PBYTE   pbWrite = pBuffer;
            PBYTE   pbTemp = pbCol;
            LONG    loopCount = deltaNew;

            while (loopCount--) {

                //
                // Rotate the next 8 bytes in the current column
                // Unroll the loop here in hopes of faster execution
                //

                dwHigh = pdwXposeHigh[*pbTemp];
                dwLow  = pdwXposeLow[*pbTemp];
                pbTemp += pdev->lineOffset;

                dwHigh = (dwHigh << 1) | pdwXposeHigh[*pbTemp];
                dwLow  = (dwLow  << 1) | pdwXposeLow[*pbTemp];
                pbTemp += pdev->lineOffset;

                dwHigh = (dwHigh << 1) | pdwXposeHigh[*pbTemp];
                dwLow  = (dwLow  << 1) | pdwXposeLow[*pbTemp];
                pbTemp += pdev->lineOffset;

                dwHigh = (dwHigh << 1) | pdwXposeHigh[*pbTemp];
                dwLow  = (dwLow  << 1) | pdwXposeLow[*pbTemp];
                pbTemp += pdev->lineOffset;

                dwHigh = (dwHigh << 1) | pdwXposeHigh[*pbTemp];
                dwLow  = (dwLow  << 1) | pdwXposeLow[*pbTemp];
                pbTemp += pdev->lineOffset;

                dwHigh = (dwHigh << 1) | pdwXposeHigh[*pbTemp];
                dwLow  = (dwLow  << 1) | pdwXposeLow[*pbTemp];
                pbTemp += pdev->lineOffset;

                dwHigh = (dwHigh << 1) | pdwXposeHigh[*pbTemp];
                dwLow  = (dwLow  << 1) | pdwXposeLow[*pbTemp];
                pbTemp += pdev->lineOffset;

                dwHigh = (dwHigh << 1) | pdwXposeHigh[*pbTemp];
                dwLow  = (dwLow  << 1) | pdwXposeLow[*pbTemp];
                pbTemp += pdev->lineOffset;

                //
                // Invert black and white pixel polarity
                //

                dwHigh ^= 0xffffffff;
                dwLow  ^= 0xffffffff;

                //
                // Distribute the resulting byte to 8 separate scanlines
                //

                *pbWrite = (BYTE) dwLow;
                pbWrite += deltaNew;

                *pbWrite = (BYTE) (dwLow >> BYTEBITS);
                pbWrite += deltaNew;

                *pbWrite = (BYTE) (dwLow >> BYTEBITS*2);
                pbWrite += deltaNew;

                *pbWrite = (BYTE) (dwLow >> BYTEBITS*3);
                pbWrite += deltaNew;

                *pbWrite = (BYTE) dwHigh;
                pbWrite += deltaNew;

                *pbWrite = (BYTE) (dwHigh >> BYTEBITS);
                pbWrite += deltaNew;

                *pbWrite = (BYTE) (dwHigh >> BYTEBITS*2);
                pbWrite += deltaNew;

                *pbWrite = (BYTE) (dwHigh >> BYTEBITS*3);
                pbWrite -= (deltaNew * BYTEBITS - deltaNew - 1);
            }

            //
            // Encode the next band of scanlines
            //

            if (! EncodeFaxData(pdev, pBuffer, bmpWidth, BYTEBITS)) {

                MemFree(pdwXposeHigh);
                MemFree(pBuffer);
                FreeCompBitsBuffer(pdev);
                return FALSE;
            }

        } while (pbCol-- != pBitmapData);

        MemFree(pdwXposeHigh);
        MemFree(pBuffer);
    }

    //
    // Output EOB (two EOLs) after the last scanline
    // and make sure the compressed data is WORD aligned
    //

    OutputBits(pdev, EOL_LENGTH, EOL_CODE);
    OutputBits(pdev, EOL_LENGTH, EOL_CODE);
    FlushBits(pdev);

    if ((compressedBytes = (DWORD)(pdev->pCompBufPtr - pdev->pCompBits)) & 1) {

        *pdev->pCompBufPtr++ = 0;
        compressedBytes++;
    }

    //
    // Output the IFD for the previous page and generate the IFD for the current page
    // Output the compressed bitmap data
    //

    result = WriteTiffIFD(pdev, bmpWidth, bmpHeight, compressedBytes) &&
             WriteTiffBits(pdev, pdev->pCompBits, compressedBytes);

    FreeCompBitsBuffer(pdev);

    return result;
}



INT
FindWhiteRun(
    PBYTE       pbuf,
    INT         startBit,
    INT         stopBit
    )

/*++

Routine Description:

    Find the next span of white pixels on the specified line

Arguments:

    pbuf - Points to uncompressed pixel data for the current line
    startBit - Starting bit index
    stopBit - Last bit index

Return Value:

    Length of the next run of white pixels

--*/

{
    static const BYTE WhiteRuns[256] = {

        8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };

    INT run, bits, n;

    pbuf += (startBit >> 3);
    if ((bits = stopBit-startBit) <= 0)
        return 0;

    //
    // Take care of the case where starting bit index is not a multiple of 8
    //

    if (n = (startBit & 7)) {

        run = WhiteRuns[(*pbuf << n) & 0xff];
        if (run > BYTEBITS-n)
            run = BYTEBITS-n;
        if (n+run < BYTEBITS)
            return run;
        bits -= run;
        pbuf++;

    } else
        run = 0;

    //
    // Look for consecutive DWORD value = 0
    //

    if (bits >= DWORDBITS * 2) {

        PDWORD  pdw;

        //
        // Align to a DWORD boundary first
        //

        while ((ULONG_PTR) pbuf & 3) {

            if (*pbuf != 0)
                return run + WhiteRuns[*pbuf];

            run += BYTEBITS;
            bits -= BYTEBITS;
            pbuf++;
        }

        pdw = (PDWORD) pbuf;

        while (bits >= DWORDBITS && *pdw == 0) {

            pdw++;
            run += DWORDBITS;
            bits -= DWORDBITS;
        }

        pbuf = (PBYTE) pdw;
    }

    //
    // Look for consecutive BYTE value = 0
    //

    while (bits >= BYTEBITS) {

        if (*pbuf != 0)
            return run + WhiteRuns[*pbuf];

        pbuf++;
        run += BYTEBITS;
        bits -= BYTEBITS;
    }

    //
    // Count the number of white pixels in the last byte
    //

    if (bits > 0)
        run += WhiteRuns[*pbuf];

    return run;
}



INT
FindBlackRun(
    PBYTE       pbuf,
    INT         startBit,
    INT         stopBit
    )

/*++

Routine Description:

    Find the next span of black pixels on the specified line

Arguments:

    pbuf - Points to uncompressed pixel data for the current line
    startBit - Starting bit index
    stopBit - Last bit index

Return Value:

    Length of the next run of black pixels

--*/

{
    static const BYTE BlackRuns[256] = {

        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8
    };

    INT run, bits, n;

    pbuf += (startBit >> 3);
    if ((bits = stopBit-startBit) <= 0)
        return 0;

    //
    // Take care of the case where starting bit index is not a multiple of 8
    //

    if (n = (startBit & 7)) {

        run = BlackRuns[(*pbuf << n) & 0xff];
        if (run > BYTEBITS-n)
            run = BYTEBITS-n;
        if (n+run < BYTEBITS)
            return run;
        bits -= run;
        pbuf++;

    } else
        run = 0;

    //
    // Look for consecutive DWORD value = 0xffffffff
    //

    if (bits >= DWORDBITS * 2) {

        PDWORD  pdw;

        //
        // Align to a DWORD boundary first
        //

        while ((ULONG_PTR) pbuf & 3) {

            if (*pbuf != 0xff)
                return run + BlackRuns[*pbuf];

            run += BYTEBITS;
            bits -= BYTEBITS;
            pbuf++;
        }

        pdw = (PDWORD) pbuf;

        while (bits >= DWORDBITS && *pdw == 0xffffffff) {

            pdw++;
            run += DWORDBITS;
            bits -= DWORDBITS;
        }

        pbuf = (PBYTE) pdw;
    }

    //
    // Look for consecutive BYTE value = 0xff
    //

    while (bits >= BYTEBITS) {

        if (*pbuf != 0xff)
            return run + BlackRuns[*pbuf];

        pbuf++;
        run += BYTEBITS;
        bits -= BYTEBITS;
    }

    //
    // Count the number of white pixels in the last byte
    //

    if (bits > 0)
        run += BlackRuns[*pbuf];

    return run;
}



VOID
OutputRun(
    PDEVDATA    pdev,
    INT         run,
    PCODETABLE  pCodeTable
    )

/*++

Routine Description:

    Output a single run (black or white) using the specified code table

Arguments:

    pdev - Points to our DEVDATA structure
    run - Specifies the length of the run
    pCodeTable - Specifies the code table to use

Return Value:

    NONE

--*/

{
    PCODETABLE  pTableEntry;

    //
    // Use make-up code word for 2560 for any runs of at least 2624 pixels
    // This is currently not necessary for us since our scanlines always
    // have 1728 pixels.
    //

    while (run >= 2624) {

        pTableEntry = pCodeTable + (63 + (2560 >> 6));
        OutputBits(pdev, pTableEntry->length, pTableEntry->code);
        run -= 2560;
    }

    //
    // Use appropriate make-up code word if the run is longer than 63 pixels
    //

    if (run >= 64) {

        pTableEntry = pCodeTable + (63 + (run >> 6));
        OutputBits(pdev, pTableEntry->length, pTableEntry->code);
        run &= 0x3f;
    }

    //
    // Output terminating code word
    //

    OutputBits(pdev, pCodeTable[run].length, pCodeTable[run].code);
}



#ifdef USE1D

VOID
OutputEOL(
    PDEVDATA    pdev
    )

/*++

Routine Description:

    Output EOL code at the beginning of each scanline

Arguments:

    pdev - Points to our DEVDATA structure

Return Value:

    NONE

--*/

{
    DWORD   length, code;

    //
    // EOL code word always ends on a byte boundary
    //

    code = EOL_CODE;
    length = EOL_LENGTH + ((pdev->bitcnt - EOL_LENGTH) & 7);
    OutputBits(pdev, length, code);
}


BOOL
EncodeFaxData(
    PDEVDATA    pdev,
    PBYTE       plinebuf,
    INT         lineWidth,
    INT         lineCount
    )

/*++

Routine Description:

    Compress the specified number of scanlines

Arguments:

    pdev - Points to our DEVDATA structure
    plinebuf - Points to scanline data to be compressed
    lineWidth - Scanline width in pixels
    lineCount - Number of scanlines

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    INT     delta = lineWidth / BYTEBITS;
    INT     bitIndex, run;

    while (lineCount--) {

        //
        // Make sure the compressed bitmap buffer doesn't overflow
        //

        if ((pdev->pCompBufPtr >= pdev->pCompBufMark) && !GrowCompBitsBuffer(pdev, delta))
            return FALSE;

        //
        // Output byte-aligned EOL code
        //

        OutputEOL(pdev);

        //
        // Use 1-dimensional encoding scheme
        //

        bitIndex = 0;

        while (TRUE) {

            //
            // Code white run
            //

            run = FindWhiteRun(plinebuf, bitIndex, lineWidth);
            OutputRun(pdev, run, WhiteRunCodes);

            if ((bitIndex += run) >= lineWidth)
                break;

            //
            // Code black run
            //

            run = FindBlackRun(plinebuf, bitIndex, lineWidth);
            OutputRun(pdev, run, BlackRunCodes);

            if ((bitIndex += run) >= lineWidth)
                break;
        }

        //
        // Move on to the next scanline
        //

        plinebuf += delta;
    }

    return TRUE;
}



#else //!USE1D

BOOL
EncodeFaxData(
    PDEVDATA    pdev,
    PBYTE       plinebuf,
    INT         lineWidth,
    INT         lineCount
    )

/*++

Routine Description:

    Compress the specified number of scanlines

Arguments:

    pdev - Points to our DEVDATA structure
    plinebuf - Points to scanline data to be compressed
    lineWidth - Scanline width in pixels
    lineCount - Number of scanlines

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    INT     delta = lineWidth / BYTEBITS;
    INT     a0, a1, a2, b1, b2, distance;
    PBYTE   prefline = pdev->prefline;

    Assert(lineWidth % BYTEBITS == 0);

    while (lineCount--) {

        //
        // Make sure the compressed bitmap buffer doesn't overflow
        //

        if ((pdev->pCompBufPtr >= pdev->pCompBufMark) && !GrowCompBitsBuffer(pdev, delta))
            return FALSE;

        //
        // Use 2-dimensional encoding scheme
        //

        a0 = 0;
        a1 = GetBit(plinebuf, 0) ? 0 : NextChangingElement(plinebuf, 0, lineWidth, 0);
        b1 = GetBit(prefline, 0) ? 0 : NextChangingElement(prefline, 0, lineWidth, 0);

        while (TRUE) {

            b2 = (b1 >= lineWidth) ? lineWidth :
                    NextChangingElement(prefline, b1, lineWidth, GetBit(prefline, b1));

            if (b2 < a1) {

                //
                // Pass mode
                //

                OutputBits(pdev, PASSCODE_LENGTH, PASSCODE);
                a0 = b2;

            } else if ((distance = a1 - b1) <= 3 && distance >= -3) {

                //
                // Vertical mode
                //

                OutputBits(pdev, VertCodes[distance+3].length, VertCodes[distance+3].code);
                a0 = a1;

            } else {

                //
                // Horizontal mode
                //

                a2 = (a1 >= lineWidth) ? lineWidth :
                        NextChangingElement(plinebuf, a1, lineWidth, GetBit(plinebuf, a1));

                OutputBits(pdev, HORZCODE_LENGTH, HORZCODE);

                if (a1 != 0 && GetBit(plinebuf, a0)) {

                    OutputRun(pdev, a1-a0, BlackRunCodes);
                    OutputRun(pdev, a2-a1, WhiteRunCodes);

                } else {

                    OutputRun(pdev, a1-a0, WhiteRunCodes);
                    OutputRun(pdev, a2-a1, BlackRunCodes);
                }

                a0 = a2;
            }

            if (a0 >= lineWidth)
                break;

            a1 = NextChangingElement(plinebuf, a0, lineWidth, GetBit(plinebuf, a0));
            b1 = NextChangingElement(prefline, a0, lineWidth, !GetBit(plinebuf, a0));
            b1 = NextChangingElement(prefline, b1, lineWidth, GetBit(plinebuf, a0));
        }

        //
        // Move on to the next scanline
        //

        prefline = plinebuf;
        plinebuf += delta;
    }

    //
    // Remember the last line as a reference
    //

    CopyMemory(pdev->prefline, prefline, delta);

    return TRUE;
}

#endif //!USE1D



//
// IFD entries we generate for each page
//

WORD FaxIFDTags[NUM_IFD_ENTRIES] = {

    TIFFTAG_NEWSUBFILETYPE,
    TIFFTAG_IMAGEWIDTH,
    TIFFTAG_IMAGEHEIGHT,
    TIFFTAG_BITSPERSAMPLE,
    TIFFTAG_COMPRESSION,
    TIFFTAG_PHOTOMETRIC,
    TIFFTAG_FILLORDER,
    TIFFTAG_STRIPOFFSETS,
    TIFFTAG_SAMPLESPERPIXEL,
    TIFFTAG_ROWSPERSTRIP,
    TIFFTAG_STRIPBYTECOUNTS,
    TIFFTAG_XRESOLUTION,
    TIFFTAG_YRESOLUTION,
#ifdef USE1D
    TIFFTAG_G3OPTIONS,
#else
    TIFFTAG_G4OPTIONS,
#endif
    TIFFTAG_RESUNIT,
    TIFFTAG_PAGENUMBER,
    TIFFTAG_SOFTWARE,
    TIFFTAG_CLEANFAXDATA,
};

#define SoftwareStr "Windows NT Fax Driver"

static FAXIFD FaxIFDTemplate = {

    0,
    NUM_IFD_ENTRIES,

    {
        { TIFFTAG_NEWSUBFILETYPE, TIFFTYPE_LONG, 1, SUBFILETYPE_PAGE },
        { TIFFTAG_IMAGEWIDTH, TIFFTYPE_LONG, 1, 0 },
        { TIFFTAG_IMAGEHEIGHT, TIFFTYPE_LONG, 1, 0 },
        { TIFFTAG_BITSPERSAMPLE, TIFFTYPE_SHORT, 1, 1 },
#ifdef USE1D
        { TIFFTAG_COMPRESSION, TIFFTYPE_SHORT, 1, COMPRESSION_G3FAX },
#else
        { TIFFTAG_COMPRESSION, TIFFTYPE_SHORT, 1, COMPRESSION_G4FAX },
#endif
        { TIFFTAG_PHOTOMETRIC, TIFFTYPE_SHORT, 1, PHOTOMETRIC_WHITEIS0 },
#ifdef USELSB
        { TIFFTAG_FILLORDER, TIFFTYPE_SHORT, 1, FILLORDER_LSB },
#else
        { TIFFTAG_FILLORDER, TIFFTYPE_SHORT, 1, FILLORDER_MSB },
#endif
        { TIFFTAG_STRIPOFFSETS, TIFFTYPE_LONG, 1, 0 },
        { TIFFTAG_SAMPLESPERPIXEL, TIFFTYPE_SHORT, 1, 1 },
        { TIFFTAG_ROWSPERSTRIP, TIFFTYPE_LONG, 1, 0 },
        { TIFFTAG_STRIPBYTECOUNTS, TIFFTYPE_LONG, 1, 0 },
        { TIFFTAG_XRESOLUTION, TIFFTYPE_RATIONAL, 1, 0 },
        { TIFFTAG_YRESOLUTION, TIFFTYPE_RATIONAL, 1, 0 },
#ifdef USE1D
        { TIFFTAG_G3OPTIONS, TIFFTYPE_LONG, 1, G3_ALIGNEOL },
#else
        { TIFFTAG_G4OPTIONS, TIFFTYPE_LONG, 1, 0 },
#endif
        { TIFFTAG_RESUNIT, TIFFTYPE_SHORT, 1, RESUNIT_INCH },
        { TIFFTAG_PAGENUMBER, TIFFTYPE_SHORT, 2, 0 },
        { TIFFTAG_SOFTWARE, TIFFTYPE_ASCII, sizeof(SoftwareStr)+1, 0 },
        { TIFFTAG_CLEANFAXDATA, TIFFTYPE_SHORT, 1, 0 },
    },

    0,
    DRIVER_SIGNATURE,
    TIFFF_RES_X,
    1,
    TIFFF_RES_Y,
    1,
    SoftwareStr
};



BOOL
OutputDocTrailer(
    PDEVDATA    pdev
    )

/*++

Routine Description:

    Output document trailer information to the spooler

Arguments:

    pdev - Points to our DEVDATA structure

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    PFAXIFD pFaxIFD = pdev->pFaxIFD;

    if (pFaxIFD == NULL || pdev->pageCount == 0)
        return TRUE;

    //
    // Output the IFD for the last page of the document
    //

    pFaxIFD->nextIFDOffset = pFaxIFD->filler = 0;

    return WriteData(pdev, pFaxIFD, sizeof(FAXIFD));
}



BOOL
WriteTiffIFD(
    PDEVDATA    pdev,
    LONG        bmpWidth,
    LONG        bmpHeight,
    DWORD       compressedBytes
    )

/*++

Routine Description:

    Output the IFD for the previous page and generate the IFD for the current page

Arguments:

    pdev - Points to our DEVDATA structure
    bmpWidth, bmpHeight - Width and height of the bitmap image
    compressedBytes - Size of compressed bitmap data

Return Value:

    TRUE if successful, FALSE otherwise

NOTE:

    Please refer to faxtiff.h for a description of
    the structure of our TIFF output file.

--*/

{
    PFAXIFD pFaxIFD = pdev->pFaxIFD;
    ULONG_PTR   offset;
    BOOL    result = TRUE;

    //
    // Create the IFD data structure if necessary
    //

    if (pFaxIFD == NULL) {

        if (! (pFaxIFD = MemAlloc(sizeof(FAXIFD))))
            return FALSE;

        pdev->pFaxIFD = pFaxIFD;
        memcpy(pFaxIFD, &FaxIFDTemplate, sizeof(FAXIFD));

        #if DBG

        for (offset=0; offset < NUM_IFD_ENTRIES; offset++) {

            Assert(pFaxIFD->ifd[offset].tag == FaxIFDTags[offset]);
        }

        #endif
    }

    if (pdev->pageCount <= 1) {

        //
        // If this is the very first page, there is no previous IFD.
        // Output the TIFF file header instead.
        //

        TIFFFILEHEADER *pTiffFileHeader;

        pdev->fileOffset = 0;

        if (pTiffFileHeader = MemAlloc(sizeof(TIFFFILEHEADER))) {

            pTiffFileHeader->magic1 = TIFF_MAGIC1;
            pTiffFileHeader->magic2 = TIFF_MAGIC2;
            pTiffFileHeader->signature = DRIVER_SIGNATURE;
            pTiffFileHeader->firstIFD = sizeof(TIFFFILEHEADER) +
                                        compressedBytes +
                                        offsetof(FAXIFD, wIFDEntries);

            result = WriteData(pdev, pTiffFileHeader, sizeof(TIFFFILEHEADER));
            MemFree(pTiffFileHeader);

        } else {

            Error(("Memory allocation failed\n"));
            result = FALSE;
        }

    } else {

        //
        // Not the first page of the document
        // Output the IFD for the previous page
        //

        pFaxIFD->nextIFDOffset = pdev->fileOffset + compressedBytes + sizeof(FAXIFD) +
                                 offsetof(FAXIFD, wIFDEntries);

        result = WriteData(pdev, pFaxIFD, sizeof(FAXIFD));
    }

    //
    // Generate the IFD for the current page
    //

    offset = pdev->fileOffset;

    pFaxIFD->ifd[IFD_PAGENUMBER].value = MAKELONG(pdev->pageCount-1, 0);
    pFaxIFD->ifd[IFD_IMAGEWIDTH].value = bmpWidth;
    pFaxIFD->ifd[IFD_IMAGEHEIGHT].value = bmpHeight;
    pFaxIFD->ifd[IFD_ROWSPERSTRIP].value = bmpHeight;
    pFaxIFD->ifd[IFD_STRIPBYTECOUNTS].value = compressedBytes;
    pFaxIFD->ifd[IFD_STRIPOFFSETS].value = (ULONG)offset;
    offset += compressedBytes;

    pFaxIFD->ifd[IFD_XRESOLUTION].value = (ULONG)offset + offsetof(FAXIFD, xresNum);
    pFaxIFD->ifd[IFD_YRESOLUTION].value = (ULONG)offset + offsetof(FAXIFD, yresNum);
    pFaxIFD->ifd[IFD_SOFTWARE].value = (ULONG)offset + offsetof(FAXIFD, software);

    pFaxIFD->yresNum = (pdev->dm.dmPublic.dmYResolution == FAXRES_VERTDRAFT) ?
                            TIFFF_RES_Y_DRAFT :
                            TIFFF_RES_Y;

    return result;
}



BOOL
WriteTiffBits(
    PDEVDATA    pdev,
    PBYTE       pCompBits,
    DWORD       compressedBytes
    )

/*++

Routine Description:

    Output the compressed bitmap data to the spooler

Arguments:

    pdev - Points to our DEVDATA structure
    pCompBits - Points to a buffer containing compressed bitmap data
    compressedBytes - Size of compressed bitmap data

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

#define OUTPUT_BUFFER_SIZE  4096

{
    PBYTE   pBuffer;
    DWORD   bytesToWrite;

#ifndef USERMODE_DRIVER
    //
    // Since we allocated the compressed bitmap data buffer from
    // the user mode memory space, we couldn't passed it directly
    // to EngWritePrinter.
    //
    // Here we allocate a temporary buffer from kernel mode memory
    // space and output the compressed data one buffer at a time.
    //

    if (! (pBuffer = MemAlloc(OUTPUT_BUFFER_SIZE))) {

        Error(("Memory allocation failed\n"));
        return FALSE;
    }

    while (compressedBytes > 0) {

        bytesToWrite = min(compressedBytes, OUTPUT_BUFFER_SIZE);
        CopyMemory(pBuffer, pCompBits, bytesToWrite);

        if (! WriteData(pdev, pBuffer, bytesToWrite)) {

            MemFree(pBuffer);
            return FALSE;
        }

        pCompBits += bytesToWrite;
        compressedBytes -= bytesToWrite;
    }

    MemFree(pBuffer);
    return TRUE;
#else
    //
    // just dump the data in OUTPUT_BUFFER_SIZE increments
    //
    pBuffer = pCompBits;
    while (compressedBytes > 0) {
        bytesToWrite = min(compressedBytes, OUTPUT_BUFFER_SIZE);

        if (! WriteData(pdev, pBuffer, bytesToWrite) ) {
            return FALSE;
        }

        pBuffer += bytesToWrite;
        compressedBytes -= bytesToWrite;
    }

    return TRUE;    
#endif

}



BOOL
GrowCompBitsBuffer(
    PDEVDATA    pdev,
    LONG        scanlineSize
    )

/*++

Routine Description:

    Enlarge the buffer for holding the compressed bitmap data

Arguments:

    pdev - Points to our DEVDATA structure
    scanlineSize - Number of uncompressed bytes per scanline

Return Value:

    TRUE if successful, FALSE if memory allocation fails

--*/

{
    DWORD   oldBufferSize;
    PBYTE   pNewBuffer;

    //
    // Allocate a new buffer which is one increment larger than existing one
    //

    oldBufferSize = pdev->pCompBits ? pdev->compBufSize : 0;
    pdev->compBufSize = oldBufferSize + pdev->compBufInc;

    if (! (pNewBuffer = MemAlloc(pdev->compBufSize))) {

        Error(("MemAlloc failed\n"));
        FreeCompBitsBuffer(pdev);
        return FALSE;
    }

    if (pdev->pCompBits) {

        //
        // Growing an existing buffer
        //

        Warning(("Growing compressed bitmap buffer: %d -> %d\n", oldBufferSize, pdev->compBufSize));

        memcpy(pNewBuffer, pdev->pCompBits, oldBufferSize);
        pdev->pCompBufPtr = pNewBuffer + (pdev->pCompBufPtr - pdev->pCompBits);
        MemFree(pdev->pCompBits);
        pdev->pCompBits = pNewBuffer;

    } else {

        //
        // First time allocation
        //

        pdev->pCompBufPtr = pdev->pCompBits = pNewBuffer;
    }

    //
    // Set a high-water mark to about 4 scanlines before the end of the buffer
    //

    pdev->pCompBufMark = pdev->pCompBits + (pdev->compBufSize - 4*scanlineSize);

    return TRUE;
}



VOID
FreeCompBitsBuffer(
    PDEVDATA    pdev
    )

/*++

Routine Description:

    Free the buffer for holding the compressed bitmap data

Arguments:

    pdev - Points to our DEVDATA structure

Return Value:

    NONE

--*/

{
    if (pdev->pCompBits) {

        MemFree(pdev->prefline);
        MemFree(pdev->pCompBits);
        pdev->pCompBits = pdev->pCompBufPtr = NULL;
        pdev->compBufSize = 0;
    }
}



BOOL
InitFaxEncoder(
    PDEVDATA    pdev,
    LONG        bmpWidth,
    LONG        bmpHeight
    )

/*++

Routine Description:

    Initialize the fax encoder

Arguments:

    pdev - Points to our DEVDATA structure
    bmpWidth, bmpHeight - Width and height of the bitmap

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    //
    // Calculate the increment in which to enlarge the compressed bits buffer:
    //  about 1/4 of the uncompressed bitmap buffer
    //

    bmpWidth /= BYTEBITS;
    pdev->compBufInc = bmpWidth * bmpHeight / 4;

    //
    // Allocate the initial buffer
    //

    if (! (pdev->prefline = MemAllocZ(bmpWidth)) ||
        ! GrowCompBitsBuffer(pdev, bmpWidth))
    {
        MemFree(pdev->prefline);
        return FALSE;
    }

    //
    // Perform other initialization of fax encoder
    //

    pdev->bitdata = 0;
    pdev->bitcnt = DWORDBITS;

    return TRUE;
}