/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    textout.c

Abstract:

    Implementation of DDI entry points DrvTextOut, DrvGetGlyphMode,
    and their related functions.

[Environment:]

    Win32 subsystem, PostScript driver

Revision History:

    02/12/91 -kentse-
        Created it.

    08/08/95 -davidx-
        Move hardcoded PS code into the resource.
        Clean up.

    08/18/95 -davidx-
        Option to do more accurate font substitution.
        Fast text output when characters are individually placed.

    mm/dd/yy -author-
        description

--*/

#include "pscript.h"
#include "type1.h"

BOOL DrvCommonPath(PDEVDATA, PATHOBJ *, BOOL, BOOL *, XFORMOBJ *,
                   BRUSHOBJ *, PPOINTL, PLINEATTRS);

BOOL DrawGlyphs(PDEVDATA,DWORD,GLYPHPOS*,FONTOBJ*,STROBJ*,TEXTDATA*,PWSTR);
VOID CharBitmap(PDEVDATA, GLYPHPOS *);
BOOL SetFontRemap(PDEVDATA, DWORD);
BOOL QueryFontRemap(PDEVDATA, DWORD);
BOOL ShouldWeRemap(PDEVDATA, ULONG, GLYPHPOS *, PWSTR, BOOL);
VOID Add1Font(PDEVDATA, FONTOBJ *);
BOOL SelectFont(PDEVDATA, FONTOBJ *, STROBJ *, TEXTDATA *);
VOID OutputGlyphBitmap(PDEVDATA, GLYPHBITS *);
BOOL CanBeSubstituted(STROBJ *);
DWORD SubstituteIFace(PDEVDATA, FONTOBJ *);



BOOL
DrvTextOut(
    SURFOBJ  *pso,
    STROBJ   *pstro,
    FONTOBJ  *pfo,
    CLIPOBJ  *pco,
    PRECTL   prclExtra,
    PRECTL   prclOpaque,
    BRUSHOBJ *pboFore,
    BRUSHOBJ *pboOpaque,
    PPOINTL  pptlOrg,
    MIX      mix
    )

/*++

Routine Description:

    Implementation of DDI entry point DrvTextOut.
    Please refer to DDK documentation for more details.

--*/

