/******************************Module*Header*******************************\ * * ******************* * * GDI SAMPLE CODE * * ******************* * * Module Name: textout.c * * On every TextOut, GDI provides an array of 'GLYPHPOS' structures * for every glyph to be drawn. Each GLYPHPOS structure contains a * glyph handle and a pointer to a monochrome bitmap that describes * the glyph. (Note that unlike Windows 3.1, which provides a column- * major glyph bitmap, Windows NT always provides a row-major glyph * bitmap.) As such, there are three basic methods for drawing text * with hardware acceleration: * * 1) Glyph caching -- Glyph bitmaps are cached by the accelerator * (probably in off-screen memory), and text is drawn by * referring the hardware to the cached glyph locations. * * 2) Glyph expansion -- Each individual glyph is colour-expanded * directly to the screen from the monochrome glyph bitmap * supplied by GDI. * * 3) Buffer expansion -- The CPU is used to draw all the glyphs into * a 1bpp monochrome bitmap, and the hardware is then used * to colour-expand the result. * * In addition, 2) and 3) can each have two permutations: * * a) Glyphs are bit-packed -- The fastest method, where no bits * are used as padding between scans of the glyph. * * b) Glyphs are byte-, word-, or dword-packed -- The slower method, * where the hardware requires that each scan be padded with * unused bits to fill out to the end of the byte, word, or * dword. * * The fastest method depends on a number of variables, such as the * colour expansion speed, bus speed, CPU speed, average glyph size, * and average string length. * * For the S3 with normal sized glyphs, I've found that caching the * glyphs in off-screen memory is typically the slowest method. * Buffer expansion is typically fastest on the slow ISA bus (or when * memory-mapped I/O isn't available on the x86), and glyph expansion * is best on fast buses such as VL and PCI. * * This driver implements glyph expansion and buffer expansion -- * methods 2) and 3). Depending on the hardware capabilities at * run-time, we'll use whichever one will be faster. * * Copyright (c) 1992-1998 Microsoft Corporation * \**************************************************************************/ #include "precomp.h" RECTL grclMax = { 0, 0, 0x8000, 0x8000 }; // Maximal clip rectangle for trivial clipping BYTE gajBit[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; // Converts bit index to set bit #define FIFTEEN_BITS ((1 << 15)-1) /******************************Public*Routine******************************\ * VOID vClipSolid * * Fills the specified rectangles with the specified colour, honouring * the requested clipping. No more than four rectangles should be passed in. * Intended for drawing the areas of the opaquing rectangle that extend * beyond the text box. The rectangles must be in left to right, top to * bottom order. Assumes there is at least one rectangle in the list. * \**************************************************************************/ VOID vClipSolid( PDEV* ppdev, LONG crcl, RECTL* prcl, ULONG iColor, CLIPOBJ* pco) { BOOL bMore; // Flag for clip enumeration CLIPENUM ce; // Clip enumeration object ULONG i; ULONG j; RECTL arclTmp[4]; ULONG crclTmp; RECTL* prclTmp; RECTL* prclClipTmp; LONG iLastBottom; RECTL* prclClip; RBRUSH_COLOR rbc; ASSERTDD((crcl > 0) && (crcl <= 4), "Expected 1 to 4 rectangles"); ASSERTDD((pco != NULL) && (pco->iDComplexity != DC_TRIVIAL), "Expected a non-null clip object"); rbc.iSolidColor = iColor; if (pco->iDComplexity == DC_RECT) { crcl = cIntersect(&pco->rclBounds, prcl, crcl); if (crcl != 0) { (ppdev->pfnFillSolid)(ppdev, crcl, prcl, 0xf0f0, rbc, NULL); } } else // iDComplexity == DC_COMPLEX { // Bottom of last rectangle to fill iLastBottom = prcl[crcl - 1].bottom; // Initialize the clip rectangle enumeration to right-down so we can // take advantage of the rectangle list being right-down: CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, CD_RIGHTDOWN, 0); // Scan through all the clip rectangles, looking for intersects // of fill areas with region rectangles: do { // Get a batch of region rectangles: bMore = CLIPOBJ_bEnum(pco, sizeof(ce), (VOID*)&ce); // Clip the rect list to each region rect: for (j = ce.c, prclClip = ce.arcl; j-- > 0; prclClip++) { // Since the rectangles and the region enumeration are both // right-down, we can zip through the region until we reach // the first fill rect, and are done when we've passed the // last fill rect. if (prclClip->top >= iLastBottom) { // Past last fill rectangle; nothing left to do: return; } // Do intersection tests only if we've reached the top of // the first rectangle to fill: if (prclClip->bottom > prcl->top) { // We've reached the top Y scan of the first rect, so // it's worth bothering checking for intersection. // Generate a list of the rects clipped to this region // rect: prclTmp = prcl; prclClipTmp = arclTmp; for (i = crcl, crclTmp = 0; i-- != 0; prclTmp++) { // Intersect fill and clip rectangles if (bIntersect(prclTmp, prclClip, prclClipTmp)) { // Add to list if anything's left to draw: crclTmp++; prclClipTmp++; } } // Draw the clipped rects if (crclTmp != 0) { (ppdev->pfnFillSolid)(ppdev, crclTmp, &arclTmp[0], 0xf0f0, rbc, NULL); } } } } while (bMore); } } /******************************Public*Routine******************************\ * BOOL bIoTextOut * * Outputs text using the 'buffer expansion' method. We call GDI to draw * all the glyphs to a single monochrome buffer, and then we use the * hardware to colour expand the result to the screen. * \**************************************************************************/ BOOL bIoTextOut( SURFOBJ* pso, STROBJ* pstro, FONTOBJ* pfo, CLIPOBJ* pco, RECTL* prclOpaque, BRUSHOBJ* pboFore, BRUSHOBJ* pboOpaque) { PDEV* ppdev; RECTL* prclBounds; LONG lDelta; ULONG ulBufferHeight; ULONG ulBufferBytes; BOOL bTmpAlloc; VOID* pvTmp; SURFOBJ* psoTmp; BOOL bOpaque; BRUSHOBJ boFore; BRUSHOBJ boOpaque; BOOL bRet; XLATECOLORS xlc; // Temporary for keeping colours XLATEOBJ xlo; // Temporary for passing colours CLIPENUM ce; // Clip enumeration object RBRUSH_COLOR rbc; RECTL* prclClip; RECTL rclClip; BOOL bMore; ROP4 rop4; ppdev = (PDEV*) pso->dhpdev; // If asked to do an opaque TextOut, we'll set it up so that the // 1bpp blt we do will automatically opaque the 'rclBkGround' // rectangle. But we have to handle here the case if 'prclOpaque' // is bigger than 'rclBkGround': if ((prclOpaque != NULL) && ((prclOpaque->left != pstro->rclBkGround.left) || (prclOpaque->top != pstro->rclBkGround.top) || (prclOpaque->right != pstro->rclBkGround.right) || (prclOpaque->bottom != pstro->rclBkGround.bottom))) { rbc.iSolidColor = pboOpaque->iSolidColor; prclClip = prclOpaque; if ((pco == NULL) || (pco->iDComplexity == DC_TRIVIAL)) { Output_Opaque: ppdev->pfnFillSolid(ppdev, 1, prclClip, 0xf0f0, rbc, NULL); } else if (pco->iDComplexity == DC_RECT) { if (bIntersect(&pco->rclBounds, prclOpaque, &rclClip)) { prclClip = &rclClip; // Save some code size by jumping to the common // functions calls: goto Output_Opaque; } } else // pco->iDComplexity == DC_COMPLEX { CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, CD_ANY, 0); do { bMore = CLIPOBJ_bEnum(pco, sizeof(ce) - sizeof(RECTL), (ULONG*) &ce); ce.c = cIntersect(prclOpaque, ce.arcl, ce.c); if (ce.c != 0) { ppdev->pfnFillSolid(ppdev, ce.c, &ce.arcl[0], 0xf0f0, rbc, NULL); } } while (bMore); } } // If there is an opaque rectangle then it will be bigger than the // background rectangle. We want to test with whichever is bigger. prclBounds = prclOpaque; if (prclBounds == NULL) { prclBounds = &pstro->rclBkGround; } if ((pco != NULL) && (pco->iDComplexity != DC_TRIVIAL)) { // I'm not entirely sure why, but GDI will occasionally send // us TextOut's where the opaquing rectangle does not intersect // with the clip object bounds -- meaning that the text out // should have already been trivially rejected. We will do so // here because the blt code usually assumes that all trivial // rejections will have already been performed, and we will be // passing this call on to the blt code: if ((pco->rclBounds.top >= pstro->rclBkGround.bottom) || (pco->rclBounds.left >= pstro->rclBkGround.right) || (pco->rclBounds.right <= pstro->rclBkGround.left) || (pco->rclBounds.bottom <= pstro->rclBkGround.top)) { // The entire operation was trivially rejected: return(TRUE); } } // See if the temporary buffer is big enough for the text; if // not, try to allocate enough memory. We round up to the // nearest dword multiple: lDelta = ((((pstro->rclBkGround.right + 31) & ~31) - (pstro->rclBkGround.left & ~31)) >> 3); ulBufferHeight = pstro->rclBkGround.bottom - pstro->rclBkGround.top; ulBufferBytes = lDelta * ulBufferHeight; if (((ULONG) lDelta > FIFTEEN_BITS) || (ulBufferHeight > FIFTEEN_BITS)) { // Fail if the math will have overflowed: return(FALSE); } // Use our temporary buffer if it's big enough, otherwise // allocate a buffer on the fly: if (ulBufferBytes >= TMP_BUFFER_SIZE) { // The textout is so big that I doubt this allocation will // cost a significant amount in performance: bTmpAlloc = TRUE; pvTmp = EngAllocUserMem(ulBufferBytes, ALLOC_TAG); if (pvTmp == NULL) return(FALSE); } else { bTmpAlloc = FALSE; pvTmp = ppdev->pvTmpBuffer; } psoTmp = ppdev->psoText; // Adjust 'lDelta' and 'pvScan0' of our temporary 1bpp surface object // so that when GDI starts drawing the text, it will begin in the // first dword psoTmp->pvScan0 = (BYTE*) pvTmp - (pstro->rclBkGround.top * lDelta) - ((pstro->rclBkGround.left & ~31) >> 3); psoTmp->lDelta = lDelta; ASSERTDD(((ULONG_PTR)psoTmp->pvScan0 &3)==0,"pvScan0 must be dword aligned"); ASSERTDD((lDelta & 3) == 0, "lDelta must be dword aligned"); // Get GDI to draw the text for us into a 1bpp buffer: boFore.iSolidColor = 1; boOpaque.iSolidColor = 0; bRet = EngTextOut(psoTmp, pstro, pfo, pco, NULL, &pstro->rclBkGround, &boFore, &boOpaque, NULL, 0x0d0d); if (bRet) { // Transparently blt the 1bpp buffer to the screen: xlc.iForeColor = pboFore->iSolidColor; xlc.iBackColor = pboOpaque->iSolidColor; xlo.pulXlate = (ULONG*) &xlc; prclClip = &pstro->rclBkGround; // Rop 'aacc' works out to a transparent blt, while 'cccc' works // out to an opaque blt: rop4 = (prclOpaque != NULL) ? 0xcccc : 0xaacc; if ((pco == NULL) || (pco->iDComplexity == DC_TRIVIAL)) { Output_Text: ppdev->pfnXfer1bpp(ppdev, 1, prclClip, rop4, psoTmp, (POINTL*) &pstro->rclBkGround, &pstro->rclBkGround, &xlo); } else if (pco->iDComplexity == DC_RECT) { if (bIntersect(&pco->rclBounds, &pstro->rclBkGround, &rclClip)) { prclClip = &rclClip; // Save some code size by jumping to the common // functions calls: goto Output_Text; } } else // pco->iDComplexity == DC_COMPLEX { CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, CD_ANY, 0); do { bMore = CLIPOBJ_bEnum(pco, sizeof(ce) - sizeof(RECTL), (ULONG*) &ce); ce.c = cIntersect(&pstro->rclBkGround, ce.arcl, ce.c); if (ce.c != 0) { ppdev->pfnXfer1bpp(ppdev, ce.c, &ce.arcl[0], rop4, psoTmp, (POINTL*) &pstro->rclBkGround, &pstro->rclBkGround, &xlo); } } while (bMore); } } // Free up any memory we allocated for the temp buffer: if (bTmpAlloc) { EngFreeUserMem(pvTmp); } return(TRUE); } /******************************Public*Routine******************************\ * VOID vMmGeneralText * * Handles any strings that need to be clipped, using the 'glyph * expansion' method. * \**************************************************************************/ VOID vMmGeneralText( PDEV* ppdev, STROBJ* pstro, CLIPOBJ* pco) { BYTE* pjMmBase; BOOL bMoreGlyphs; ULONG cGlyphOriginal; ULONG cGlyph; GLYPHPOS* pgpOriginal; GLYPHPOS* pgp; GLYPHBITS* pgb; POINTL ptlOrigin; BOOL bMore; CLIPENUM ce; RECTL* prclClip; ULONG ulCharInc; LONG cxGlyph; LONG cyGlyph; BYTE* pjGlyph; LONG cj; LONG cw; LONG xLeft; LONG yTop; LONG xRight; LONG yBottom; LONG xBias; LONG lDelta; LONG cx; LONG cy; BYTE iDComplexity; ASSERTDD(pco != NULL, "Don't expect NULL clip objects here"); pjMmBase = ppdev->pjMmBase; do { if (pstro->pgp != NULL) { // There's only the one batch of glyphs, so save ourselves // a call: pgpOriginal = pstro->pgp; cGlyphOriginal = pstro->cGlyphs; bMoreGlyphs = FALSE; } else { bMoreGlyphs = STROBJ_bEnum(pstro, &cGlyphOriginal, &pgpOriginal); } if (cGlyphOriginal > 0) { ulCharInc = pstro->ulCharInc; iDComplexity = (pco == NULL) ? DC_TRIVIAL : pco->iDComplexity; if (iDComplexity != DC_COMPLEX) { // We could call 'cEnumStart' and 'bEnum' when the clipping is // DC_RECT, but the last time I checked, those two calls took // more than 150 instructions to go through GDI. Since // 'rclBounds' already contains the DC_RECT clip rectangle, // and since it's such a common case, we'll special case it: bMore = FALSE; ce.c = 1; if (iDComplexity == DC_TRIVIAL) prclClip = &grclMax; else prclClip = &pco->rclBounds; goto SingleRectangle; } CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, CD_ANY, 0); do { bMore = CLIPOBJ_bEnum(pco, sizeof(ce), (ULONG*) &ce); for (prclClip = &ce.arcl[0]; ce.c != 0; ce.c--, prclClip++) { SingleRectangle: pgp = pgpOriginal; cGlyph = cGlyphOriginal; pgb = pgp->pgdf->pgb; ptlOrigin.x = pgb->ptlOrigin.x + pgp->ptl.x; ptlOrigin.y = pgb->ptlOrigin.y + pgp->ptl.y; // Loop through all the glyphs for this rectangle: while (TRUE) { cxGlyph = pgb->sizlBitmap.cx; cyGlyph = pgb->sizlBitmap.cy; pjGlyph = pgb->aj; if ((prclClip->left <= ptlOrigin.x) && (prclClip->top <= ptlOrigin.y) && (prclClip->right >= ptlOrigin.x + cxGlyph) && (prclClip->bottom >= ptlOrigin.y + cyGlyph)) { //----------------------------------------------------- // Unclipped glyph IO_FIFO_WAIT(ppdev, 4); MM_CUR_X(ppdev, pjMmBase, ptlOrigin.x); MM_CUR_Y(ppdev, pjMmBase, ptlOrigin.y); MM_MAJ_AXIS_PCNT(ppdev, pjMmBase, cxGlyph - 1); MM_MIN_AXIS_PCNT(ppdev, pjMmBase, cyGlyph - 1); IO_GP_WAIT(ppdev); if (cxGlyph <= 8) { //----------------------------------------------------- // 1 to 8 pels in width MM_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | BUS_SIZE_16)); CHECK_DATA_READY(ppdev); MM_TRANSFER_BYTE_THIN(ppdev, pjMmBase, pjGlyph, cyGlyph); CHECK_DATA_COMPLETE(ppdev); } else if (cxGlyph <= 16) { //----------------------------------------------------- // 9 to 16 pels in width MM_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | BUS_SIZE_16)); CHECK_DATA_READY(ppdev); MM_TRANSFER_WORD_ALIGNED(ppdev, pjMmBase, pjGlyph, cyGlyph); CHECK_DATA_COMPLETE(ppdev); } else { lDelta = (cxGlyph + 7) >> 3; if (!(lDelta & 1)) { //----------------------------------------------------- // Even number of bytes in width MM_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | BUS_SIZE_16)); CHECK_DATA_READY(ppdev); MM_TRANSFER_WORD_ALIGNED(ppdev, pjMmBase, pjGlyph, ((lDelta * cyGlyph) >> 1)); CHECK_DATA_COMPLETE(ppdev); } else { //----------------------------------------------------- // Odd number of bytes in width // We revert to byte transfers instead of word transfers // because word transfers would cause us to do unaligned // reads for every second scan, which could cause us to // read past the end of the glyph bitmap, and access // violate. MM_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | BUS_SIZE_16)); CHECK_DATA_READY(ppdev); MM_TRANSFER_WORD_ODD(ppdev, pjMmBase, pjGlyph, lDelta, cyGlyph); CHECK_DATA_COMPLETE(ppdev); } } } else { //----------------------------------------------------- // Clipped glyph // Find the intersection of the glyph rectangle // and the clip rectangle: xLeft = max(prclClip->left, ptlOrigin.x); yTop = max(prclClip->top, ptlOrigin.y); xRight = min(prclClip->right, ptlOrigin.x + cxGlyph); yBottom = min(prclClip->bottom, ptlOrigin.y + cyGlyph); // Check for trivial rejection: if (((cx = xRight - xLeft) > 0) && ((cy = yBottom - yTop) > 0)) { IO_FIFO_WAIT(ppdev, 5); xBias = (xLeft - ptlOrigin.x) & 7; if (xBias != 0) { // 'xBias' is the bit position in the monochrome glyph // bitmap of the first pixel to be lit, relative to // the start of the byte. That is, if 'xBias' is 2, // then the first unclipped pixel is represented by bit // 2 of the corresponding bitmap byte. // // Normally, the accelerator expects bit 0 to be the // first lit byte. We use the scissors so that the // first 'xBias' bits of the byte will not be displayed. // // (What we're doing is simply aligning the monochrome // blt using the hardware clipping.) MM_SCISSORS_L(ppdev, pjMmBase, xLeft); xLeft -= xBias; cx += xBias; } MM_CUR_X(ppdev, pjMmBase, xLeft); MM_CUR_Y(ppdev, pjMmBase, yTop); MM_MAJ_AXIS_PCNT(ppdev, pjMmBase, cx - 1); MM_MIN_AXIS_PCNT(ppdev, pjMmBase, cy - 1); lDelta = (cxGlyph + 7) >> 3; pjGlyph += (yTop - ptlOrigin.y) * lDelta + ((xLeft - ptlOrigin.x) >> 3); cj = (cx + 7) >> 3; IO_GP_WAIT(ppdev); MM_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | BUS_SIZE_16)); CHECK_DATA_READY(ppdev); // We use byte transfers because I don't expect we'll be // asked to clip many large glyphs where it would be // worth the overhead of setting up for word transfers: do { MM_TRANSFER_BYTE(ppdev, pjMmBase, pjGlyph, cj); pjGlyph += lDelta; } while (--cy != 0); CHECK_DATA_COMPLETE(ppdev); if (xBias != 0) { // Reset the scissors if we used it: IO_FIFO_WAIT(ppdev, 1); MM_ABS_SCISSORS_L(ppdev, pjMmBase, 0); } } } if (--cGlyph == 0) break; // Get ready for next glyph: pgp++; pgb = pgp->pgdf->pgb; if (ulCharInc == 0) { ptlOrigin.x = pgp->ptl.x + pgb->ptlOrigin.x; ptlOrigin.y = pgp->ptl.y + pgb->ptlOrigin.y; } else { ptlOrigin.x += ulCharInc; } } } } while (bMore); } } while (bMoreGlyphs); } /******************************Public*Routine******************************\ * CACHEDFONT* pcfAllocateCachedFont() * * Initializes our font data structure. * \**************************************************************************/ CACHEDFONT* pcfAllocateCachedFont( PDEV* ppdev) { CACHEDFONT* pcf; CACHEDGLYPH** ppcg; LONG i; pcf = EngAllocMem(FL_ZERO_MEMORY, sizeof(CACHEDFONT), ALLOC_TAG); if (pcf != NULL) { // Note that we rely on FL_ZERO_MEMORY to zero 'pgaChain' and // 'cjAlloc': pcf->cgSentinel.hg = HGLYPH_SENTINEL; // Initialize the hash table entries to all point to our sentinel: for (ppcg = &pcf->apcg[0], i = GLYPH_HASH_SIZE; i != 0; i--, ppcg++) { *ppcg = &pcf->cgSentinel; } } return(pcf); } /******************************Public*Routine******************************\ * VOID vFreeCachedFont() * * Frees all memory associated with the cache we kept for this font. * \**************************************************************************/ VOID vFreeCachedFont( CACHEDFONT* pcf) { GLYPHALLOC* pga; GLYPHALLOC* pgaNext; pga = pcf->pgaChain; while (pga != NULL) { pgaNext = pga->pgaNext; EngFreeMem(pga); pga = pgaNext; } EngFreeMem(pcf); } /******************************Public*Routine******************************\ * VOID vTrimAndBitpackGlyph * * This routine takes a GDI byte-aligned glyphbits definition, trims off * any unused pixels on the sides, and creates a bit-packed result that * is a natural for the S3's monochrome expansion capabilities. * "Bit-packed" is where a small monochrome bitmap is packed with no * unused bits between strides. So if GDI gives us a 16x16 bitmap to * represent '.' that really only has a 2x2 array of lit pixels, we would * trim the result to give a single byte value of 0xf0. * * Use this routine if your monochrome expansion hardware can do bit-packed * expansion (this is the fastest method). If your hardware requires byte-, * word-, or dword-alignment on monochrome expansions, use * vTrimAndPackGlyph(). * * (This driver doesn't use this routine only because the hardware can't do * bit-packing!) * \**************************************************************************/ VOID vTrimAndBitpackGlyph( BYTE* pjBuf, // Note: Routine may touch preceding byte! BYTE* pjGlyph, LONG* pcxGlyph, LONG* pcyGlyph, POINTL* pptlOrigin, LONG* pcj) // For returning the count of bytes of the result { LONG cxGlyph; LONG cyGlyph; POINTL ptlOrigin; LONG cAlign; LONG lDelta; BYTE* pj; BYTE jBit; LONG cjSrcWidth; LONG lSrcSkip; LONG lDstSkip; LONG cRem; BYTE* pjSrc; BYTE* pjDst; LONG i; LONG j; BYTE jSrc; /////////////////////////////////////////////////////////////// // Trim the glyph cyGlyph = *pcyGlyph; cxGlyph = *pcxGlyph; ptlOrigin = *pptlOrigin; cAlign = 0; lDelta = (cxGlyph + 7) >> 3; // Trim off any zero rows at the bottom of the glyph: pj = pjGlyph + cyGlyph * lDelta; // One past last byte in glyph while (cyGlyph > 0) { i = lDelta; do { if (*(--pj) != 0) goto Done_Bottom_Trim; } while (--i != 0); // The entire last row has no lit pixels, so simply skip it: cyGlyph--; } ASSERTDD(cyGlyph == 0, "cyGlyph should only be zero here"); // We found a space character. Set both dimensions to zero, so // that it's easy to special-case later: cxGlyph = 0; Done_Bottom_Trim: // If cxGlyph != 0, we know that the glyph has at least one non-zero // row and column. By exploiting this knowledge, we can simplify our // end-of-loop tests, because we don't have to check to see if we've // decremented either 'cyGlyph' or 'cxGlyph' to zero: if (cxGlyph != 0) { // Trim off any zero rows at the top of the glyph: pj = pjGlyph; // First byte in glyph while (TRUE) { i = lDelta; do { if (*(pj++) != 0) goto Done_Top_Trim; } while (--i != 0); // The entire first row has no lit pixels, so simply skip it: cyGlyph--; ptlOrigin.y++; pjGlyph = pj; } Done_Top_Trim: // Trim off any zero columns at the right edge of the glyph: while (TRUE) { j = cxGlyph - 1; pj = pjGlyph + (j >> 3); // Last byte in first row of glyph jBit = gajBit[j & 0x7]; i = cyGlyph; do { if ((*pj & jBit) != 0) goto Done_Right_Trim; pj += lDelta; } while (--i != 0); // The entire last column has no lit pixels, so simply skip it: cxGlyph--; } Done_Right_Trim: // Trim off any zero columns at the left edge of the glyph: while (TRUE) { pj = pjGlyph; // First byte in first row of glyph jBit = gajBit[cAlign]; i = cyGlyph; do { if ((*pj & jBit) != 0) goto Done_Left_Trim; pj += lDelta; } while (--i != 0); // The entire first column has no lit pixels, so simply skip it: ptlOrigin.x++; cxGlyph--; cAlign++; if (cAlign >= 8) { cAlign = 0; pjGlyph++; } } } Done_Left_Trim: /////////////////////////////////////////////////////////////// // Pack the glyph cjSrcWidth = (cxGlyph + cAlign + 7) >> 3; lSrcSkip = lDelta - cjSrcWidth; lDstSkip = ((cxGlyph + 7) >> 3) - cjSrcWidth - 1; cRem = ((cxGlyph - 1) & 7) + 1; // 0 -> 8 pjSrc = pjGlyph; pjDst = pjBuf; // Zero the buffer, because we're going to 'or' stuff into it: memset(pjBuf, 0, (cxGlyph * cyGlyph + 7) >> 3); // cAlign used to indicate which bit in the first byte of the unpacked // glyph was the first non-zero pixel column. Now, we flip it to // indicate which bit in the packed byte will receive the next non-zero // glyph bit: cAlign = (-cAlign) & 0x7; if (cAlign > 0) { // It would be bad if our trimming calculations were wrong, because // we assume any bits to the left of the 'cAlign' bit will be zero. // As a result of this decrement, we will 'or' those zero bits into // whatever byte precedes the glyph bits array: pjDst--; ASSERTDD((*pjSrc >> cAlign) == 0, "Trimmed off too many bits"); } for (i = cyGlyph; i != 0; i--) { for (j = cjSrcWidth; j != 0; j--) { // Note that we may modify a byte past the end of our // destination buffer, which is why we reserved an // extra byte: jSrc = *pjSrc; *(pjDst) |= (jSrc >> (cAlign)); *(pjDst + 1) |= (jSrc << (8 - cAlign)); pjSrc++; pjDst++; } pjSrc += lSrcSkip; pjDst += lDstSkip; cAlign += cRem; if (cAlign >= 8) { cAlign -= 8; pjDst++; } } /////////////////////////////////////////////////////////////// // Return results *pcxGlyph = cxGlyph; *pcyGlyph = cyGlyph; *pptlOrigin = ptlOrigin; *pcj = ((cxGlyph * cyGlyph) + 7) >> 3; } /******************************Public*Routine******************************\ * VOID vTrimAndPackGlyph * * This routine takes a GDI byte-aligned glyphbits definition, trims off * any unused pixels on the sides, and creates a word-algined result that * is a natural for the S3's monochrome expansion capabilities. * So if GDI gives us a 16x16 bitmap to represent '.' that really only * has a 2x2 array of lit pixels, we would trim the result to give 2 words * of 0xc000 and 0xc000. * * Use this routine if your monochrome expansion hardware requires byte-, * word-, or dword-alignment on monochrome expansions. If your hardware * can do bit-packed expansions, please use vTrimAndBitpackGlyph(), since * it will be faster. * \**************************************************************************/ VOID vTrimAndPackGlyph( PDEV* ppdev, BYTE* pjBuf, // Note: Routine may touch preceding byte! BYTE* pjGlyph, LONG* pcxGlyph, LONG* pcyGlyph, POINTL* pptlOrigin, LONG* pcj) // For returning the count of bytes of the result { LONG cxGlyph; LONG cyGlyph; POINTL ptlOrigin; LONG cAlign; LONG lDelta; BYTE* pj; BYTE jBit; LONG cjSrcWidth; LONG lSrcSkip; LONG lDstSkip; LONG lDstDelta; BYTE* pjSrc; BYTE* pjDst; LONG i; LONG j; BYTE jSrc; /////////////////////////////////////////////////////////////// // Trim the glyph cyGlyph = *pcyGlyph; cxGlyph = *pcxGlyph; ptlOrigin = *pptlOrigin; cAlign = 0; // let [x] denote the least integer greater than or equal to x // Set lDelta to be [cxGlyph/8]. This is the number of bytes occupied // by the pixels in the horizontal direction of the monochrome glyph. lDelta = (cxGlyph + 7) >> 3; // Trim off any zero rows at the bottom of the glyph: pj = pjGlyph + cyGlyph * lDelta; // One past last byte in glyph while (cyGlyph > 0) { i = lDelta; do { if (*(--pj) != 0) goto Done_Bottom_Trim; } while (--i != 0); // The entire last row has no lit pixels, so simply skip it: cyGlyph--; } ASSERTDD(cyGlyph == 0, "cyGlyph should only be zero here"); // We found a space character. Set both dimensions to zero, so // that it's easy to special-case later: cxGlyph = 0; Done_Bottom_Trim: // If cxGlyph != 0, we know that the glyph has at least one non-zero // row and column. By exploiting this knowledge, we can simplify our // end-of-loop tests, because we don't have to check to see if we've // decremented either 'cyGlyph' or 'cxGlyph' to zero: if (cxGlyph != 0) { // Trim off any zero rows at the top of the glyph: pj = pjGlyph; // First byte in glyph while (TRUE) { i = lDelta; do { if (*(pj++) != 0) goto Done_Top_Trim; } while (--i != 0); // The entire first row has no lit pixels, so simply skip it: cyGlyph--; ptlOrigin.y++; pjGlyph = pj; } Done_Top_Trim: // Trim off any zero columns at the right edge of the glyph: while (TRUE) { j = cxGlyph - 1; pj = pjGlyph + (j >> 3); // Last byte in first row of glyph jBit = gajBit[j & 0x7]; i = cyGlyph; do { if ((*pj & jBit) != 0) goto Done_Right_Trim; pj += lDelta; } while (--i != 0); // The entire last column has no lit pixels, so simply skip it: cxGlyph--; } Done_Right_Trim: // Trim off any zero columns at the left edge of the glyph: while (TRUE) { pj = pjGlyph; // First byte in first row of glyph jBit = gajBit[cAlign]; i = cyGlyph; do { if ((*pj & jBit) != 0) goto Done_Left_Trim; pj += lDelta; } while (--i != 0); // The entire first column has no lit pixels, so simply skip it: ptlOrigin.x++; cxGlyph--; cAlign++; if (cAlign >= 8) { cAlign = 0; pjGlyph++; } } } Done_Left_Trim: /////////////////////////////////////////////////////////////// // Pack the glyph // byte count of cell size (trimmed width + blank left columns). cjSrcWidth = (cxGlyph + cAlign + 7) >> 3; // difference between cell width and trimmed glyph width. lSrcSkip = lDelta - cjSrcWidth; // trimmed glyph width in bytes. lDstDelta = (cxGlyph + 7) >> 3; // Make the glyphs 'word-packed' (i.e., every scan is word aligned) // unless in 24bpp mode, in which case we have to use 32 bit bus size, // which in turn requires dword packing. if (ppdev->iBitmapFormat == BMF_24BPP) lDstDelta = (lDstDelta + 3) & ~3; else lDstDelta = (lDstDelta + 1) & ~1; lDstSkip = lDstDelta - cjSrcWidth; pjSrc = pjGlyph; // Start of trimmed glyph, not including empty left columns. pjDst = pjBuf; // Zero the first byte of the buffer, because we're going to 'or' stuff // into it: *pjDst = 0; // cAlign used to indicate which bit in the first byte of the unpacked // glyph was the first non-zero pixel column. Now, we flip it to // indicate which bit in the packed byte will receive the next non-zero // glyph bit: cAlign = (-cAlign) & 0x7; if (cAlign > 0) { // It would be bad if our trimming calculations were wrong, because // we assume any bits to the left of the 'cAlign' bit will be zero. // As a result of this decrement, we will 'or' those zero bits into // whatever byte precedes the glyph bits array: pjDst--; ASSERTDD((*pjSrc >> cAlign) == 0, "Trimmed off too many bits"); } for (i = cyGlyph; i != 0; i--) { for (j = cjSrcWidth; j != 0; j--) { // Note that we may modify a byte past the end of our // destination buffer, which is why we reserved an // extra byte: jSrc = *pjSrc; *(pjDst) |= (jSrc >> (cAlign)); *(pjDst + 1) = (jSrc << (8 - cAlign)); pjSrc++; pjDst++; } pjSrc += lSrcSkip; pjDst += lDstSkip; } /////////////////////////////////////////////////////////////// // Return results *pcxGlyph = cxGlyph; *pcyGlyph = cyGlyph; *pptlOrigin = ptlOrigin; *pcj = lDstDelta * cyGlyph; } /******************************Public*Routine******************************\ * LONG cjPutGlyphInCache * * Figures out where to be a glyph in off-screen memory, copies it * there, and fills in any other data we'll need to display the glyph. * * This routine is rather device-specific, and will have to be extensively * modified for other display adapters. * * Returns the number of bytes taken by the cached glyph bits. * \**************************************************************************/ LONG cjPutGlyphInCache( PDEV* ppdev, CACHEDGLYPH* pcg, GLYPHBITS* pgb) { BYTE* pjGlyph; LONG cxGlyph; LONG cyGlyph; POINTL ptlOrigin; BYTE* pjSrc; ULONG* pulDst; LONG i; LONG cPels; ULONG ulGlyphThis; ULONG ulGlyphNext; ULONG ul; ULONG ulStart; LONG cj; pjGlyph = pgb->aj; cyGlyph = pgb->sizlBitmap.cy; cxGlyph = pgb->sizlBitmap.cx; ptlOrigin = pgb->ptlOrigin; vTrimAndPackGlyph(ppdev, (BYTE*) &pcg->ad, pjGlyph, &cxGlyph, &cyGlyph, &ptlOrigin, &cj); /////////////////////////////////////////////////////////////// // Initialize the glyph fields pcg->ptlOrigin = ptlOrigin; pcg->cxLessOne = cxGlyph - 1; pcg->cyLessOne = cyGlyph - 1; pcg->cxcyLessOne = PACKXY(cxGlyph - 1, cyGlyph - 1); pcg->cw = (cj + 1) >> 1; pcg->cd = (cj + 3) >> 2; return(cj); } /******************************Public*Routine******************************\ * CACHEDGLYPH* pcgNew() * * Creates a new CACHEDGLYPH structure for keeping track of the glyph in * off-screen memory. bPutGlyphInCache is called to actually put the glyph * in off-screen memory. * * This routine should be reasonably device-independent, as bPutGlyphInCache * will contain most of the code that will have to be modified for other * display adapters. * \**************************************************************************/ CACHEDGLYPH* pcgNew( PDEV* ppdev, CACHEDFONT* pcf, GLYPHPOS* pgp) { GLYPHBITS* pgb; GLYPHALLOC* pga; CACHEDGLYPH* pcg; LONG cjCachedGlyph; HGLYPH hg; LONG iHash; CACHEDGLYPH* pcgFind; LONG cjGlyphRow; LONG cj; // First, calculate the amount of storage we'll need for this glyph: pgb = pgp->pgdf->pgb; // The glyphs are 'word-packed': cjGlyphRow = ((pgb->sizlBitmap.cx + 15) & ~15) >> 3; cjCachedGlyph = sizeof(CACHEDGLYPH) + (pgb->sizlBitmap.cy * cjGlyphRow); // Reserve an extra byte at the end for temporary usage by our pack // routine: cjCachedGlyph++; if (cjCachedGlyph > pcf->cjAlloc) { // Have to allocate a new glyph allocation structure: pga = EngAllocMem(FL_ZERO_MEMORY, GLYPH_ALLOC_SIZE, ALLOC_TAG); if (pga == NULL) { // It's safe to return at this time because we haven't // fatally altered any of our data structures: return(NULL); } // Add this allocation to the front of the allocation linked list, // so that we can free it later: pga->pgaNext = pcf->pgaChain; pcf->pgaChain = pga; // Now we've got a chunk of memory where we can store our cached // glyphs: pcf->pcgNew = &pga->acg[0]; pcf->cjAlloc = GLYPH_ALLOC_SIZE - (sizeof(*pga) - sizeof(pga->acg[0])); // It would be bad if we let in any glyphs that would be bigger // than our basic allocation size: ASSERTDD(cjCachedGlyph <= GLYPH_ALLOC_SIZE, "Woah, this is one big glyph!"); } pcg = pcf->pcgNew; /////////////////////////////////////////////////////////////// // Insert the glyph, in-order, into the list hanging off our hash // bucket: hg = pgp->hg; pcg->hg = hg; iHash = GLYPH_HASH_FUNC(hg); pcgFind = pcf->apcg[iHash]; if (pcgFind->hg > hg) { pcf->apcg[iHash] = pcg; pcg->pcgNext = pcgFind; } else { // The sentinel will ensure that we never fall off the end of // this list: while (pcgFind->pcgNext->hg < hg) pcgFind = pcgFind->pcgNext; // 'pcgFind' now points to the entry to the entry after which // we want to insert our new node: pcg->pcgNext = pcgFind->pcgNext; pcgFind->pcgNext = pcg; } cj = cjPutGlyphInCache(ppdev, pcg, pgp->pgdf->pgb); /////////////////////////////////////////////////////////////// // We now know the size taken up by the packed and trimmed glyph; // adjust the pointer to the next glyph accordingly. We only need // to ensure 'dword' alignment: cjCachedGlyph = sizeof(CACHEDGLYPH) + ((cj + 7) & ~7); pcf->pcgNew = (CACHEDGLYPH*) ((BYTE*) pcg + cjCachedGlyph); pcf->cjAlloc -= cjCachedGlyph; return(pcg); } /******************************Public*Routine******************************\ * BOOL bMmCachedProportionalText * * Draws proportionally spaced glyphs via glyph caching. * \**************************************************************************/ BOOL bMmCachedProportionalText( PDEV* ppdev, CACHEDFONT* pcf, GLYPHPOS* pgp, LONG cGlyph) { BYTE* pjMmBase; LONG xOffset; LONG yOffset; HGLYPH hg; CACHEDGLYPH* pcg; LONG cyLessOne; LONG x; LONG y; pjMmBase = ppdev->pjMmBase; xOffset = ppdev->xOffset; yOffset = ppdev->yOffset; // Ensure that there is enough room in the FIFO for the // coordinate and dimensions of the first glyph, so that we // don't accidentally hold the bus for a long to while a // previous big operation, such as a screen-to-screen blt, // is done. IO_FIFO_WAIT(ppdev, 4); do { hg = pgp->hg; pcg = pcf->apcg[GLYPH_HASH_FUNC(hg)]; while (pcg->hg < hg) pcg = pcg->pcgNext; // Traverse collision list, if any if (pcg->hg > hg) { // This will hopefully not be the common case (that is, // we will have a high cache hit rate), so if I were // writing this in Asm I would have this out-of-line // to avoid the jump around for the common case. // But the Pentium has branch prediction, so what the // heck. pcg = pcgNew(ppdev, pcf, pgp); if (pcg == NULL) return(FALSE); } // Space glyphs are trimmed to a height of zero, and we don't // even have to touch the hardware for them: cyLessOne = pcg->cyLessOne; if (cyLessOne >= 0) { x = pgp->ptl.x + pcg->ptlOrigin.x + xOffset; y = pgp->ptl.y + pcg->ptlOrigin.y + yOffset; DBG_FAKE_WAIT(ppdev, pjMmBase, 4); // For debug builds only MM_MAJ_AXIS_PCNT(ppdev, pjMmBase, pcg->cxLessOne); MM_MIN_AXIS_PCNT(ppdev, pjMmBase, cyLessOne); MM_ABS_CUR_X(ppdev, pjMmBase, x); MM_ABS_CUR_Y(ppdev, pjMmBase, y); IO_GP_WAIT(ppdev); MM_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | BUS_SIZE_16)); CHECK_DATA_READY(ppdev); MM_TRANSFER_WORD_ALIGNED(ppdev, pjMmBase, &pcg->ad[0], pcg->cw); CHECK_DATA_COMPLETE(ppdev); } } while (pgp++, --cGlyph != 0); return(TRUE); } /******************************Public*Routine******************************\ * BOOL bMmCachedClippedText * * Draws clipped text via glyph caching. * \**************************************************************************/ BOOL bMmCachedClippedText( PDEV* ppdev, CACHEDFONT* pcf, STROBJ* pstro, CLIPOBJ* pco) { BOOL bRet; BYTE* pjMmBase; LONG xOffset; LONG yOffset; BOOL bMoreGlyphs; ULONG cGlyphOriginal; ULONG cGlyph; BOOL bClippingSet; GLYPHPOS* pgpOriginal; GLYPHPOS* pgp; LONG xGlyph; LONG yGlyph; LONG x; LONG y; LONG xRight; LONG cyLessOne; BOOL bMore; CLIPENUM ce; RECTL* prclClip; ULONG ulCharInc; HGLYPH hg; CACHEDGLYPH* pcg; BYTE iDComplexity; bRet = TRUE; pjMmBase = ppdev->pjMmBase; xOffset = ppdev->xOffset; yOffset = ppdev->yOffset; ulCharInc = pstro->ulCharInc; // Ensure that there is enough room in the FIFO for the // coordinate and dimensions of the first glyph, so that we // don't accidentally hold the bus for a long to while a // previous big operation, such as a screen-to-screen blt, // is done. IO_FIFO_WAIT(ppdev, 4); do { if (pstro->pgp != NULL) { // There's only the one batch of glyphs, so save ourselves // a call: pgpOriginal = pstro->pgp; cGlyphOriginal = pstro->cGlyphs; bMoreGlyphs = FALSE; } else { bMoreGlyphs = STROBJ_bEnum(pstro, &cGlyphOriginal, &pgpOriginal); } iDComplexity = (pco == NULL) ? DC_TRIVIAL : pco->iDComplexity; if (cGlyphOriginal > 0) { if (iDComplexity != DC_COMPLEX) { // We could call 'cEnumStart' and 'bEnum' when the clipping is // DC_RECT, but the last time I checked, those two calls took // more than 150 instructions to go through GDI. Since // 'rclBounds' already contains the DC_RECT clip rectangle, // and since it's such a common case, we'll special case it: bMore = FALSE; ce.c = 1; if (iDComplexity == DC_TRIVIAL) prclClip = &grclMax; else prclClip = &pco->rclBounds; goto SingleRectangle; } CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, CD_ANY, 0); do { bMore = CLIPOBJ_bEnum(pco, sizeof(ce), (ULONG*) &ce); for (prclClip = &ce.arcl[0]; ce.c != 0; ce.c--, prclClip++) { SingleRectangle: // We don't always simply set the clipping rectangle here // because it may actually end up that no text intersects // this clip rectangle, so it would be for naught. This // actually happens a lot when using NT's analog clock set // to always-on-top, with a round shape: bClippingSet = FALSE; pgp = pgpOriginal; cGlyph = cGlyphOriginal; // We can't yet convert to absolute coordinates by adding // in 'xOffset' or 'yOffset' here because we have yet to // compare the coordinates to 'prclClip': xGlyph = pgp->ptl.x; yGlyph = pgp->ptl.y; // Loop through all the glyphs for this rectangle: while (TRUE) { hg = pgp->hg; pcg = pcf->apcg[GLYPH_HASH_FUNC(hg)]; while (pcg->hg < hg) pcg = pcg->pcgNext; if (pcg->hg > hg) { // This will hopefully not be the common case (that is, // we will have a high cache hit rate), so if I were // writing this in Asm I would have this out-of-line // to avoid the jump around for the common case. // But the Pentium has branch prediction, so what the // heck. pcg = pcgNew(ppdev, pcf, pgp); if (pcg == NULL) { bRet = FALSE; goto AllDone; } } // Space glyphs are trimmed to a height of zero, and we don't // even have to touch the hardware for them: cyLessOne = pcg->cyLessOne; if (cyLessOne >= 0) { y = pcg->ptlOrigin.y + yGlyph; x = pcg->ptlOrigin.x + xGlyph; xRight = pcg->cxLessOne + x; // Do trivial rejection: if ((prclClip->right > x) && (prclClip->bottom > y) && (prclClip->left <= xRight) && (prclClip->top <= y + cyLessOne)) { // Lazily set the hardware clipping: if ((iDComplexity != DC_TRIVIAL) && (!bClippingSet)) { bClippingSet = TRUE; vSetClipping(ppdev, prclClip); // Wait here for same reason we do IO_FIFO_WAIT(4) above... IO_FIFO_WAIT(ppdev, 4); } DBG_FAKE_WAIT(ppdev, pjMmBase, 4); // For debug builds only MM_MAJ_AXIS_PCNT(ppdev, pjMmBase, pcg->cxLessOne); MM_MIN_AXIS_PCNT(ppdev, pjMmBase, cyLessOne); MM_ABS_CUR_X(ppdev, pjMmBase, xOffset + x); MM_ABS_CUR_Y(ppdev, pjMmBase, yOffset + y); IO_GP_WAIT(ppdev); MM_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | BUS_SIZE_16)); CHECK_DATA_READY(ppdev); MM_TRANSFER_WORD_ALIGNED(ppdev, pjMmBase, &pcg->ad[0], pcg->cw); CHECK_DATA_COMPLETE(ppdev); } } if (--cGlyph == 0) break; // Get ready for next glyph: pgp++; if (ulCharInc == 0) { xGlyph = pgp->ptl.x; yGlyph = pgp->ptl.y; } else { xGlyph += ulCharInc; } } } } while (bMore); } } while (bMoreGlyphs); AllDone: if (iDComplexity != DC_TRIVIAL) { vResetClipping(ppdev); } return(bRet); } /******************************Public*Routine******************************\ * BOOL bMmTextOut * * Outputs text using the 'buffer expansion' method. The CPU draws to a * 1bpp buffer, and the result is colour-expanded to the screen using the * hardware. * * Note that this is x86 only ('vFastText', which draws the glyphs to the * 1bpp buffer, is writen in Asm). * * If you're just getting your driver working, this is the fastest way to * bring up working accelerated text. All you have to do is write the * 'Xfer1bpp' function that's also used by the blt code. This * 'bBufferExpansion' routine shouldn't need to be modified at all. * \**************************************************************************/ BOOL bMmTextOut( SURFOBJ* pso, STROBJ* pstro, FONTOBJ* pfo, CLIPOBJ* pco, RECTL* prclOpaque, BRUSHOBJ* pboFore, BRUSHOBJ* pboOpaque) { PDEV* ppdev; DSURF* pdsurf; BYTE* pjMmBase; BOOL bGlyphExpand; BOOL bTextPerfectFit; ULONG cGlyph; BOOL bMoreGlyphs; GLYPHPOS* pgp; GLYPHBITS* pgb; BYTE* pjGlyph; LONG cyGlyph; POINTL ptlOrigin; LONG ulCharInc; BYTE iDComplexity; LONG lDelta; LONG cw; RECTL rclOpaque; CACHEDFONT* pcf; ppdev = (PDEV*) pso->dhpdev; pjMmBase = ppdev->pjMmBase; iDComplexity = (pco == NULL) ? DC_TRIVIAL : pco->iDComplexity; if (prclOpaque != NULL) { //////////////////////////////////////////////////////////// // Opaque Initialization //////////////////////////////////////////////////////////// if (iDComplexity == DC_TRIVIAL) { DrawOpaqueRect: IO_FIFO_WAIT(ppdev, 8); MM_FRGD_COLOR(ppdev, pjMmBase, pboOpaque->iSolidColor); MM_PIX_CNTL(ppdev, pjMmBase, ALL_ONES); MM_FRGD_MIX(ppdev, pjMmBase, FOREGROUND_COLOR | OVERPAINT); MM_CUR_X(ppdev, pjMmBase, prclOpaque->left); MM_CUR_Y(ppdev, pjMmBase, prclOpaque->top); MM_MAJ_AXIS_PCNT(ppdev, pjMmBase, prclOpaque->right - prclOpaque->left - 1); MM_MIN_AXIS_PCNT(ppdev, pjMmBase, prclOpaque->bottom - prclOpaque->top - 1); MM_CMD(ppdev, pjMmBase, RECTANGLE_FILL | DRAWING_DIR_TBLRXM | DRAW | DIR_TYPE_XY | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE); } else if (iDComplexity == DC_RECT) { if (bIntersect(prclOpaque, &pco->rclBounds, &rclOpaque)) { prclOpaque = &rclOpaque; goto DrawOpaqueRect; } } else { vClipSolid(ppdev, 1, prclOpaque, pboOpaque->iSolidColor, pco); } // If we paint the glyphs in 'opaque' mode, we may not actually // have to draw the opaquing rectangle up-front -- the process // of laying down all the glyphs will automatically cover all // of the pixels in the opaquing rectangle. // // The condition that must be satisfied is that the text must // fit 'perfectly' such that the entire background rectangle is // covered, and none of the glyphs overlap (if the glyphs // overlap, such as for italics, they have to be drawn in // transparent mode after the opaquing rectangle is cleared). bTextPerfectFit = (pstro->flAccel & (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT | SO_MAXEXT_EQUAL_BM_SIDE | SO_CHAR_INC_EQUAL_BM_BASE)) == (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT | SO_MAXEXT_EQUAL_BM_SIDE | SO_CHAR_INC_EQUAL_BM_BASE); if (bTextPerfectFit) { // If the glyphs don't overlap, we can lay the glyphs down // in 'opaque' mode, which on the S3 I've found to be faster // than opaque mode: IO_FIFO_WAIT(ppdev, 7); MM_PIX_CNTL(ppdev, pjMmBase, CPU_DATA); MM_FRGD_MIX(ppdev, pjMmBase, FOREGROUND_COLOR | OVERPAINT); MM_BKGD_MIX(ppdev, pjMmBase, BACKGROUND_COLOR | OVERPAINT); MM_FRGD_COLOR(ppdev, pjMmBase, pboFore->iSolidColor); MM_BKGD_COLOR(ppdev, pjMmBase, pboOpaque->iSolidColor); goto SkipTransparentInitialization; } } //////////////////////////////////////////////////////////// // Transparent Initialization //////////////////////////////////////////////////////////// // Initialize the hardware for transparent text: IO_FIFO_WAIT(ppdev, 4); MM_PIX_CNTL(ppdev, pjMmBase, CPU_DATA); MM_FRGD_MIX(ppdev, pjMmBase, FOREGROUND_COLOR | OVERPAINT); MM_BKGD_MIX(ppdev, pjMmBase, BACKGROUND_COLOR | LEAVE_ALONE); MM_FRGD_COLOR(ppdev, pjMmBase, pboFore->iSolidColor); SkipTransparentInitialization: if ((pfo->cxMax <= GLYPH_CACHE_CX) && ((pstro->rclBkGround.bottom - pstro->rclBkGround.top) <= GLYPH_CACHE_CY)) { pcf = (CACHEDFONT*) pfo->pvConsumer; if (pcf == NULL) { pcf = pcfAllocateCachedFont(ppdev); if (pcf == NULL) return(FALSE); pfo->pvConsumer = pcf; } // Use our glyph cache: if ((iDComplexity == DC_TRIVIAL) && (pstro->ulCharInc == 0)) { do { if (pstro->pgp != NULL) { // There's only the one batch of glyphs, so save ourselves // a call: pgp = pstro->pgp; cGlyph = pstro->cGlyphs; bMoreGlyphs = FALSE; } else { bMoreGlyphs = STROBJ_bEnum(pstro, &cGlyph, &pgp); } if (cGlyph > 0) { if (!bMmCachedProportionalText(ppdev, pcf, pgp, cGlyph)) return(FALSE); } } while (bMoreGlyphs); } else { if (!bMmCachedClippedText(ppdev, pcf, pstro, pco)) return(FALSE); } } else { DISPDBG((4, "Text too big to cache: %li x %li", pfo->cxMax, pstro->rclBkGround.bottom - pstro->rclBkGround.top)); vMmGeneralText(ppdev, pstro, pco); } return(TRUE); } /******************************Public*Routine******************************\ * BOOL bNwCachedProportionalText * * Draws proportionally spaced glyphs via glyph caching. * \**************************************************************************/ BOOL bNwCachedProportionalText( PDEV* ppdev, CACHEDFONT* pcf, GLYPHPOS* pgp, LONG cGlyph) { BYTE* pjMmBase; LONG xOffset; LONG yOffset; HGLYPH hg; CACHEDGLYPH* pcg; LONG cxcyLessOne; LONG x; LONG y; USHORT busmode = BUS_SIZE_16; pjMmBase = ppdev->pjMmBase; xOffset = ppdev->xOffset; yOffset = ppdev->yOffset; // Ensure that there is enough room in the FIFO for the // coordinate and dimensions of the first glyph, so that we // don't accidentally hold the bus for a long to while a // previous big operation, such as a screen-to-screen blt, // is done. NW_FIFO_WAIT(ppdev, pjMmBase, 2); if (ppdev->iBitmapFormat == BMF_24BPP) busmode = BUS_SIZE_32; do { hg = pgp->hg; pcg = pcf->apcg[GLYPH_HASH_FUNC(hg)]; while (pcg->hg < hg) pcg = pcg->pcgNext; // Traverse collision list, if any if (pcg->hg > hg) { // This will hopefully not be the common case (that is, // we will have a high cache hit rate), so if I were // writing this in Asm I would have this out-of-line // to avoid the jump around for the common case. // But the Pentium has branch prediction, so what the // heck. pcg = pcgNew(ppdev, pcf, pgp); if (pcg == NULL) return(FALSE); } // Space glyphs are trimmed to a height of zero, and we don't // even have to touch the hardware for them: cxcyLessOne = pcg->cxcyLessOne; if (cxcyLessOne >= 0) { x = pgp->ptl.x + pcg->ptlOrigin.x + xOffset; y = pgp->ptl.y + pcg->ptlOrigin.y + yOffset; DBG_FAKE_WAIT(ppdev, pjMmBase, 2); // For debug builds only NW_ABS_CURXY_FAST(ppdev, pjMmBase, x, y); NW_ALT_PCNT_PACKED(ppdev, pjMmBase, cxcyLessOne); NW_GP_WAIT(ppdev, pjMmBase); NW_ALT_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | busmode)); CHECK_DATA_READY(ppdev); #if defined(_X86_) memcpy(pjMmBase, &pcg->ad[0], pcg->cd << 2); #else // Non-x86 platforms may be required to call the HAL to // the I/O, or to do memory barriers: MM_TRANSFER_DWORD_ALIGNED(ppdev, pjMmBase, &pcg->ad[0], pcg->cd); #endif CHECK_DATA_COMPLETE(ppdev); } } while (pgp++, --cGlyph != 0); return(TRUE); } /******************************Public*Routine******************************\ * BOOL bNwCachedClippedText * * Draws clipped text via glyph caching. * \**************************************************************************/ BOOL bNwCachedClippedText( PDEV* ppdev, CACHEDFONT* pcf, STROBJ* pstro, CLIPOBJ* pco) { BOOL bRet; BYTE* pjMmBase; LONG xOffset; LONG yOffset; BOOL bMoreGlyphs; ULONG cGlyphOriginal; ULONG cGlyph; BOOL bClippingSet; GLYPHPOS* pgpOriginal; GLYPHPOS* pgp; LONG xGlyph; LONG yGlyph; LONG x; LONG y; LONG xRight; LONG cyLessOne; BOOL bMore; CLIPENUM ce; RECTL* prclClip; ULONG ulCharInc; HGLYPH hg; CACHEDGLYPH* pcg; BYTE iDComplexity; USHORT busmode = BUS_SIZE_16; bRet = TRUE; pjMmBase = ppdev->pjMmBase; xOffset = ppdev->xOffset; yOffset = ppdev->yOffset; ulCharInc = pstro->ulCharInc; // Ensure that there is enough room in the FIFO for the // coordinate and dimensions of the first glyph, so that we // don't accidentally hold the bus for a long to while a // previous big operation, such as a screen-to-screen blt, // is done. NW_FIFO_WAIT(ppdev, pjMmBase, 2); if (ppdev->iBitmapFormat == BMF_24BPP) busmode = BUS_SIZE_32; do { if (pstro->pgp != NULL) { // There's only the one batch of glyphs, so save ourselves // a call: pgpOriginal = pstro->pgp; cGlyphOriginal = pstro->cGlyphs; bMoreGlyphs = FALSE; } else { bMoreGlyphs = STROBJ_bEnum(pstro, &cGlyphOriginal, &pgpOriginal); } iDComplexity = (pco == NULL) ? DC_TRIVIAL : pco->iDComplexity; if (cGlyphOriginal > 0) { if (iDComplexity != DC_COMPLEX) { // We could call 'cEnumStart' and 'bEnum' when the clipping is // DC_RECT, but the last time I checked, those two calls took // more than 150 instructions to go through GDI. Since // 'rclBounds' already contains the DC_RECT clip rectangle, // and since it's such a common case, we'll special case it: bMore = FALSE; ce.c = 1; if (iDComplexity == DC_TRIVIAL) prclClip = &grclMax; else prclClip = &pco->rclBounds; goto SingleRectangle; } CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, CD_ANY, 0); do { bMore = CLIPOBJ_bEnum(pco, sizeof(ce), (ULONG*) &ce); for (prclClip = &ce.arcl[0]; ce.c != 0; ce.c--, prclClip++) { SingleRectangle: // We don't always simply set the clipping rectangle here // because it may actually end up that no text intersects // this clip rectangle, so it would be for naught. This // actually happens a lot when using NT's analog clock set // to always-on-top, with a round shape: bClippingSet = FALSE; pgp = pgpOriginal; cGlyph = cGlyphOriginal; // We can't yet convert to absolute coordinates by adding // in 'xOffset' or 'yOffset' here because we have yet to // compare the coordinates to 'prclClip': xGlyph = pgp->ptl.x; yGlyph = pgp->ptl.y; // Loop through all the glyphs for this rectangle: while (TRUE) { hg = pgp->hg; pcg = pcf->apcg[GLYPH_HASH_FUNC(hg)]; while (pcg->hg < hg) pcg = pcg->pcgNext; if (pcg->hg > hg) { // This will hopefully not be the common case (that is, // we will have a high cache hit rate), so if I were // writing this in Asm I would have this out-of-line // to avoid the jump around for the common case. // But the Pentium has branch prediction, so what the // heck. pcg = pcgNew(ppdev, pcf, pgp); if (pcg == NULL) { bRet = FALSE; goto AllDone; } } // Space glyphs are trimmed to a height of zero, and we don't // even have to touch the hardware for them: cyLessOne = pcg->cyLessOne; if (cyLessOne >= 0) { y = pcg->ptlOrigin.y + yGlyph; x = pcg->ptlOrigin.x + xGlyph; xRight = pcg->cxLessOne + x; // Do trivial rejection: if ((prclClip->right > x) && (prclClip->bottom > y) && (prclClip->left <= xRight) && (prclClip->top <= y + cyLessOne)) { // Lazily set the hardware clipping: if ((iDComplexity != DC_TRIVIAL) && (!bClippingSet)) { bClippingSet = TRUE; vSetClipping(ppdev, prclClip); // Wait here for same reason we do NW_FIFO_WAIT(2) above... NW_FIFO_WAIT(ppdev, pjMmBase, 2); } DBG_FAKE_WAIT(ppdev, pjMmBase, 2); // For debug builds only NW_ABS_CURXY(ppdev, pjMmBase, xOffset + x, yOffset + y); NW_ALT_PCNT_PACKED(ppdev, pjMmBase, pcg->cxcyLessOne); NW_GP_WAIT(ppdev, pjMmBase); NW_ALT_CMD(ppdev, pjMmBase, (RECTANGLE_FILL | WAIT | DRAWING_DIR_TBLRXM | DRAW | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE | BYTE_SWAP | busmode)); CHECK_DATA_READY(ppdev); MM_TRANSFER_DWORD_ALIGNED(ppdev, pjMmBase, &pcg->ad[0], pcg->cd); CHECK_DATA_COMPLETE(ppdev); } } if (--cGlyph == 0) break; // Get ready for next glyph: pgp++; if (ulCharInc == 0) { xGlyph = pgp->ptl.x; yGlyph = pgp->ptl.y; } else { xGlyph += ulCharInc; } } } } while (bMore); } } while (bMoreGlyphs); AllDone: if (iDComplexity != DC_TRIVIAL) { vResetClipping(ppdev); } return(bRet); } /******************************Public*Routine******************************\ * BOOL bNwTextOut * * Outputs text using the 'buffer expansion' method. The CPU draws to a * 1bpp buffer, and the result is colour-expanded to the screen using the * hardware. * * Note that this is x86 only ('vFastText', which draws the glyphs to the * 1bpp buffer, is writen in Asm). * * If you're just getting your driver working, this is the fastest way to * bring up working accelerated text. All you have to do is write the * 'Xfer1bpp' function that's also used by the blt code. This * 'bBufferExpansion' routine shouldn't need to be modified at all. * \**************************************************************************/ BOOL bNwTextOut( SURFOBJ* pso, STROBJ* pstro, FONTOBJ* pfo, CLIPOBJ* pco, RECTL* prclOpaque, BRUSHOBJ* pboFore, BRUSHOBJ* pboOpaque) { PDEV* ppdev; DSURF* pdsurf; BYTE* pjMmBase; BOOL bGlyphExpand; BOOL bTextPerfectFit; ULONG cGlyph; BOOL bMoreGlyphs; GLYPHPOS* pgp; GLYPHBITS* pgb; BYTE* pjGlyph; LONG cyGlyph; POINTL ptlOrigin; LONG ulCharInc; BYTE iDComplexity; LONG lDelta; LONG cw; RECTL rclOpaque; CACHEDFONT* pcf; LONG xOffset; LONG yOffset; ppdev = (PDEV*) pso->dhpdev; pjMmBase = ppdev->pjMmBase; xOffset = ppdev->xOffset; yOffset = ppdev->yOffset; iDComplexity = (pco == NULL) ? DC_TRIVIAL : pco->iDComplexity; if (prclOpaque != NULL) { //////////////////////////////////////////////////////////// // Opaque Initialization //////////////////////////////////////////////////////////// if (iDComplexity == DC_TRIVIAL) { DrawOpaqueRect: NW_FIFO_WAIT(ppdev, pjMmBase, 6); NW_FRGD_COLOR(ppdev, pjMmBase, pboOpaque->iSolidColor); MM_PIX_CNTL(ppdev, pjMmBase, ALL_ONES); MM_FRGD_MIX(ppdev, pjMmBase, FOREGROUND_COLOR | OVERPAINT); NW_ABS_CURXY_FAST(ppdev, pjMmBase, prclOpaque->left + xOffset, prclOpaque->top + yOffset); NW_ALT_PCNT(ppdev, pjMmBase, prclOpaque->right - prclOpaque->left - 1, prclOpaque->bottom - prclOpaque->top - 1); NW_ALT_CMD(ppdev, pjMmBase, RECTANGLE_FILL | DRAWING_DIR_TBLRXM | DRAW | DIR_TYPE_XY | LAST_PIXEL_ON | MULTIPLE_PIXELS | WRITE); } else if (iDComplexity == DC_RECT) { if (bIntersect(prclOpaque, &pco->rclBounds, &rclOpaque)) { prclOpaque = &rclOpaque; goto DrawOpaqueRect; } } else { vClipSolid(ppdev, 1, prclOpaque, pboOpaque->iSolidColor, pco); } // If we paint the glyphs in 'opaque' mode, we may not actually // have to draw the opaquing rectangle up-front -- the process // of laying down all the glyphs will automatically cover all // of the pixels in the opaquing rectangle. // // The condition that must be satisfied is that the text must // fit 'perfectly' such that the entire background rectangle is // covered, and none of the glyphs overlap (if the glyphs // overlap, such as for italics, they have to be drawn in // transparent mode after the opaquing rectangle is cleared). bTextPerfectFit = (pstro->flAccel & (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT | SO_MAXEXT_EQUAL_BM_SIDE | SO_CHAR_INC_EQUAL_BM_BASE)) == (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT | SO_MAXEXT_EQUAL_BM_SIDE | SO_CHAR_INC_EQUAL_BM_BASE); if (bTextPerfectFit) { // If the glyphs don't overlap, we can lay the glyphs down // in 'opaque' mode, which on the S3 I've found to be faster // than opaque mode: NW_FIFO_WAIT(ppdev, pjMmBase, 4); MM_PIX_CNTL(ppdev, pjMmBase, CPU_DATA); NW_ALT_MIX(ppdev, pjMmBase, FOREGROUND_COLOR | OVERPAINT, BACKGROUND_COLOR | OVERPAINT); NW_FRGD_COLOR(ppdev, pjMmBase, pboFore->iSolidColor); NW_BKGD_COLOR(ppdev, pjMmBase, pboOpaque->iSolidColor); goto SkipTransparentInitialization; } } //////////////////////////////////////////////////////////// // Transparent Initialization //////////////////////////////////////////////////////////// // Initialize the hardware for transparent text: NW_FIFO_WAIT(ppdev, pjMmBase, 3); MM_PIX_CNTL(ppdev, pjMmBase, CPU_DATA); NW_ALT_MIX(ppdev, pjMmBase, FOREGROUND_COLOR | OVERPAINT, BACKGROUND_COLOR | LEAVE_ALONE); NW_FRGD_COLOR(ppdev, pjMmBase, pboFore->iSolidColor); SkipTransparentInitialization: if ((pfo->cxMax <= GLYPH_CACHE_CX) && ((pstro->rclBkGround.bottom - pstro->rclBkGround.top) <= GLYPH_CACHE_CY)) { pcf = (CACHEDFONT*) pfo->pvConsumer; if (pcf == NULL) { pcf = pcfAllocateCachedFont(ppdev); if (pcf == NULL) return(FALSE); pfo->pvConsumer = pcf; } // Use our glyph cache: if ((iDComplexity == DC_TRIVIAL) && (pstro->ulCharInc == 0)) { do { if (pstro->pgp != NULL) { // There's only the one batch of glyphs, so save ourselves // a call: pgp = pstro->pgp; cGlyph = pstro->cGlyphs; bMoreGlyphs = FALSE; } else { bMoreGlyphs = STROBJ_bEnum(pstro, &cGlyph, &pgp); } if (cGlyph > 0) { if (!bNwCachedProportionalText(ppdev, pcf, pgp, cGlyph)) return(FALSE); } } while (bMoreGlyphs); } else { if (!bNwCachedClippedText(ppdev, pcf, pstro, pco)) return(FALSE); } } else { DISPDBG((4, "Text too big to cache: %li x %li", pfo->cxMax, pstro->rclBkGround.bottom - pstro->rclBkGround.top)); // Can't do large glyphs via accelerator at 24bpp: if (ppdev->iBitmapFormat == BMF_24BPP) { BANK bnk; BOOL b = TRUE; vBankStart(ppdev, (prclOpaque!= NULL) ? prclOpaque : &pstro->rclBkGround, pco, &bnk); do { b &= EngTextOut(bnk.pso, pstro, pfo, bnk.pco, NULL, prclOpaque, pboFore, pboOpaque, NULL, 0x0d0d); } while (bBankEnum(&bnk)); return b; } vMmGeneralText(ppdev, pstro, pco); } return(TRUE); } /******************************Public*Routine******************************\ * BOOL DrvTextOut * * Calls the appropriate text drawing routine. * \**************************************************************************/ BOOL DrvTextOut( SURFOBJ* pso, STROBJ* pstro, FONTOBJ* pfo, CLIPOBJ* pco, RECTL* prclExtra, // If we had set GCAPS_HORIZSTRIKE, we would have // to fill these extra rectangles (it is used // largely for underlines). It's not a big // performance win (GDI will call our DrvBitBlt // to draw the extra rectangles). RECTL* prclOpaque, BRUSHOBJ* pboFore, BRUSHOBJ* pboOpaque, POINTL* pptlBrush, // Always unused, unless GCAPS_ARBRUSHOPAQUE set MIX mix) // Always a copy mix -- 0x0d0d { PDEV* ppdev; DSURF* pdsurf; pdsurf = (DSURF*) pso->dhsurf; ppdev = (PDEV*) pso->dhpdev; ASSERTDD(!(pdsurf->dt & DT_DIB), "Didn't expect DT_DIB"); ppdev->xOffset = pdsurf->x; ppdev->yOffset = pdsurf->y; // There seems to be a problem with 24 bpp accelerated large text // on s3 diamond 968 so for now, punt to GDI // The DDI spec says we'll only ever get foreground and background // mixes of R2_COPYPEN: ASSERTDD(mix == 0x0d0d, "GDI should only give us a copy mix"); return(ppdev->pfnTextOut(pso, pstro, pfo, pco, prclOpaque, pboFore, pboOpaque)); } /******************************Public*Routine******************************\ * BOOL bEnableText * * Performs the necessary setup for the text drawing subcomponent. * \**************************************************************************/ BOOL bEnableText( PDEV* ppdev) { SIZEL sizl; HBITMAP hbm; if (ppdev->pfnTextOut == bIoTextOut) { // We need to allocate a temporary 1bpp surface object if we're // going to have GDI draw the glyphs for us: sizl.cx = ppdev->cxMemory; sizl.cy = ppdev->cyMemory; // We will be mucking with the surface's 'pvScan0' value, so we // simply must pass in a non-NULL 'pvBits' value to EngCreateBitmap: hbm = EngCreateBitmap(sizl, sizl.cx, BMF_1BPP, 0, ppdev->pvTmpBuffer); if (hbm == 0) return(FALSE); ppdev->psoText = EngLockSurface((HSURF) hbm); if (ppdev->psoText == NULL) { EngDeleteSurface((HSURF) hbm); return(FALSE); } } return(TRUE); } /******************************Public*Routine******************************\ * VOID vDisableText * * Performs the necessary clean-up for the text drawing subcomponent. * \**************************************************************************/ VOID vDisableText(PDEV* ppdev) { HSURF hsurf; SURFOBJ* psoText; // Here we free any stuff allocated in 'bEnableText'. psoText = ppdev->psoText; if (psoText != NULL) { hsurf = psoText->hsurf; EngUnlockSurface(psoText); EngDeleteSurface(hsurf); } } /******************************Public*Routine******************************\ * VOID vAssertModeText * * Disables or re-enables the text drawing subcomponent in preparation for * full-screen entry/exit. * \**************************************************************************/ VOID vAssertModeText( PDEV* ppdev, BOOL bEnable) { // If we were to do off-screen glyph caching, we would probably want // to invalidate our cache here, because it will get destroyed when // we switch to full-screen. } /******************************Public*Routine******************************\ * VOID DrvDestroyFont * * Note: Don't forget to export this call in 'enable.c', otherwise you'll * get some pretty big memory leaks! * * We're being notified that the given font is being deallocated; clean up * anything we've stashed in the 'pvConsumer' field of the 'pfo'. * \**************************************************************************/ VOID DrvDestroyFont( FONTOBJ* pfo) { CACHEDFONT* pcf; pcf = pfo->pvConsumer; if (pcf != NULL) { vFreeCachedFont(pcf); pfo->pvConsumer = NULL; } }