{
    PDEVDATA    pdev;
    DWORD       cGlyphs;
    BOOL        gsaved;
    TEXTDATA    tdata;

    TRACEDDIENTRY("DrvTextOut");

    // Validate input parameters

    if (pso == NULL || pstro == NULL || pfo == NULL) {
        DBGMSG(DBG_LEVEL_ERROR, "Invalid parameters.\n");
        SETLASTERROR(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    // Get the pointer to our DEVDATA structure and validate it

    pdev = (PDEVDATA) pso->dhpdev;

    if (! bValidatePDEV(pdev)) {
        DBGERRMSG("bValidatePDEV");
        SETLASTERROR(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (pdev->dwFlags & PDEV_CANCELDOC)
        return FALSE;

    if (pdev->dwFlags & PDEV_IGNORE_GDI)
        return TRUE;

    // Find out if we ever get any bitmap fonts

    if (! (pfo->flFontType & (DEVICE_FONTTYPE | TRUETYPE_FONTTYPE))) {
        DBGMSG(DBG_LEVEL_WARNING, "Bitmap font type\n");
    }

    // Make sure we have been given a valid font.

    if ((pfo->flFontType & DEVICE_FONTTYPE) &&
        ! ValidPsFontIndex(pdev, pfo->iFace))
    {
        DBGERRMSG("ValidPsFontIndex");
        SETLASTERROR(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    // Initialize our TEXTDATA structure.

    tdata.flAccel = pstro->flAccel;
    tdata.bFontSubstitution = FALSE;
    tdata.iFace = pfo->iFace;

    // Check if we need to substitute TrueType font with device font

    if ((pfo->flFontType & TRUETYPE_FONTTYPE) &&
        (pdev->cDeviceFonts > 0) &&
        (pdev->dm.dmPrivate.dwFlags & PSDEVMODE_FONTSUBST) &&
        CanBeSubstituted(pstro))
    {
        DWORD newface = SubstituteIFace(pdev, pfo);

        if (newface > 0 && newface <= pdev->cDeviceFonts) {
            tdata.bFontSubstitution = TRUE;
            tdata.iFace = newface;
        }
    }

    tdata.bDeviceFont = tdata.bFontSubstitution || (pfo->flFontType & DEVICE_FONTTYPE);
    tdata.bSimItalic = tdata.bDeviceFont && (pfo->flFontType & FO_SIM_ITALIC);
    tdata.doReencode = FALSE;

    // Select the current font in the printer from the given FONTOBJ.

    if (! SelectFont(pdev, pfo, pstro, &tdata)) {
        DBGERRMSG("SelectFont");
        return FALSE;
    }

    // Handle the clip object passed in.

    gsaved =  bDoClipObj(pdev, pco, NULL, NULL);

    // Output the opaque rectangle if necessary. This is a background
    // rectangle that goes behind the foreground text, therefore, send
    // it to the printer before the text.

    if (prclOpaque != NULL) {

        ps_newpath(pdev);
        ps_box(pdev, prclOpaque, FALSE);

        if (! ps_patfill(pdev, pso, (FLONG) FP_WINDINGMODE, pboOpaque,
                         pptlOrg, MixToRop4(mix), prclOpaque, FALSE, FALSE))
        {
            DBGERRMSG("ps_patfill");
            return FALSE;
        }
    }

    // Output the text color to draw with.

    if (pboFore->iSolidColor == NOT_SOLID_COLOR) {

        ULONG   ulColor;

        // This is not a solid brush, so get a pointer to the
        // realized brush.

        DBGMSG(DBG_LEVEL_WARNING,
            "Non-solid text brush, defaulting to black.\n");

        ulColor = RGB_BLACK;
        ps_setrgbcolor(pdev, (PSRGB *)&ulColor);
    } else
        // We have a solid brush, so simply output its color.
        ps_setrgbcolor(pdev, (PSRGB *)&pboFore->iSolidColor);

    if (pstro->pgp) {

        // If the GLYPHPOS's are provided, then use it

        if (! DrawGlyphs(pdev, pstro->cGlyphs, pstro->pgp,
                         pfo, pstro, &tdata, pstro->pwszOrg))
        {
            DBGERRMSG("DrawGlyphs");
            return FALSE;
        }

    } else {

        BOOL        bMore;
        GLYPHPOS   *pGlyphPos;
        PWSTR       pwstr = pstro->pwszOrg;

        // If the GLYPHPOS's are not provided, then we
        // have to enumerate on the STROBJ.

        STROBJ_vEnumStart(pstro);

        do {
            bMore = STROBJ_bEnum(pstro, &cGlyphs, &pGlyphPos);

            if (! DrawGlyphs(pdev, cGlyphs, pGlyphPos,
                             pfo, pstro, &tdata, pwstr))
            {
                DBGERRMSG("DrawGlyphs");
                return FALSE;
            }

            if (pwstr != NULL)
                pwstr += cGlyphs;
        } while (bMore);
    }

    // Output the extra rectangles if necessary. These rectangles are
    // bottom right exclusive. The pels of the rectangles are to be
    // combined with the pixels of the glyphs to produce the foreground
    // pels. The extra rectangles are used to simulate underlining or
    // strikeout.

    if (prclExtra != NULL) {

        RECTL   rclBounds;

        // output a newpath command to the printer.

        ps_newpath(pdev);

        // Set up bounding rectangle.

        rclBounds = *prclExtra;

        // Output each extra rectangle until we find the terminating
        // retangle with all NULL coordinates.

        while ((prclExtra->right != prclExtra->left) ||
               (prclExtra->top != prclExtra->bottom) ||
               (prclExtra->right != 0L) ||
               (prclExtra->top != 0L))
        {
            ps_box(pdev, prclExtra, FALSE);

            // Update the bounding rectangle if necessary.

            if (prclExtra->left < rclBounds.left)
                rclBounds.left = prclExtra->left;
            if (prclExtra->right > rclBounds.right)
                rclBounds.right = prclExtra->right;
            if (prclExtra->top < rclBounds.top)
                rclBounds.top = prclExtra->top;
            if (prclExtra->bottom > rclBounds.bottom)
                rclBounds.bottom = prclExtra->bottom;

            prclExtra++;
        }

        // Call the driver's filling routine. This routine will do the
        // right thing with the brush.

        if (!ps_patfill(pdev, pso, (FLONG)FP_WINDINGMODE, pboFore, pptlOrg,
                        MixToRop4(mix), &rclBounds, FALSE, FALSE))
        {
            DBGERRMSG("ps_patfill");
            return FALSE;
        }
    }

    // If a gsave was performed earlier, do a grestore here

    if (gsaved)
        ps_restore(pdev, TRUE, FALSE);

    return TRUE;
}



ULONG
DrvGetGlyphMode(
    DHPDEV  dhpdev,
    FONTOBJ *pfo
    )

/*++

Routine Description:

    Implementation of DDI entry point DrvGetGlyphMode.
    Please refer to DDK documentation for more details.

--*/

{
    return FO_GLYPHBITS;
}



BOOL
bDoClipObj(
    PDEVDATA    pdev,
    CLIPOBJ    *pco,
    RECTL      *prclClipBound,
    RECTL      *prclTarget
    )

/*++

Routine Description:

    Send PS code to the printer to set up the clippath as
    specified by CLIPOBJ pco.

Arguments:

    pdev    Pointer to DEVDATA structure
    pco     Pointer to CLIPOBJ
    prclClipBound
            Pointer to a RECTL variable for returning the
            bounding rectangle of the clippath.
            NULL if the caller is not interested in such.
    prclTarget
            Bounding rectangle for the figure to be drawn.
            If this is specified and it's entirely contained
            within the CLIPOBJ, then no clipping is done.

Return Value:

    TRUE if the clipping path is set up in the printer and
    a gsaved was performed. FALSE otherwise.

--*/

{
    PATHOBJ    *ppo;

    if (pco == NULL)
        return FALSE;

    switch(pco->iDComplexity) {

    case DC_TRIVIAL:

        // In this case, there is no clipping.  Therefore, we have
        // no commands to send to the printer.

        return FALSE;

    case DC_RECT:

        // Check to see if the target rectangle fits inside the clip
        // rectangle. If it does, don't do clipping.

        if (prclTarget != NULL) {

            if ((pco->rclBounds.left <= prclTarget->left) &&
                (pco->rclBounds.top <= prclTarget->top) &&
                (pco->rclBounds.right >= prclTarget->right) &&
                (pco->rclBounds.bottom >= prclTarget->bottom))
            {
                return FALSE;
            }
        }

        // In this case, we are clipping to a single rectangle.
        // get it from the CLIPOBJ, then send it to the printer.

        if (! ps_save(pdev, TRUE, FALSE))
            return FALSE;

        ps_newpath(pdev);
        ps_box(pdev, &pco->rclBounds, TRUE);

        if (prclClipBound != NULL)
            *prclClipBound = pco->rclBounds;

        // ps_box above has already done a clip.
        // Simply return success.

        return TRUE;

    case DC_COMPLEX:

        // In this case, we are clipping to a complex clip region.
        // Enumerate the clip region from the CLIPOBJ, and send the
        // entire clip region to the printer.

        if (! ps_save(pdev, TRUE, FALSE)) {
            DBGERRMSG("ps_save");
            return FALSE;
        }

        // Call the engine to get the clippath.

        if ((ppo = CLIPOBJ_ppoGetPath(pco)) == NULL)
        {
            DBGERRMSG("CLIPOBJ_ppoGetPath");
            ps_restore(pdev, TRUE, FALSE);
            return FALSE;
        }

        // Send the path to the printer.

        if (! DrvCommonPath(pdev, ppo, FALSE, NULL, NULL, NULL, NULL, NULL))
        {
            DBGERRMSG("DrvCommonPath");
            ps_restore(pdev, TRUE, FALSE);
            return FALSE;
        }

        // Update the clipping bound rectangle if necessary.

        if (prclClipBound != NULL)
        {
            RECTFX  rcfx;
            RECTL   rclClipBound;

            PATHOBJ_vGetBounds(ppo, &rcfx);

            rclClipBound.top    = FXTOL(rcfx.yTop);
            rclClipBound.left   = FXTOL(rcfx.xLeft);
            rclClipBound.right  = FXTOL(rcfx.xRight) + 1;
            rclClipBound.bottom = FXTOL(rcfx.yBottom) + 1;

            *prclClipBound = rclClipBound;
        }

        // Free up the path resources.

        EngDeletePath(ppo);

        break;

    default:

        DBGMSG(DBG_LEVEL_ERROR, "Invalid pco->iDComplexity.\n");
        return FALSE;
    }

    ps_clip(pdev, FALSE);
    return TRUE;
}



VOID
Add1Font(
    PDEVDATA    pdev,
    FONTOBJ    *pfo
    )

/*++

Routine Description:

    Add a font to the list of downloaded fonts

Arguments:

    pdev    Pointer to DEVDATA structure
    pfo     Pointer to GDI FONTOBJ to be downloaded

Return Value:

    NONE

--*/

{
    if (pdev->cDownloadedFonts >= pdev->maxDLFonts) {

        ps_restore(pdev, FALSE, TRUE);
        ps_save(pdev, FALSE, TRUE);

        // The preceding restore/save sequence wipes out the current
        // font size information. We have to recalculate the point
        // size here otherwise the text drawn using the current
        // font will have incorrect size.
        //
        // NOTE!!! Other information in CGS also gets wiped out by the
        // restore/save sequence. If we set their current value before
        // this function is called, they will be lost.

        pdev->cgs.psfxScaleFactor =
            GetPointSize(pdev, pfo, &pdev->cgs.FontXform);

    } else if (pdev->cDownloadedFonts == 0) {

        ps_save(pdev, FALSE, TRUE);
    }

    pdev->cDownloadedFonts++;
}



VOID
PsMakeFont(
    PDEVDATA    pdev,
    BOOL        bReencoded,
    BOOL        bSimItalic,
    BOOL        bBitmap
    )

/*++

Routine Description:

    Send PS code to select an appropriately sized font in the printer

Arguments:

    pdev        Pointer to DEVDATA structure
    bReencoded  Whether the font has been reencoded
    bSimItalic  Whether we're simulating italic font
    bBitmap     Whether the font is a downloaded bitmap font

Return Value:

    NONE

--*/

{
    psputs(pdev, "[");

    if (bBitmap) {

        LONG fontsize;

        // All scaling done already by TT driver for bitmap fonts

        fontsize = (pdev->cgs.psfxScaleFactor *
                   pdev->dm.dmPublic.dmPrintQuality) / PS_RESOLUTION;

        psputfix(pdev, 6, fontsize, 0, 0, -fontsize, 0, 0);

    } else {

        FLOATOBJ m[4], fo;
        INT index;

        // Get font transformation matrix

        FLOATOBJ_SetFloat(&m[0], pdev->cgs.FontXform.eM11);
        FLOATOBJ_SetFloat(&m[1], pdev->cgs.FontXform.eM12);
        FLOATOBJ_SetFloat(&m[2], pdev->cgs.FontXform.eM21);
        FLOATOBJ_SetFloat(&m[3], pdev->cgs.FontXform.eM22);
       
        // If we're simulating an italic font, then
        // shear the font to the right by 18 degree:
        //
        // | 1 0 0 | * m
        // | b 1 0 |
        // | 0 0 1 |
        //
        // where b = -sin(18) = -0.309017

        #define ITALIC_SHEAR (FLOAT) -0.309017

        if (bSimItalic) {

            // m[2] += b * m[0]

            FLOATOBJ_SetFloat(&fo, ITALIC_SHEAR);
            FLOATOBJ_Mul(&fo, &m[0]);
            FLOATOBJ_Add(&m[2], &fo);

            // m[3] += b * m[1]

            FLOATOBJ_SetFloat(&fo, ITALIC_SHEAR);
            FLOATOBJ_Mul(&fo, &m[1]);
            FLOATOBJ_Add(&m[3], &fo);
        }

        // Multiply font transformation matrix by the emheight

        for (index=0; index < 4; index++) {

            FLOATOBJ_MulLong(&m[index], LTOPSFX(pdev->cgs.fwdEmHeight));
        }

        psputfix(pdev, 6,
            FLOATOBJ_GetLong(&m[0]), FLOATOBJ_GetLong(&m[1]),
            -FLOATOBJ_GetLong(&m[2]), -FLOATOBJ_GetLong(&m[3]),
            0, 0);
    }

    psprintf(pdev, "]/%s%s MF\n",
        bReencoded ? "_" : "", pdev->cgs.szFont);
}



BOOL
SelectFont(
    PDEVDATA    pdev,
    FONTOBJ    *pfo,
    STROBJ     *pstro,
    TEXTDATA   *pdata
    )

/*++

Routine Description:

    Select the specified font as the current font in the printer.
    Download the font to the printer first if it's not already there.

Arguments:

    pdev    Pointer to DEVDATA structure
    pfo     Pointer to FONTOBJ
    pstro   Pointer to STROBJ
    pdata   Pointer to TEXTDATA structure

Return Value:

    TRUE if successful, FALSE otherwise

--*/

{
    PNTFM   pntfm;
    DLFONT *pDLFont;
    BOOL    boutline;
    BOOL    currentFontInSync;

    // Get the point size, and fill in the font xform.

    pdev->cgs.psfxScaleFactor = GetPointSize(pdev, pfo, &pdev->cgs.FontXform);

    currentFontInSync = (pdev->cgs.lidFont == pfo->iUniq) &&
                        (pdev->cgs.fontsubFlag == pdata->bFontSubstitution);

    if (pdata->bDeviceFont) {

        // Get the font metrics for the specified font.

        ASSERT(ValidPsFontIndex(pdev, pdata->iFace));
        pntfm = GetPsFontNtfm(pdev, pdata->iFace);

        if (! currentFontInSync) {

            DWORD iFace = pdata->iFace - 1;
            PSTR pFontName = (PSTR) pntfm + pntfm->ntfmsz.loszFontName;

            if (! BitArrayGetBit(pdev->cgs.pFontFlags, iFace)) {

                if (iFace < pdev->cDeviceFonts) {
    
                    // First time in current save context that a device font
                    // has been used, generate %%IncludeFont: DSC comment

                    DscIncludeFont(pdev, pFontName);

                } else {
    
                    // If a soft font is requested and it hasn't been
                    // downloaded yet, then download the soft font to
                    // the printer and convert PFB to ASCII on the fly.
                    // Remember DownloadSoftFont takes a zero-based index.
    
                    Add1Font(pdev, pfo);
    
                    DscBeginFont(pdev, pFontName);
    
                    if (! DownloadSoftFont(pdev, iFace - pdev->cDeviceFonts)) {

                        DBGERRMSG("DownloadSoftFont");
                        return FALSE;
                    }
    
                    DscEndFont(pdev);
    
                    pDLFont = pdev->pDLFonts + pdev->cDownloadedFonts - 1;
                    pDLFont->iUniq = pfo->iUniq;
                    pDLFont->iTTUniq = 0;
                    pDLFont->cGlyphs = 0;
                    pDLFont->phgVector = NULL;
                }

                BitArraySetBit(pdev->cgs.pFontFlags, iFace);
            }

            // Indicate we have seen the font before

            BitArraySetBit(pdev->pFontFlags, iFace);

            // Select the font in the printer.

            strcpy(pdev->cgs.szFont, pFontName);
        }

        // Reencode by default if not a symbol font

        pdata->doReencode = TRUE;

        if (pdata->iFace > pdev->cDeviceFonts) {

            // Soft font

            if (pntfm->flNTFM & FL_NTFM_NO_TRANSLATE_CHARSET)
                pdata->doReencode= FALSE;

        } else {

            // Device font

            PIFIMETRICS pifi;

            pifi = (PIFIMETRICS) ((PBYTE) pntfm + pntfm->ntfmsz.loIFIMETRICS);

            if ((pifi->jWinPitchAndFamily & 0x0f0) == FF_DECORATIVE ||
                strcmp((PSTR)pntfm + pntfm->ntfmsz.loszFontName, "Symbol") == EQUAL_STRING)
            {
                pdata->doReencode= FALSE;
            }
        }

    } else {

        // Must be a GDI font which will be downloaded

        boutline = DownloadedAsOutline(pdev, pfo);

        if (! DownloadFont(pdev, pfo, NULL, pstro, boutline)) {
            DBGERRMSG("DownloadFont");
            return FALSE;
        }
    }

    // Do setfont if different from current font

    if (! currentFontInSync) {

        PsMakeFont(pdev,
            pdata->bDeviceFont && QueryFontRemap(pdev, pfo->iUniq),
            pdata->bSimItalic,
            ! pdata->bDeviceFont && ! boutline);

        pdev->cgs.lidFont = pfo->iUniq;
        pdev->cgs.fontsubFlag = pdata->bFontSubstitution;
    }

    return TRUE;
}



VOID
ReencodeFont(
    PDEVDATA    pdev,
    FONTOBJ    *pfo,
    GLYPHPOS   *pgp,
    ULONG       cGlyphs,
    TEXTDATA   *pdata,
    PWSTR       pwstr
    )

/*++

Routine Description:

    Rencode a font when we have characters which don't have
    standard PostScript character code. If necessary, this
    routine will download a new encoding vector. It will then
    reencode the current font to the new encoding vector.

Arguments:

    pdev    Pointer to DEVDATA structure
    pfo     Pointer to GDI FONTOBJ
    pgp     Pointer to an array of glyphs to be drawn
    cGlyphs Number of glyphs in pgp or number of characters in pwstr
    pdata   Pointer to TEXTDATA structure
    pwstr   Pointer to a character string to be drawn

Return Value:

    NONE

--*/

{
    // Does the current font need to be remapped for this string?

    if (! ShouldWeRemap(pdev, cGlyphs, pgp, pwstr, pdata->bFontSubstitution))
        return;

    // If the font remapping header and latin encoding vector have not
    // been downloaded to the printer, do it now.

    if (! (pdev->cgs.dwFlags & CGS_LATINENCODED)) {

        bSendPSProcSet(pdev, PSPROC_REENCODE);
        pdev->cgs.dwFlags |= CGS_LATINENCODED;
    }

    // Output the PostScript commands to reencode the current font
    // using the proper encoding vector, if the current font has
    // not already been reencoded.

    if (QueryFontRemap(pdev, pdev->cgs.lidFont))
        return;

    psprintf(pdev, "LATENC /_%s /%s reencode\n",
             pdev->cgs.szFont, pdev->cgs.szFont);

    // Select the newly reencoded font.

    pdev->cgs.psfxScaleFactor = GetPointSize(pdev, pfo, &pdev->cgs.FontXform);

    PsMakeFont(pdev, TRUE,
        pdata->bSimItalic,
        (pfo->flFontType & TRUETYPE_FONTTYPE) &&
        ! pdata->bFontSubstitution &&
        ! DownloadedAsOutline(pdev, pfo));

    // Indicate that the current font has been reencoded

    SetFontRemap(pdev, pdev->cgs.lidFont);
}



BOOL
RemapDeviceChars(
    PDEVDATA    pdev,
    DWORD       cChars,
    CHAR       *pch
    )

/*++

Routine Description:

    Display a character on the printer

Arguments:

    pdev    Pointer to DEVDATA structure
    cChars  Number of characters
    pch     Pointer to array of character to be drawn

Return Value:

    TRUE if successful, FALSE otherwise

--*/

{
    CHAR    ch, buffer[4];
    DWORD   nchar;

    while (cChars--) {

        ch = *pch++;

        if (ch == '(' || ch == ')' || ch == '\\') {

            // Precede each of these special characters with a backslash

            buffer[0] = '\\';
            buffer[1] = ch;
            nchar = 2;

        } else if (B_PRINTABLE(ch))  {

            // Output printable characters directly

            buffer[0] = ch;
            nchar = 1;

        } else {

            // Convert non-printable character to octal representation

            buffer[0] = '\\';
            buffer[1] = ((ch >> 6) & 3) + '0';
            buffer[2] = ((ch >> 3) & 7) + '0';
            buffer[3] = (ch & 7) + '0';
            nchar = 4;
        }

        if (! pswrite(pdev, buffer, nchar))
            return FALSE;
    }

    return TRUE;
}



DWORD
FindDownloadedGlyph(
    DLFONT     *pDLFont,
    HGLYPH      hGlyph
    )

/*++

Routine Description:

    Convert a glyph handle of a downloaded GDI font to its
    corresponding character code

Arguments:

    pDLFont Pointer to DLFONT structure
    hGlyph  Glyph handle

Return Value:

    Index of the specified glyph handle within the array of downloaded
    glyphs. Otherwise, INVALID_GLYPH_INDEX is returned if the specified
    glyph hasn't been downloaded.

--*/

#define INVALID_GLYPH_INDEX 0xffff

{
    DWORD   index;
    HGLYPH *phGlyph;

    ASSERT(pDLFont != NULL && pDLFont->phgVector != NULL);

    for (index=0, phGlyph = pDLFont->phgVector;
         index < pDLFont->cGlyphs && *phGlyph != hGlyph;
         index++, phGlyph++)
    {
    }

    return (index < pDLFont->cGlyphs) ? index : INVALID_GLYPH_INDEX;
}



FIX
GetGlyphAdvanceWidth(
    FONTOBJ *pfo,
    HGLYPH  hGlyph
    )

/*++

Routine Description:

    Calculate the advance width of a specified glyph

Arguments:

    pfo     Pointer to FONTOBJ
    hGlyph  Handle to the specified glyph

Return Value:

    Glyph advance width

--*/

{
    GLYPHDATA *pGlyphData;

    // NOTE!!! Even though we have no interest in the bitmap here,
    // we must get it in order to retrieve glyph metrics info.

    if (! FONTOBJ_cGetGlyphs(pfo, FO_GLYPHBITS, 1, &hGlyph, &pGlyphData)) {
        DBGERRMSG("FONTOBJ_cGetGlyphs");
        return 0;
    }

    // We're not prepared to deal with negative advance width

    if (pGlyphData->fxD < 0) {
        DBGMSG(DBG_LEVEL_ERROR, "Invalid character increment.\n");
    }

    return pGlyphData->fxD;
}



BOOL
DrawGDIGlyphs(
    PDEVDATA    pdev,
    FONTOBJ    *pfo,
    DLFONT     *pDLFont,
    DWORD       cGlyphs,
    GLYPHPOS   *pGlyphPos,
    PSTR        charStr,
    BOOL        bPositionEachChar
    )

/*++

Routine Description:

    Display a number of glyphs on the printer using a downloaded GDI font.
    This is only used when some of the glyphs must be displayed by
    drawing the bitmap on the printer.

Arguments:

    pdev        Pointer to DEVDATA structure
    pfo         Pointer to FONTOBJ
    pDLFont     Pointer to DLFONT structure corresponding to pfo
    cGlyphs     Number of glyphs to display
    pGlyphPos   Pointer to array of GLYPHPOS's
    charStr     Pointer to array of character codes
    bPositionEachChar   Whether characters are individually positioned

Return Value:

    TRUE if successful, FALSE otherwise

--*/

{
    DWORD   count;

    for (count=1; count <= cGlyphs; count++, pGlyphPos++, charStr++) {

        // Position each character separately if needed
        // Or if we're display the very last glyph

        if (bPositionEachChar || count == 1) {
            ps_moveto(pdev, &pGlyphPos->ptl);
            psputs(pdev, "(");
        }

        // If the character is NOT part of the downloaded font,
        // then we have to draw the character bitmap manually.

        if (*charStr == NUL &&
            FindDownloadedGlyph(pDLFont, pGlyphPos->hg) == INVALID_GLYPH_INDEX)
        {
            // Make sure we have a valid glyph handle

            if (pGlyphPos->hg == HGLYPH_INVALID) {
                DBGMSG(DBG_LEVEL_ERROR, "Invalid glyph handle.\n");
                return FALSE;
            }

            // Force any characters in the current string to be shown

            psputs(pdev, ")t\n");

            // Draw the character - only bitmap fonts are supported.

            CharBitmap(pdev, pGlyphPos);

            // Advance the current point and start a new string

            if (! bPositionEachChar) {

                // !!! We assume ptl field of every GLYPHPOS is valid
                // no matter SO_FLAG_DEFAULT_PLACEMENT bit of flAccel
                // is set or not set. This assumption is currently
                // valid and gives us SIGNIFICANT performance gain over
                // calling FONTOBJ_cGetGlyphs.

                if (count >= cGlyphs) {

                    psputs(pdev, "0 0 rm");

                } else {

                    psprintf(pdev, "%d %d rm",
                        pGlyphPos[1].ptl.x - pGlyphPos[0].ptl.x,
                        pGlyphPos[1].ptl.y - pGlyphPos[0].ptl.y);
                }
            }

            psputs(pdev, "(");

        } else {

            // If the character is part of the downloaded font, then
            // use its glyph index as the device character code.

            if (! RemapDeviceChars(pdev, 1, charStr)) {
                DBGERRMSG("RemapDeviceChars");
                return FALSE;
            }
        }

        // Show each character separately if needed
        // Or if we're display the very last glyph

        if (bPositionEachChar || count == cGlyphs)
            psputs(pdev, ")t\n");
    }

    return TRUE;
}



BOOL
DrawGlyphs(
    PDEVDATA    pdev,
    DWORD       cGlyphs,
    GLYPHPOS   *pGlyphPos,
    FONTOBJ    *pfo,
    STROBJ     *pstro,
    TEXTDATA   *pdata,
    PWSTR       pwstr
    )

/*++

Routine Description:

    Display a set of glyphs on the printer

Arguments:

    pdev        Pointer to DEVDATA structure
    cGlyphs     Number of glyphs to drawn
    pGlyphPos   Pointer to an array of GLYPHPOS's
    pfo         Pointer to FONTOBJ
    pstro       Pointer to STROBJ
    pdata       Pointer to TEXTDATA structure
    pwstr       Pointer to the string to be drawn (if using substituted font)

Return Value:

    TRUE if successful, FALSE otherwise

--*/

{
    DWORD       count;
    GLYPHPOS   *pgp;
    PSTR        charStr, pch;
    BOOL        bPositionEachChar, bCharBitmap;
    BOOL        bResult = TRUE;
    DLFONT     *pDLFont;

    // Quick exit if there is nothing to do

    if (cGlyphs == 0)
        return TRUE;

    // Reencode the font if necessary

    if (pdata->bDeviceFont && pdata->doReencode)
        ReencodeFont(pdev, pfo, pGlyphPos, cGlyphs, pdata, pwstr);

    // Decide whether each character in the string is positioned
    // individually. If not, we only need to position once and
    // let the printer take care of the rest.

    bPositionEachChar =
        (cGlyphs > 1) && ! (pdata->flAccel & SO_FLAG_DEFAULT_PLACEMENT);

    // Assume we don't have to download character bitmaps

    bCharBitmap = FALSE;

    // Allocate memory to hold character codes

    if ((pch = charStr = MEMALLOC(cGlyphs * sizeof(CHAR))) == NULL) {
        DBGERRMSG("MEMALLOC");
        return FALSE;
    }

    // Convert glyph handles to character codes

    if (! pdata->bDeviceFont) {

        DWORD   glyphIndex;

        // For downloaded GDI font, to convert a glyph handle
        // to character code, we search the array of downloaded
        // glyph handles. If the given handle is found, its index
        // in the array is used as the character code. Otherwise,
        // we have to send the glyph to the printer as bitmap.

        // Find the DLFont structure corresponding to the GDI font

        pDLFont = FindDownloadedFont(pdev, pfo, DownloadedAsOutline(pdev,pfo));
        ASSERT(pDLFont != NULL);

        for (count=0, pgp = pGlyphPos; count < cGlyphs; count++, pgp++) {

            glyphIndex = FindDownloadedGlyph(pDLFont, pgp->hg);

            if (glyphIndex == INVALID_GLYPH_INDEX) {
                *pch++ = NUL;
                bCharBitmap = TRUE;
            } else
                *pch++ = (CHAR) glyphIndex;
        }

    } else if (pdata->bFontSubstitution) {

        // For substituted TrueType fonts, character codes
        // are converted from Unicode string

        UNICODETOMULTIBYTE(pch, cGlyphs*sizeof(CHAR), NULL,
                           pwstr, cGlyphs*sizeof(WCHAR));
    } else {

        // For device font, glyph handles are used directly
        // as character codes

        for (count=0, pgp = pGlyphPos; count < cGlyphs; count++, pgp++)
            *pch++ = (CHAR) pgp->hg;
    }

    if (bCharBitmap) {

        // If we're using a previously downloaded bitmap font
        // and we need to download character bitmaps, then call
        // another function to do the dirty work.

        bResult = DrawGDIGlyphs(pdev, pfo, pDLFont, cGlyphs, pGlyphPos,
                                charStr, bPositionEachChar);

    } else if (bPositionEachChar ||
               (pdata->bFontSubstitution &&
                (pdev->pPrinterData->dwFlags & PSDEV_SLOW_FONTSUBST)))
    {
        // We're told to position each character individually, or
        // if we're substituting a TrueType font with a device font.
        //
        // In the latter case, we must adjust the inter-character
        // spacing to compensate the difference between TrueType
        // and device font metrics. Even though we'd like to use
        // ashow here, but it's difficult to calculate the string
        // width on the printer. So we position each character
        // individually instead.
        //
        // !!! We assume ptl field of every GLYPHPOS is valid no matter
        // SO_FLAG_DEFAULT_PLACEMENT bit of flAccel is set or not set.

        // Intentionally disable level 2 features

        if (FALSE && Level2Device(pdev->hppd)) {

            LONG y0;

            // On level 2 devices, output x- and/or y- displacements
            // as a numarray and use the xyshow/xshow operator.

            // Move to the starting position of the first character

            ps_moveto(pdev, &pGlyphPos->ptl);

            // Output the entire character string

            psputs(pdev, "(");
            bResult = RemapDeviceChars(pdev, cGlyphs, charStr);
            psputs(pdev, ")\n[");

            // Decide whether we should use xyshow or xshow
            
            for (count=1, y0 = pGlyphPos[0].ptl.y;
                 count < cGlyphs && pGlyphPos[count].ptl.y == y0;
                 count++)
            {
            }

            if (count >= cGlyphs) {

                // All y-coordinates are the same - Use xshow.

                for (count=1; count < cGlyphs; count++, pGlyphPos++) {
    
                    psprintf(pdev, "%d%c",
                        pGlyphPos[1].ptl.x - pGlyphPos[0].ptl.x,
                        (count & 31) ? ' ' : '\n');
                }

                psputs(pdev, "1]X\n");

            } else {

                // Y-coordinates are not all the same - Use xyshow.
    
                for (count=1; count < cGlyphs; count++, pGlyphPos++) {
    
                    psprintf(pdev, "%d %d%c",
                        pGlyphPos[1].ptl.x - pGlyphPos[0].ptl.x,
                        pGlyphPos[1].ptl.y - pGlyphPos[0].ptl.y,
                        (count & 15) ? ' ' : '\n');
                }
    
                psputs(pdev, "1 0]XY\n");
            }

        } else {
    
            // On level 1 devices, use moveto-show combination
            // to individually position and display each character
    
            for (count = 1, pch = charStr;
                 count <= cGlyphs && bResult;
                 count++, pGlyphPos++)
            {
                psputs(pdev, "(");
                bResult = RemapDeviceChars(pdev, 1, pch++);
                psprintf(pdev, ")%d %d MS%c",
                    pGlyphPos->ptl.x, pGlyphPos->ptl.y,
                    ((count & 15) && count != cGlyphs) ? ' ' : '\n');
            }
        }

    } else {

        // The optimal case: We're using a device font and don't
        // have to position individual characters. Output the entire
        // character string and simply "show" it.

        psputs(pdev, "(");
        bResult = RemapDeviceChars(pdev, cGlyphs, charStr);
        psputs(pdev, ")");

        psprintf(pdev, "%d %d MS\n", pGlyphPos->ptl.x, pGlyphPos->ptl.y);
    }

    MEMFREE(charStr);
    return bResult;
}



BOOL
ShouldWeRemap(
    PDEVDATA    pdev,
    ULONG       cGlyphs,
    GLYPHPOS   *pgp,
    PWSTR       pwstr,
    BOOL        bFontSubstitution
    )

/*++

Routine Description:

    Determine if the current font needs to be reencoded in order
    to output a given string or a set of glyphs

Arguments:

    pdev    Pointer to DEVDATA structure
    cGlyphs Number of characters or glyphs to be drawn
    pgp     Pointer to GLYPHPOS for specifying the set of glyphs
    pwstr   Pointer to the string to be drawn
    bFontSubstition Whether this is a device font or a substituted font

Return Value:

    TRUE if reencoding is needed, FALSE otherwise

--*/

{
    BYTE j;

    if (bFontSubstitution)
    {
        // For a substituted font, pwstr parameter points to
        // the character string to be drawn

        while (cGlyphs--)
        {
            UNICODETOMULTIBYTE(&j, sizeof(BYTE), NULL, pwstr, sizeof(WCHAR));

            if (!B_PRINTABLE(j))
                return TRUE;

            pwstr++;
        }
    }
    else
    {
        // For a device font, pgp parameter specifies the
        // set of glyphs to be drawn.

        while (cGlyphs--)
        {
            j = (BYTE)pgp->hg;
            if (!B_PRINTABLE(j))
                return TRUE;
            pgp++;
        }
    }

    return FALSE;
}



VOID
CharBitmap(
    PDEVDATA    pdev,
    GLYPHPOS   *pgp
    )

/*++

Routine Description:

    Download a character bitmap to the printer

Arguments:

    pdev    Pointer to DEVDATA structure
    pgp     Pointer to GLYPHPOS structure for the downloaded character

Return Value:

    NONE

--*/

{
    GLYPHBITS *pgb;
    LONG    cx, cy;

    // Figure out the bitmap origin and size

    pgb = pgp->pgdf->pgb;
    psprintf(pdev,
        "gsave %d %d rm a translate\n", pgb->ptlOrigin.x, pgb->ptlOrigin.y);

    cx = pgb->sizlBitmap.cx;
    cy = pgb->sizlBitmap.cy;

    psprintf(pdev, "%d %d scale\n", cx, cy);

    // Output the image operator and the scan data.  true means to
    // paint the '1' bits with the foreground color.

    psprintf(pdev, "%d %d true [%d 0 0 %d 0 0]\n", cx, cy, cx, cy);

    // Output glyph bitmaps

    OutputGlyphBitmap(pdev, pgb);

    psputs(pdev, "im grestore\n");
}



BOOL
SetFontRemap(
    PDEVDATA    pdev,
    DWORD       iFontID
    )

/*++

Routine Description:

    Add the specified font to the list of reencoded font

Arguments:

    pdev    Pointer to DEVDATA structure
    iFontID Index of the specified font

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    FREMAP *pfremap = &pdev->cgs.FontRemap;

    if (pfremap->pNext)
    {
        // Find the end of the list

        while (pfremap->pNext)
            pfremap = (PFREMAP)pfremap->pNext;

        // Allocate memory for the new list item

        if ((pfremap->pNext = HEAPALLOC(pdev->hheap, sizeof(FREMAP))) == NULL)
        {
            DBGERRMSG("HEAPALLOC");
            return FALSE;
        }

        pfremap = (PFREMAP)pfremap->pNext;
    }

    // Fill in the fields of the new list item

    pfremap->iFontID = iFontID;
    pfremap->pNext = NULL;

    return TRUE;
}



BOOL
QueryFontRemap(
    PDEVDATA   pdev,
    DWORD      iFontID
    )

/*++

Routine Description:

    Find out if the specified font has been reencoded

Arguments:

    pdev    Pointer to DEVDATA structure
    iFontID Index of the font in question

Return Value:

    TRUE if the font has been reencoded, FALSE otherwise

--*/

{
    FREMAP *pfremap = &pdev->cgs.FontRemap;

    do {
        if (pfremap->iFontID == iFontID)
            return TRUE;

        pfremap = (PFREMAP) pfremap->pNext;

    } while (pfremap != NULL);

    return FALSE;
}



BOOL
CanBeSubstituted(
    STROBJ  *pstro
    )

/*++

Routine Description:

    Determine if a STROBJ can be substituted using device font

Arguments:

    pstro - Specifies the STROBJ in question

Return Value:

    TRUE if the STROBJ can be substituted, FALSE otherwise

--*/

{
    PWSTR   pwch;
    DWORD   cch, codePage;

    //
    // Unicode values corresponding to ANSI characters 80-9f
    //

    static WCHAR MyCodeTable[32] = {

        /* 0x80 */  0x0080,
        /* 0x81 */  0x0081,
        /* 0x8d */  0x008d,
        /* 0x8e */  0x008e,
        /* 0x8f */  0x008f,
        /* 0x90 */  0x0090,
        /* 0x9d */  0x009d,
        /* 0x9e */  0x009e,
        /* 0x8c */  0x0152,     // Latin Capital Ligature Oe
        /* 0x9c */  0x0153,     // Latin Small Ligature Oe
        /* 0x8a */  0x0160,     // Latin Capital Letter S With Caron
        /* 0x9a */  0x0161,     // Latin Small Letter S With Caron
        /* 0x9f */  0x0178,     // Latin Capital Letter Y With Diaeresis
        /* 0x83 */  0x0192,     // Latin Small Letter F With Hook
        /* 0x88 */  0x02c6,     // Modifier Letter Circumflex Accent
        /* 0x98 */  0x02dc,     // Small Tilde
        /* 0x96 */  0x2013,     // En Dash
        /* 0x97 */  0x2014,     // Em Dash
        /* 0x91 */  0x2018,     // Left Single Quotation Mark
        /* 0x92 */  0x2019,     // Right Single Quotation Mark
        /* 0x82 */  0x201a,     // Single Low-9 Quotation Mark
        /* 0x93 */  0x201c,     // Left Double Quotation Mark
        /* 0x94 */  0x201d,     // Right Double Quotation Mark
        /* 0x84 */  0x201e,     // Double Low-9 Quotation Mark
        /* 0x86 */  0x2020,     // Dagger
        /* 0x87 */  0x2021,     // Double Dagger
        /* 0x95 */  0x2022,     // Bullet
        /* 0x85 */  0x2026,     // Horizontal Ellipsis
        /* 0x89 */  0x2030,     // Per Mille Sign
        /* 0x8b */  0x2039,     // Single Left-Pointing Angle Quotation Mark
        /* 0x9b */  0x203a,     // Single Right-Pointing Angle Quotation Mark
        /* 0x99 */  0x2122,     // Trade Mark Sign
    };

    //
    // Go through the Unicode characters and find out if
    // all of them belong to code page 1252.
    //

    if ((codePage = STROBJ_dwGetCodePage(pstro)) == 0) {

        if (pwch = pstro->pwszOrg) {
    
            //
            // Go through each Unicode character and
            // see if it belong to code page 1252
            //
    
            for (cch=pstro->cGlyphs; cch--; pwch++) {
    
                //
                // If the Unicode value is between 00 - 7f and a0-ff,
                // they belong to code page 1252. Otherwise, we have
                // to do a slower search.
                //
    
                if ((*pwch & 0xff00) || (*pwch & 0xe0) == 0x80) {
    
                    INT low, high, mid;
    
                    low = 0;
                    high = 31;
    
                    while (low <= high) {
    
                        mid = (low + high) >> 1;
    
                        if (*pwch == MyCodeTable[mid])
                            break;
                        else if (*pwch < MyCodeTable[mid])
                            high = mid - 1;
                        else
                            low = mid + 1;
                    }
    
                    if (low > high)
                        return FALSE;
                }
            }
        }

        return TRUE;
    }

    return codePage == 1252;
}



DWORD
SubstituteIFace(
    PDEVDATA    pdev,
    FONTOBJ    *pfo
    )

/*++

Routine Description:

    Substitute a TrueType font with a device font or soft font

Arguments:

    pdev    Pointer to DEVDATA structure
    pfo     Pointer to TrueType FONTOBJ to be substituted

Return Value:

    one-based device or soft font index if there is a substitution
    for the TrueType font. 0 otherwise.

--*/

{
    CHAR        strDevFont[MAX_FONT_NAME];
    IFIMETRICS *pifiTT;
    PWSTR       pwstr, pwstrTT;
    WORD        index;
    PDEVFONT    pDevFont;

    // Get the TrueType font name from the IFIMETRICS structure.

    if ((pifiTT = FONTOBJ_pifi(pfo)) == NULL) {
        DBGERRMSG("FONTOBJ_pifi");
        return 0;
    }

    pwstrTT = (PWSTR)((BYTE *)pifiTT + pifiTT->dpwszFaceName);

    // Search the font substitution table and find out if there is
    // a mapping for the TrueType font in question

    pwstr = FindTrueTypeSubst(pdev->pTTSubstTable, pwstrTT);

    // If there is no mapping found, return 0

    if (pwstr == NULL)
        return 0;

    // Convert device font name from Unicode to ANSI

    CopyUnicode2Str(strDevFont, pwstr, MAX_FONT_NAME);

    // At this point we have a mapping between a TrueType font name,
    // and a device font name. We need to map the device font name
    // to a device font index.

    pDevFont = (PDEVFONT)
        PpdFindUiOptionWithXlation(
            (PUIOPTION) pdev->hppd->pFontList, strDevFont, &index);

    if (pDevFont != NULL) {

        // If we found a font whose name matches the substituted
        // font name, then select it in the current graphics state
        // and return its index.

        CopyStringA(pdev->cgs.szFont, pDevFont->pName, MAX_FONT_NAME);

        // Remember the index returned by PpdFindUiOptionWithXlation
        // is zero-based, while the device font index is one-based.

        return index + 1;
    }

    // The substituted font name is not a known device font. We'll
    // ignore it and act as if there is no substitution.

    return 0;
}



LONG
iHipot(
    LONG x,
    LONG y
    )

/*++

Routine Description:

    Computes the hypoteneous of a right triangle

Arguments:

    x, y    Edges of the right triangle

[Note:]

    Solve sq(x) + sq(y) = sq(hypo)
    Start with MAX(x, y),
    Use sq(x + 1) = sq(x) + 2x + 1 to incrementally
    get to the target hypotenouse.

Return Value:

    Hypoteneous of a right triangle

--*/

{
    INT hypo;       // Value to calculate
    INT delta;      // Used in the calculation loop
    INT target;     // Loop limit factor

    // Quick exit for frequent trivial cases [bodind]

    if (x == 0)
        return abs(y);

    if (y == 0)
        return abs(x);

    if (x < 0)
        x = -x;

    if (y < 0)
        y = -y;

    if(x > y)
    {
        hypo = x;
        target = y * y;
    }
    else
    {
        hypo = y;
        target = x * x;
    }

    for (delta = 0; delta < target; hypo++)
        delta += (hypo << 1) + 1;

    return hypo;
}