/**************************************************************************\ * * Copyright (c) 1998 Microsoft Corporation * * Abstract: * * Engine text out routines. * * Revision History: * * 3/25/1999 cameronb * Created it. * \**************************************************************************/ #include "precomp.hpp" struct GlyphScanBuf { INT left; INT top; INT bottom; INT widthInBytes; DpRegion::Visibility visibility; }; const BYTE GRAYSCALE_LEVEL = 16; /**************************************************************************\ * * Function Description: * * Draws text at a position. * * Arguments: * * [IN] context - the context (matrix and clipping) * [IN] surface - the surface to fill * [IN] drawBounds - the surface bounds * [IN] text - the typeset text to be drawn * [IN] font - the font to use * [IN] fgBrush - the brush to use for the text * [IN] bgBrush - the brush to use for the background (default = NULL) * * Return Value: * * GpStatus - Ok or failure status * * Created: * * 3/25/1999 cameronb * \**************************************************************************/ GpStatus DpDriver::DrawGlyphs ( DrawGlyphData *drawGlyphData ) { GpStatus status = GenericError; HDC hdc = NULL; ASSERT(!drawGlyphData->glyphPathPos); // Choose appropriate brush behaviour switch(drawGlyphData->brush->Type) { case BrushTypeSolidColor: INT angle; // Passed from GetTextOutputHdc to GdiText if (!(drawGlyphData->flags & DG_NOGDI)) { hdc = drawGlyphData->context->GetTextOutputHdc( drawGlyphData->faceRealization, drawGlyphData->brush->SolidColor, drawGlyphData->surface, &angle ); } if (hdc) { BOOL isClip; BOOL usePathClipping = FALSE; SetupClipping(hdc, drawGlyphData->context, drawGlyphData->drawBounds, isClip, usePathClipping, FALSE); status = GdiText( hdc, angle, drawGlyphData->glyphs, drawGlyphData->glyphOrigins, drawGlyphData->glyphCount, drawGlyphData->rightToLeft ); RestoreClipping(hdc, isClip, usePathClipping); drawGlyphData->context->ReleaseTextOutputHdc(hdc); } else { status = SolidText( drawGlyphData->context, drawGlyphData->surface, drawGlyphData->drawBounds, drawGlyphData->brush->SolidColor, drawGlyphData->glyphPos, drawGlyphData->count, drawGlyphData->faceRealization->RealizationMethod(), drawGlyphData->rightToLeft ); } break; // case BrushRectGrad: // case BrushRadialGrad: case BrushTypeTextureFill: case BrushTypeHatchFill: // case BrushTriangleGrad: case BrushTypePathGradient: case BrushTypeLinearGradient: status = BrushText( drawGlyphData->context, drawGlyphData->surface, drawGlyphData->drawBounds, drawGlyphData->brush, drawGlyphData->glyphPos, drawGlyphData->count, drawGlyphData->faceRealization->RealizationMethod() ); break; default: status = GenericError; break; } return status; } static GpStatus OutputSolidNormalText ( DpContext* context, DpDriver *driver, DpBitmap* surface, const GpRect* drawBounds, GpColor color, const GpGlyphPos *glyphPos, INT count ) { DpScanBuffer scan( surface->Scan, driver, context, surface, (color.IsOpaque() && (!context->AntiAliasMode))); if (!scan.IsValid()) { return(GenericError); } FPUStateSaver fpuState; ARGB argb = color.GetPremultipliedValue(); DpOutputSolidColorSpan outputSolid(argb, &scan); DpClipRegion * clipRegion = NULL; if (context->VisibleClip.GetRectVisibility( drawBounds->X, drawBounds->Y, drawBounds->GetRight(), drawBounds->GetBottom()) != DpRegion::TotallyVisible) { clipRegion = &(context->VisibleClip); clipRegion->InitClipping(&outputSolid, drawBounds->Y); } for (int i = 0; i < count; i++) { INT left = glyphPos[i].GetLeft(); INT top = glyphPos[i].GetTop(); #if 0 printf("Drawing glyph at [%d,%d]\n", left, top); #endif INT widthInPixels = glyphPos[i].GetWidth(); INT right = left + widthInPixels; INT height = glyphPos[i].GetHeight(); INT bottom = top + height; INT widthInBytes = (widthInPixels + 7) / 8; const BYTE* mask = glyphPos[i].GetBits(); if (widthInPixels == 0 || height == 0) continue; ASSERT(mask != NULL); if (clipRegion != NULL) { // Clipping GpRect clippedRect; DpRegion::Visibility visibility = clipRegion->GetRectVisibility( left, top, right, bottom, &clippedRect ); if (visibility == DpRegion::Invisible) continue; for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { const BYTE* maskPtr = mask + my * widthInBytes; BYTE bit = 0x80; BYTE nextPixel = (*(maskPtr) & bit) ? 255 : 0; INT runStart = 0; for (INT mx = 0; mx < widthInPixels; mx++) { BYTE pixel = nextPixel; bit = (bit == 0x01) ? 0x80 : bit >> 1; nextPixel = (mx == widthInPixels - 1) ? 0 : ( (*(maskPtr + (mx + 1) / 8) & bit) ? 255 : 0); if (pixel != nextPixel) { if (pixel) { // Draw this run INT runLength = mx - runStart + 1; INT from = left + runStart; if (visibility == DpRegion::TotallyVisible) { // Draw the entire run FillMemoryInt32( scan.NextBuffer(from, y, runLength), runLength, argb); } else { // Clip the run INT to = from + runLength; // reference needed clipRegion->OutputSpan(y, from, to); } }; if (nextPixel) { // Start a new run runStart = mx + 1; } } } } } else { // No clipping for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { const BYTE* maskPtr = mask + my * widthInBytes; BYTE bit = 0x80; INT runLength = 0; INT runStart; for (INT mx = 0; mx < widthInPixels; mx++) { BOOL pixelOn = *(maskPtr + mx / 8) & bit; if (pixelOn) { if (runLength == 0) { // Start a new run runStart = mx; } runLength++; } if ( runLength > 0 && !pixelOn || runLength > 0 && mx == widthInPixels - 1 ) { // Finish this run and draw it FillMemoryInt32( scan.NextBuffer(left + runStart, y, runLength), runLength, argb ); runLength = 0; }; bit = (bit == 0x01) ? 0x80 : bit >> 1; } } } } if (clipRegion != NULL) { clipRegion->EndClipping(); } return(Ok); } static inline VOID GetGlyphDimensions( const GpGlyphPos & glyphPos, INT * left, INT * top, INT * widthInPixels, INT * right, INT * height, INT * bottom ) { *left = glyphPos.GetLeft(); *top = glyphPos.GetTop(); *widthInPixels = glyphPos.GetWidth(); *right = *left + *widthInPixels; *height = glyphPos.GetHeight(); *bottom = *top + *height; } // GetGlyphDimensions template class DpOutputOptimizedSpan : public DpOutputSpan { public: typedef MASKTYPE SCANMASKTYPE; DpOutputOptimizedSpan(DpScanBuffer * scan) : Scan(scan) {} virtual BOOL IsValid() const { return TRUE; } void SetMaskAndLeft(const MASKTYPE * maskPtr, INT left) { ASSERT(maskPtr != 0); MaskPtr = maskPtr; Left = left; } protected: DpScanBuffer * Scan; INT Left; const MASKTYPE *MaskPtr; }; template static GpStatus OutputTextOptimized( DpContext* context, const GpRect* drawBounds, const GpGlyphPos *glyphPos, INT count, OUTPUT_SPAN & outputSpan ) { ASSERT(context->CompositingMode == CompositingModeSourceOver); DpClipRegion * clipRegion = &(context->VisibleClip); clipRegion->InitClipping(&outputSpan, drawBounds->Y); // measure bounding box for all glyphs INT minX = INT_MAX, maxX = INT_MIN, minY = INT_MAX, maxY = INT_MIN; for (INT i = 0; i < count; ++i) { INT left, top, widthInPixels, right, height, bottom; GetGlyphDimensions(glyphPos[i], &left, &top, &widthInPixels, &right, &height, &bottom); if (widthInPixels == 0 || height == 0 || clipRegion->GetRectVisibility(left, top, right, bottom) == DpRegion::Invisible) continue; if (left < minX) minX = left; if (top < minY) minY = top; if (right > maxX) maxX = right; if (bottom > maxY) maxY = bottom; } ASSERT(drawBounds->GetLeft() <= minX); ASSERT(drawBounds->GetTop() <= minY); ASSERT(drawBounds->GetRight() >= maxX); ASSERT(drawBounds->GetBottom() >= maxY); if (minX >= maxX || minY >= maxY) return Ok; AutoArray scanLine(new OUTPUT_SPAN::SCANMASKTYPE[maxX - minX]); if (!scanLine) return OutOfMemory; outputSpan.SetMaskAndLeft(scanLine.Get(), minX); for (INT line = minY; line < maxY; ++line) { GpMemset(scanLine.Get(), 0, (maxX - minX) * sizeof(OUTPUT_SPAN::SCANMASKTYPE)); for (INT i = 0; i < count; ++i) { INT left, top, widthInPixels, right, height, bottom; GetGlyphDimensions(glyphPos[i], &left, &top, &widthInPixels, &right, &height, &bottom); if (widthInPixels == 0 || height == 0 || top > line || line >= bottom || clipRegion->GetRectVisibility(left, top, right, bottom) == DpRegion::Invisible) continue; // is the last check necessary? [mleonov] // now, render glyph into the horizontal merge buffer outputSpan.RenderGlyph( glyphPos[i].GetBits(), &scanLine[left-minX], widthInPixels, line - top ); } // now, clip and render scan line clipRegion->OutputSpan(line, minX, maxX); } clipRegion->EndClipping(); return Ok; } // OutputTextOptimized class DpOutputSolidColorOptimizedSpan : public DpOutputOptimizedSpan { typedef DpOutputOptimizedSpan super; public: DpOutputSolidColorOptimizedSpan(DpScanBuffer * scan, ARGB argb) : super(scan), Argb(argb) {} virtual GpStatus OutputSpan(INT y, INT xMin, INT xMax) { INT width = xMax - xMin; const ARGB * maskPtr = MaskPtr + xMin - Left; ARGB * buf = Scan->NextBuffer(xMin, y, width); GpMemcpy(buf, maskPtr, width * sizeof(ARGB)); return Ok; } void RenderGlyph(const BYTE * glyphBits, SCANMASKTYPE * dst, INT widthInPixels, INT y) { const INT widthInBytes = (widthInPixels + 7) / 8; const BYTE * mask = glyphBits + widthInBytes * y; for (INT pos = 0; pos < widthInPixels; ++dst, ++pos) { if (!*dst && (mask[pos>>3] & (0x80 >> (pos & 7)))) *dst = Argb; } } // RenderGlyph protected: const ARGB Argb; }; // class DpOutputSolidColorOptimizedSpan static GpStatus OutputSolidNormalTextOptimized( DpContext* context, DpDriver *driver, DpBitmap* surface, const GpRect* drawBounds, const GpColor & color, const GpGlyphPos *glyphPos, INT count ) { DpScanBuffer scan(surface->Scan, driver, context, surface); if (!scan.IsValid()) return GenericError; DpOutputSolidColorOptimizedSpan outputSolid(&scan, color.GetPremultipliedValue()); return OutputTextOptimized(context, drawBounds, glyphPos, count, outputSolid); } // OutputSolidNormalTextOptimized class DpOutputClearTypeOptimizedSpan : public DpOutputOptimizedSpan { typedef DpOutputOptimizedSpan super; public: DpOutputClearTypeOptimizedSpan(DpScanBuffer * scan) : super(scan) {} void RenderGlyph(const BYTE * glyphBits, SCANMASKTYPE * dst, INT widthInPixels, INT y) { const BYTE * mask = glyphBits + widthInPixels * y; for (INT pos = 0; pos < widthInPixels; ++pos, ++dst) { const BYTE src = mask[pos]; ASSERT(0 <= *dst && *dst <= CT_LOOKUP - 1); ASSERT(0 <= src && src <= CT_LOOKUP - 1); if (*dst == 0) { *dst = src; } else if (src != 0) { // merge ClearType data ULONG kR = (ULONG)Globals::gaOutTable[*dst].kR + (ULONG)Globals::gaOutTable[src].kR; ULONG kG = (ULONG)Globals::gaOutTable[*dst].kG + (ULONG)Globals::gaOutTable[src].kG; ULONG kB = (ULONG)Globals::gaOutTable[*dst].kB + (ULONG)Globals::gaOutTable[src].kB; if (kR > CT_SAMPLE_F) {kR = CT_SAMPLE_F;} if (kG > CT_SAMPLE_F) {kG = CT_SAMPLE_F;} if (kB > CT_SAMPLE_F) {kB = CT_SAMPLE_F;} *dst = Globals::FilteredCTLut[kB + 7 * kG + 49 * kR]; } } } // RenderGlyph }; // class DpOutputClearTypeOptimizedSpan class DpOutputClearTypeSolidOptimizedSpan : public DpOutputClearTypeOptimizedSpan { typedef DpOutputClearTypeOptimizedSpan super; public: DpOutputClearTypeSolidOptimizedSpan(DpScanBuffer * scan) : super(scan) {} virtual GpStatus OutputSpan(INT y, INT xMin, INT xMax) { const INT width = xMax - xMin; const BYTE * maskPtr = MaskPtr + xMin - Left; Scan->NextBuffer(xMin, y, width); BYTE * buf = reinterpret_cast(Scan->GetCurrentCTBuffer()); for (INT i = 0; i < width; ++i) { ASSERT(0 <= *maskPtr && *maskPtr <= CT_LOOKUP - 1); *buf = *maskPtr; ++buf; ++maskPtr; } return Ok; } }; // class DpOutputClearTypeSolidOptimizedSpan class DpOutputClearTypeBrushOptimizedSpan : public DpOutputClearTypeOptimizedSpan { typedef DpOutputClearTypeOptimizedSpan super; public: DpOutputClearTypeBrushOptimizedSpan(DpScanBuffer * scan, DpOutputSpan * output) : super(scan), Output(output) {} virtual GpStatus OutputSpan(INT y, INT xMin, INT xMax) { GpStatus status = Output->OutputSpan(y, xMin, xMax); if (status != Ok) return status; const INT width = xMax - xMin; const BYTE * maskPtr = MaskPtr + xMin - Left; BYTE * buf = reinterpret_cast(Scan->GetCurrentCTBuffer()); for (INT i = 0; i < width; ++i) { ASSERT(0 <= *maskPtr && *maskPtr <= CT_LOOKUP - 1); *buf = *maskPtr; ++buf; ++maskPtr; } return Ok; } protected: DpOutputSpan * Output; }; // class DpOutputClearTypeBrushOptimizedSpan static GpStatus OutputBrushClearTypeText( DpContext* context, DpDriver *driver, DpBitmap* surface, const GpRect* drawBounds, const DpBrush * brush, const GpGlyphPos *glyphPos, INT count ) { DpScanBuffer scan( surface->Scan, driver, context, surface, FALSE, EpScanTypeCT); if (!scan.IsValid()) { return GenericError; } AutoPointer output(DpOutputSpan::Create(brush, &scan, context)); if (!output) return OutOfMemory; DpOutputClearTypeBrushOptimizedSpan outputCTSpan(&scan, output.Get()); return OutputTextOptimized(context, drawBounds, glyphPos, count, outputCTSpan); } // OutputBrushClearTypeText static GpStatus OutputSolidClearTypeText( DpContext* context, DpDriver *driver, DpBitmap* surface, const GpRect* drawBounds, GpColor color, const GpGlyphPos *glyphPos, INT count ) { DpScanBuffer scan( surface->Scan, driver, context, surface, FALSE, EpScanTypeCTSolidFill, PixelFormat32bppPARGB, PixelFormat32bppPARGB, color.GetValue()); if (!scan.IsValid()) { return GenericError; } DpOutputClearTypeSolidOptimizedSpan outputCTSpan(&scan); return OutputTextOptimized(context, drawBounds, glyphPos, count, outputCTSpan); } // OutputSolidClearTypeText class DpOutputAntiAliasSolidColorOptimizedSpan : public DpOutputOptimizedSpan { typedef DpOutputOptimizedSpan super; public: DpOutputAntiAliasSolidColorOptimizedSpan(DpScanBuffer * scan, const GpColor & color, ULONG gammaValue) : super(scan) { TextGammaTable.CreateTextColorGammaTable(&color, gammaValue, GRAYSCALE_LEVEL); } virtual GpStatus OutputSpan(INT y, INT xMin, INT xMax) { INT width = xMax - xMin; const BYTE * maskPtr = MaskPtr + xMin - Left; ARGB * buf = Scan->NextBuffer(xMin, y, width); for (ARGB * cur = buf; cur < buf + width; ++cur, ++maskPtr) { if (*maskPtr) *cur = TextGammaTable.argb[*maskPtr]; else *cur = 0; } return Ok; } protected: TextColorGammaTable TextGammaTable; BYTE MergeGrayscale(BYTE src, BYTE dst) { return max(src, dst); /* how do we correctly merge overlapping antialiased glyphs? we need to know if subpixels come from the same glyph UINT res = src + dst; if (res >= GsLevel) return GsLevel - 1; return (BYTE)res; */ } // MergeGrayscale }; // class DpOutputAntiAliasSolidColorOptimizedSpan class DpOutputAntiAliasSolid8BPPOptimizedSpan : public DpOutputAntiAliasSolidColorOptimizedSpan { typedef DpOutputAntiAliasSolidColorOptimizedSpan super; public: DpOutputAntiAliasSolid8BPPOptimizedSpan(DpScanBuffer * scan, const GpColor & color, ULONG gammaValue) : super(scan, color, gammaValue) {} void RenderGlyph(const BYTE * glyphBits, SCANMASKTYPE * dst, INT widthInPixels, INT y) { const BYTE * mask = glyphBits + widthInPixels * y; for (INT pos = 0; pos < widthInPixels; ++pos, ++dst) { *dst = MergeGrayscale(*dst, mask[pos]); } } // RenderGlyph }; // class DpOutputAntiAliasSolid8BPPOptimizedSpan class DpOutputAntiAliasSolid4BPPOptimizedSpan : public DpOutputAntiAliasSolidColorOptimizedSpan { typedef DpOutputAntiAliasSolidColorOptimizedSpan super; public: DpOutputAntiAliasSolid4BPPOptimizedSpan(DpScanBuffer * scan, const GpColor & color, ULONG gammaValue) : super(scan, color, gammaValue) {} void RenderGlyph(const BYTE * glyphBits, SCANMASKTYPE * dst, INT widthInPixels, INT y) { const INT widthInBytes = (widthInPixels + 1) / 2; const BYTE * mask = glyphBits + widthInBytes * y; for (INT pos = 0; pos < widthInPixels; ++dst, ++pos) { BYTE value = mask[pos / 2]; value >>= 4 * ((pos + 1) & 1); value &= 0x0F; *dst = MergeGrayscale(*dst, value); } } // RenderGlyph }; // class DpOutputAntiAliasSolid4BPPOptimizedSpan static GpStatus OutputSolidAntiAliasText8BPPOptimized( DpContext* context, DpDriver * driver, DpBitmap* surface, const GpRect* drawBounds, GpColor color, const GpGlyphPos *glyphPos, INT count ) { DpScanBuffer scan(surface->Scan, driver, context, surface); if (!scan.IsValid()) return GenericError; DpOutputAntiAliasSolid8BPPOptimizedSpan outputAASolid(&scan, color, context->TextContrast); return OutputTextOptimized(context, drawBounds, glyphPos, count, outputAASolid); } // OutputSolidAntiAliasText8BPPOptimized static GpStatus OutputSolidAntiAliasText4BPPOptimized( DpContext* context, DpDriver * driver, DpBitmap* surface, const GpRect* drawBounds, GpColor color, const GpGlyphPos *glyphPos, INT count ) { DpScanBuffer scan(surface->Scan, driver, context, surface); if (!scan.IsValid()) return GenericError; DpOutputAntiAliasSolid4BPPOptimizedSpan outputAASolid(&scan, color, context->TextContrast); return OutputTextOptimized(context, drawBounds, glyphPos, count, outputAASolid); } // OutputSolidAntiAliasText4BPPOptimized static GpStatus OutputSolidAntiAliasText8BPP ( DpContext* context, DpDriver * driver, DpBitmap* surface, const GpRect* drawBounds, GpColor color, const GpGlyphPos *glyphPos, INT count ) { INT i; DpScanBuffer scan( surface->Scan, driver, context, surface, FALSE); if (!scan.IsValid()) { return(GenericError); } FPUStateSaver fpuState; DpOutputAntiAliasSolidColorSpan outputAASolid(color, &scan, context->TextContrast, GRAYSCALE_LEVEL); DpClipRegion * clipRegion = NULL; if (context->VisibleClip.GetRectVisibility( drawBounds->X, drawBounds->Y, drawBounds->GetRight(), drawBounds->GetBottom()) != DpRegion::TotallyVisible) { clipRegion = &(context->VisibleClip); clipRegion->InitClipping(&outputAASolid, drawBounds->Y); } ////////////////////////////////////////////////////////////////////////////// for (i = 0; i < count; i++) { INT left = glyphPos[i].GetLeft(); INT top = glyphPos[i].GetTop(); INT widthInPixels = glyphPos[i].GetWidth(); INT right = left + widthInPixels; INT height = glyphPos[i].GetHeight(); INT bottom = top + height; if (widthInPixels == 0 || height == 0) continue; INT widthInBytes = widthInPixels; const BYTE* mask = glyphPos[i].GetBits(); ASSERT(mask != NULL); if (clipRegion != NULL) { // Clipping GpRect clippedRect; DpRegion::Visibility visibility = clipRegion->GetRectVisibility( left, top, right, bottom, &clippedRect ); if (visibility == DpRegion::Invisible) continue; for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { const BYTE* maskPtr = mask + my * widthInBytes; BYTE grayscaleValue = *maskPtr; INT runStart = 0; for (INT mx = 0; mx <= widthInPixels; mx++) { BYTE nextgrayscaleValue; if (mx == widthInPixels) { nextgrayscaleValue = 0; } else { nextgrayscaleValue = *maskPtr; maskPtr++; } if (grayscaleValue != nextgrayscaleValue) { if (grayscaleValue != 0) { // Draw this run INT runLength = mx - runStart; INT from = left + runStart; if (visibility == DpRegion::TotallyVisible) { // Draw the entire run FillMemoryInt32( scan.NextBuffer(from, y, runLength), runLength, outputAASolid.GetAASolidColor((ULONG) grayscaleValue)); } else { // Clip the run INT to = from + runLength; // reference needed outputAASolid.GetAASolidColor((ULONG) grayscaleValue); clipRegion->OutputSpan(y, from, to); } // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } else { // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } } } } } else { ARGB * buf; // No clipping for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { // Get the first byte in the scan line const BYTE* maskPtr = mask + my * widthInBytes; buf = scan.NextBuffer(left, y, widthInPixels); for (INT mx = 0; mx < widthInPixels; mx++) { *buf++ = outputAASolid.GetAASolidColor((ULONG) *maskPtr); maskPtr++; } } } } if (clipRegion != NULL) { clipRegion->EndClipping(); } return(Ok); } static GpStatus OutputSolidAntiAliasText4BPP ( DpContext* context, DpDriver * driver, DpBitmap* surface, const GpRect* drawBounds, GpColor color, const GpGlyphPos *glyphPos, INT count ) { INT i; DpScanBuffer scan( surface->Scan, driver, context, surface, FALSE); if (!scan.IsValid()) { return(GenericError); } FPUStateSaver fpuState; DpOutputAntiAliasSolidColorSpan outputAASolid(color, &scan, context->TextContrast, GRAYSCALE_LEVEL); DpClipRegion * clipRegion = NULL; if (context->VisibleClip.GetRectVisibility( drawBounds->X, drawBounds->Y, drawBounds->GetRight(), drawBounds->GetBottom()) != DpRegion::TotallyVisible) { clipRegion = &(context->VisibleClip); clipRegion->InitClipping(&outputAASolid, drawBounds->Y); } ////////////////////////////////////////////////////////////////////////////// for (i = 0; i < count; i++) { INT left = glyphPos[i].GetLeft(); INT top = glyphPos[i].GetTop(); INT widthInPixels = glyphPos[i].GetWidth(); INT right = left + widthInPixels; INT height = glyphPos[i].GetHeight(); INT bottom = top + height; if (widthInPixels == 0 || height == 0) continue; INT widthInBytes = ((widthInPixels + 1) / 2); const BYTE* mask = glyphPos[i].GetBits(); ASSERT(mask != NULL); if (clipRegion != NULL) { // Clipping GpRect clippedRect; DpRegion::Visibility visibility = clipRegion->GetRectVisibility( left, top, right, bottom, &clippedRect ); if (visibility == DpRegion::Invisible) continue; for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { const BYTE* maskPtr = mask + my * widthInBytes; BYTE grayscaleValue = *maskPtr >> 4; INT runStart = 0; for (INT mx = 0; mx <= widthInPixels; mx++) { BYTE nextgrayscaleValue; if (mx == widthInPixels) { nextgrayscaleValue = 0; } else { if (mx % 2) { nextgrayscaleValue = *maskPtr & 0x0F; maskPtr++; } else { nextgrayscaleValue = *maskPtr >> 4; } } if (grayscaleValue != nextgrayscaleValue) { if (grayscaleValue != 0) { // Draw this run INT runLength = mx - runStart; INT from = left + runStart; if (visibility == DpRegion::TotallyVisible) { // Draw the entire run FillMemoryInt32( scan.NextBuffer(from, y, runLength), runLength, outputAASolid.GetAASolidColor((ULONG) grayscaleValue)); } else { // Clip the run INT to = from + runLength; // reference needed outputAASolid.GetAASolidColor((ULONG) grayscaleValue); clipRegion->OutputSpan(y, from, to); } // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } else { // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } } } } } else { ARGB * buf; // No clipping for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { // Get the first byte in the scan line const BYTE* maskPtr = mask + my * widthInBytes; buf = scan.NextBuffer(left, y, widthInPixels); for (INT mx = 0; mx < widthInPixels; mx++) { if (!(mx % 2)) *buf++ = outputAASolid.GetAASolidColor((ULONG) (*maskPtr >> 4)); else { *buf++ = outputAASolid.GetAASolidColor((ULONG) (*maskPtr & 0x0F)); maskPtr++; } } } } } if (clipRegion != NULL) { clipRegion->EndClipping(); } return(Ok); } /**************************************************************************\ * * Function Description: * * Engine version of routine to draw solid text. * * Arguments: * * [IN] - DDI parameters. * * Return Value: * * TRUE if successful. * * History: * * 4/4/1999 cameronb * Created it. * \**************************************************************************/ GpStatus DpDriver::SolidText( DpContext* context, DpBitmap* surface, const GpRect* drawBounds, GpColor color, const GpGlyphPos *glyphPos, INT count, TextRenderingHint textMode, BOOL rightToLeft ) { ASSERT (textMode != TextRenderingHintSystemDefault); switch(textMode) { case TextRenderingHintSingleBitPerPixelGridFit: case TextRenderingHintSingleBitPerPixel: if (context->CompositingMode == CompositingModeSourceCopy) return OutputSolidNormalText(context, this, surface, drawBounds, color, glyphPos, count); // we are allowed to output transparent pixels // version with minimized number of scan records return OutputSolidNormalTextOptimized(context, this, surface, drawBounds, color, glyphPos, count); case TextRenderingHintAntiAlias: if (context->CompositingMode == CompositingModeSourceCopy) return OutputSolidAntiAliasText8BPP(context, this, surface, drawBounds, color, glyphPos, count); return OutputSolidAntiAliasText8BPPOptimized(context, this, surface, drawBounds, color, glyphPos, count); case TextRenderingHintAntiAliasGridFit: if (context->CompositingMode == CompositingModeSourceCopy) return OutputSolidAntiAliasText4BPP(context, this, surface, drawBounds, color, glyphPos, count); return OutputSolidAntiAliasText4BPPOptimized(context, this, surface, drawBounds, color, glyphPos, count); case TextRenderingHintClearTypeGridFit: return OutputSolidClearTypeText(context, this, surface, drawBounds, color, glyphPos, count); default: break; } return Ok; } /**************************************************************************\ * * Function Description: * * Engine version of routine to draw text based on a brush. * * Arguments: * * [IN] - DDI parameters. * * Return Value: * * TRUE if successful. * * History: * 5-1-2000 YungT rewrite it. * 2/7/2000 YungT modify it. * 4/14/1999 cameronb * Created it. * \**************************************************************************/ static GpStatus OutputBrushNormalText( DpContext* context, DpDriver * driver, DpBitmap* surface, const GpRect* drawBounds, const DpBrush* brush, const GpGlyphPos* glyphPos, INT count ) { DpScanBuffer scan( surface->Scan, driver, context, surface, FALSE); // (color64.IsOpaque() && // (!context->AntiAliasMode))); // !!! If you fix this, you'll get a perf improvement for // text that has no transparency. if (!scan.IsValid()) { return(GenericError); } FPUStateSaver fpuState; DpOutputSpan* output = DpOutputSpan::Create(brush, &scan, context); if (output != NULL) { INT i; INT my; GlyphScanBuf glyphScanBuf[256], *pglyphScanBuf; DpClipRegion* clipRegion = NULL; INT topY = drawBounds->Y; INT bottomY = drawBounds->GetBottom(); // Allocate enough space for glyph scan buffer if (count < 256) { pglyphScanBuf = &glyphScanBuf[0]; } else { pglyphScanBuf = (GlyphScanBuf *) GpMalloc(count * sizeof(GlyphScanBuf)); if (!pglyphScanBuf) return (OutOfMemory); } if (context->VisibleClip.GetRectVisibility(drawBounds->X, topY, drawBounds->GetRight(), bottomY) != DpRegion::TotallyVisible) { clipRegion = &(context->VisibleClip); clipRegion->InitClipping(output, drawBounds->Y); } // Scan evrey Glyph and get the Visibility // Also we cache some data we will need to for later computation for (i = 0; i < count; i++) { GpRect clippedRect; pglyphScanBuf[i].left = glyphPos[i].GetLeft(); pglyphScanBuf[i].top = glyphPos[i].GetTop(); pglyphScanBuf[i].widthInBytes = (glyphPos[i].GetWidth() + 7) / 8; pglyphScanBuf[i].bottom = pglyphScanBuf[i].top + glyphPos[i].GetHeight(); // Set the glyph as invisible if it is empty. if (glyphPos[i].GetWidth() == 0 || glyphPos[i].GetHeight() == 0) pglyphScanBuf[i].visibility = DpRegion::Invisible; else if (clipRegion != NULL) pglyphScanBuf[i].visibility = clipRegion->GetRectVisibility(pglyphScanBuf[i].left, pglyphScanBuf[i].top, pglyphScanBuf[i].left + glyphPos[i].GetWidth(), pglyphScanBuf[i].bottom, &clippedRect); else pglyphScanBuf[i].visibility = DpRegion::TotallyVisible; } // Start to scan from top of bounding box to bottom for (int y = topY; y < bottomY; y++) { for (i = 0; i < count; i++) { const BYTE* maskPtr; INT runLength, runStart; INT from, to; BYTE thisBit; BYTE nextPixel; BYTE pixel; // Invisible glyph if (pglyphScanBuf[i].visibility == DpRegion::Invisible) { continue; } // check the scan line with y.top and y.bottom if (y < pglyphScanBuf[i].top || y >= pglyphScanBuf[i].bottom) continue; if (pglyphScanBuf[i].visibility != DpRegion::TotallyVisible) { // Get the relateive y-scan line for each glyph my = y - pglyphScanBuf[i].top; // Get the address of glyph bits maskPtr = glyphPos[i].GetBits(); ASSERT(maskPtr != NULL); maskPtr += my * pglyphScanBuf[i].widthInBytes; thisBit = 0x80; nextPixel = (*(maskPtr) & thisBit) ? 255 : 0; runStart = 0; for (INT mx = 0; mx < glyphPos[i].GetWidth(); mx++) { pixel = nextPixel; thisBit = (thisBit == 0x01) ? 0x80 : thisBit >> 1; nextPixel = (mx == glyphPos[i].GetWidth() - 1) ? 0 : ( (*(maskPtr + (mx + 1) / 8) & thisBit) ? 255 : 0); if (pixel != nextPixel) { if (pixel) { // Draw this run runLength = mx - runStart + 1; // Clip the run from = pglyphScanBuf[i].left + runStart; to = from + runLength; clipRegion->OutputSpan(y, from, to); } if (nextPixel) { // Start a new run runStart = mx + 1; } } } } else { my = y - pglyphScanBuf[i].top; maskPtr = glyphPos[i].GetBits(); ASSERT(maskPtr != NULL); maskPtr += my * pglyphScanBuf[i].widthInBytes; thisBit = 0x80; runLength = 0; for (INT mx = 0; mx < glyphPos[i].GetWidth(); mx++) { BOOL pixelOn = *(maskPtr + mx / 8) & thisBit; if (pixelOn) { if (runLength == 0) { // Start a new run runStart = mx; } runLength++; } if (runLength > 0 && !pixelOn || runLength > 0 && mx == glyphPos[i].GetWidth() - 1) { // Finish this run and draw it from = pglyphScanBuf[i].left + runStart; to = from + runLength; output->OutputSpan(y, from, to); runLength = 0; }; thisBit = (thisBit == 0x01) ? 0x80 : thisBit >> 1; } } } } // next scan line if (clipRegion != NULL) { clipRegion->EndClipping(); } delete output; if (pglyphScanBuf != &glyphScanBuf[0]) GpFree(pglyphScanBuf); } return(Ok); } /**************************************************************************\ * * Function Description: * * Antialias version of routine to draw text based on a brush. * * Arguments: * * [IN] - same as DDI parameters. * * Return Value: * * Ok if successful. * * History: * * 2/20/00 YungT * Created it. * \**************************************************************************/ static GpStatus OutputBrushAntiAliasText8BPP( DpContext* context, DpDriver * driver, DpBitmap* surface, const GpRect* drawBounds, const DpBrush* brush, const GpGlyphPos* glyphPos, INT count ) { DpScanBuffer scan( surface->Scan, driver, context, surface, FALSE); // (color64.IsOpaque() && // (!context->AntiAliasMode))); // !!! If you fix this, you'll get a perf improvement for // text that has no transparency. if (!scan.IsValid()) { return(GenericError); } DpOutputSpan* output = DpOutputSpan::Create(brush, &scan, context); DpOutputAntiAliasBrushOutputSpan aaBrushSpan; if (output != NULL) { INT i; TextColorGammaTable textContrastTable; textContrastTable.CreateTextColorGammaTable((GpColor *) NULL, context->TextContrast, GRAYSCALE_LEVEL); DpClipRegion* clipRegion = NULL; if (context->VisibleClip.GetRectVisibility( drawBounds->X, drawBounds->Y, drawBounds->GetRight(), drawBounds->GetBottom()) != DpRegion::TotallyVisible) { aaBrushSpan.Init(output); clipRegion = &(context->VisibleClip); clipRegion->InitClipping(&aaBrushSpan, drawBounds->Y); } for (i = 0; i < count; i++) { INT left = glyphPos[i].GetLeft(); INT top = glyphPos[i].GetTop(); INT widthInPixels = glyphPos[i].GetWidth(); INT right = left + widthInPixels; INT height = glyphPos[i].GetHeight(); INT bottom = top + height; if (widthInPixels == 0 || height == 0) continue; INT widthInBytes = widthInPixels; const BYTE* mask = glyphPos[i].GetBits(); ASSERT(mask != NULL); if (clipRegion != NULL) { GpRect clippedRect; DpRegion::Visibility visibility = clipRegion->GetRectVisibility(left, top, right, bottom, &clippedRect); if (visibility == DpRegion::Invisible) continue; for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { const BYTE* maskPtr = mask + my * widthInBytes; BYTE grayscaleValue = *maskPtr; INT runStart = 0; for (INT mx = 0; mx <= widthInPixels; mx++) { BYTE nextgrayscaleValue; if (mx == widthInPixels) { nextgrayscaleValue = 0; } else { nextgrayscaleValue = *maskPtr; maskPtr++; } if (grayscaleValue != nextgrayscaleValue) { if (grayscaleValue != 0) { // Draw this run INT runLength = mx - runStart; INT from = left + runStart; if (visibility == DpRegion::TotallyVisible) { // Clip the run INT to = from + runLength; // reference needed output->OutputSpan(y, from, to); ARGB *buffer; buffer = output->GetScanBuffer()->GetCurrentBuffer(); for (INT j = from; j < to; j++) { *buffer++ = GpColor::MultiplyCoverage(*buffer, textContrastTable.GetGammaTableIndexValue(grayscaleValue, GRAYSCALE_LEVEL)); } } else { // Clip the run INT to = from + runLength; // reference needed aaBrushSpan.SetCoverage(textContrastTable.GetGammaTableIndexValue(grayscaleValue, GRAYSCALE_LEVEL)); clipRegion->OutputSpan(y, from, to); } // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } else { // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } } } } } else { for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { const BYTE* maskPtr = mask + my * widthInBytes; BYTE grayscaleValue = *maskPtr; INT runStart = 0; for (INT mx = 0; mx <= widthInPixels; mx++) { BYTE nextgrayscaleValue; if (mx == widthInPixels) { nextgrayscaleValue = 0; } else { nextgrayscaleValue = *maskPtr; maskPtr++; } if (grayscaleValue != nextgrayscaleValue) { if (grayscaleValue != 0) { // Draw this run INT runLength = mx - runStart; INT from = left + runStart; // Clip the run INT to = from + runLength; // reference needed output->OutputSpan(y, from, to); ARGB *buffer; buffer = output->GetScanBuffer()->GetCurrentBuffer(); for (INT j = from; j < to; j++) { *buffer++ = GpColor::MultiplyCoverage(*buffer, textContrastTable.GetGammaTableIndexValue(grayscaleValue, GRAYSCALE_LEVEL)); } // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } else { // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } } } } } } if (clipRegion != NULL) { clipRegion->EndClipping(); } delete output; } return(Ok); } /**************************************************************************\ * * Function Description: * * Antialias version of routine to draw text based on a brush. * * Arguments: * * [IN] - same as DDI parameters. * * Return Value: * * Ok if successful. * * History: * * 1/28/00 YungT * Created it. * \**************************************************************************/ static GpStatus OutputBrushAntiAliasText4BPP( DpContext* context, DpDriver * driver, DpBitmap* surface, const GpRect* drawBounds, const DpBrush* brush, const GpGlyphPos* glyphPos, INT count ) { DpScanBuffer scan( surface->Scan, driver, context, surface, FALSE); // (color64.IsOpaque() && // (!context->AntiAliasMode))); // !!! If you fix this, you'll get a perf improvement for // text that has no transparency. if (!scan.IsValid()) { return(GenericError); } DpOutputSpan* output = DpOutputSpan::Create(brush, &scan, context); DpOutputAntiAliasBrushOutputSpan aaBrushSpan; if (output != NULL) { INT i; TextColorGammaTable textContrastTable; textContrastTable.CreateTextColorGammaTable((GpColor *) NULL, context->TextContrast, GRAYSCALE_LEVEL); DpClipRegion* clipRegion = NULL; if (context->VisibleClip.GetRectVisibility( drawBounds->X, drawBounds->Y, drawBounds->GetRight(), drawBounds->GetBottom()) != DpRegion::TotallyVisible) { aaBrushSpan.Init(output); clipRegion = &(context->VisibleClip); clipRegion->InitClipping(&aaBrushSpan, drawBounds->Y); } for (i = 0; i < count; i++) { INT left = glyphPos[i].GetLeft(); INT top = glyphPos[i].GetTop(); INT widthInPixels = glyphPos[i].GetWidth(); INT right = left + widthInPixels; INT height = glyphPos[i].GetHeight(); INT bottom = top + height; if (widthInPixels == 0 || height == 0) continue; INT widthInBytes = ((widthInPixels + 1) / 2); const BYTE* mask = glyphPos[i].GetBits(); ASSERT(mask != NULL); if (clipRegion != NULL) { GpRect clippedRect; DpRegion::Visibility visibility = clipRegion->GetRectVisibility(left, top, right, bottom, &clippedRect); if (visibility == DpRegion::Invisible) continue; for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { const BYTE* maskPtr = mask + my * widthInBytes; BYTE grayscaleValue = *maskPtr >> 4; INT runStart = 0; for (INT mx = 0; mx <= widthInPixels; mx++) { BYTE nextgrayscaleValue; if (mx == widthInPixels) { nextgrayscaleValue = 0; } else { if (mx % 2) { nextgrayscaleValue = *maskPtr & 0x0F; maskPtr++; } else { nextgrayscaleValue = *maskPtr >> 4; } } if (grayscaleValue != nextgrayscaleValue) { if (grayscaleValue != 0) { // Draw this run INT runLength = mx - runStart; INT from = left + runStart; if (visibility == DpRegion::TotallyVisible) { // Clip the run INT to = from + runLength; // reference needed output->OutputSpan(y, from, to); ARGB *buffer; buffer = output->GetScanBuffer()->GetCurrentBuffer(); for (INT j = from; j < to; j++) { *buffer++ = GpColor::MultiplyCoverage(*buffer, textContrastTable.GetGammaTableIndexValue(grayscaleValue, GRAYSCALE_LEVEL)); } } else { // Clip the run INT to = from + runLength; // reference needed aaBrushSpan.SetCoverage(textContrastTable.GetGammaTableIndexValue(grayscaleValue, GRAYSCALE_LEVEL)); clipRegion->OutputSpan(y, from, to); } // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } else { // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } } } } } else { for (INT y = top, my = 0; y < bottom && my < height; y++, my++) { const BYTE* maskPtr = mask + my * widthInBytes; BYTE grayscaleValue = *maskPtr >> 4; INT runStart = 0; for (INT mx = 0; mx <= widthInPixels; mx++) { BYTE nextgrayscaleValue; if (mx == widthInPixels) { nextgrayscaleValue = 0; } else { if (mx % 2) { nextgrayscaleValue = *maskPtr & 0x0F; maskPtr++; } else { nextgrayscaleValue = *maskPtr >> 4; } } if (grayscaleValue != nextgrayscaleValue) { if (grayscaleValue != 0) { // Draw this run INT runLength = mx - runStart; INT from = left + runStart; // Clip the run INT to = from + runLength; // reference needed output->OutputSpan(y, from, to); ARGB *buffer; buffer = output->GetScanBuffer()->GetCurrentBuffer(); for (INT j = from; j < to; j++) { *buffer++ = GpColor::MultiplyCoverage(*buffer, textContrastTable.GetGammaTableIndexValue(grayscaleValue, GRAYSCALE_LEVEL)); } // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } else { // Start a new run runStart = mx; grayscaleValue = nextgrayscaleValue; } } } } } } if (clipRegion != NULL) { clipRegion->EndClipping(); } delete output; } return(Ok); } /**************************************************************************\ * * Function Description: * * Engine version of routine to draw solid text. * * Arguments: * * [IN] - DDI parameters. * * Return Value: * * TRUE if successful. * * History: * 1/24/2000 YungT modified it * 4/4/1999 cameronb * Created it. * \**************************************************************************/ GpStatus DpDriver::BrushText( DpContext* context, DpBitmap* surface, const GpRect* drawBounds, const DpBrush* brush, const GpGlyphPos *glyphPos, INT count, TextRenderingHint textMode ) { ASSERT (textMode != TextRenderingHintSystemDefault); switch(textMode) { case TextRenderingHintSingleBitPerPixelGridFit: case TextRenderingHintSingleBitPerPixel: return OutputBrushNormalText(context, this, surface, drawBounds, brush, glyphPos, count); case TextRenderingHintAntiAlias: return OutputBrushAntiAliasText8BPP(context, this, surface, drawBounds, brush, glyphPos, count); case TextRenderingHintAntiAliasGridFit: return OutputBrushAntiAliasText4BPP(context, this, surface, drawBounds, brush, glyphPos, count); // version 2 : // case TextRenderingHintClearType: case TextRenderingHintClearTypeGridFit: return OutputBrushClearTypeText(context, this, surface, drawBounds, brush, glyphPos, count); default: break; } return Ok; } ///// GdiText - Draw glyph on downlevel DC // // !!! Optimize to use lpdx // NOTE: // Here we explicitly call ExtTextOutW because we calling it with // ETO_GLYPH_INDEX. It will be fine even if we are running on Windows 9x // because internally it calls ExtTextOutA. // We didn't use Global::ExtTextOutFunction because we don't want to call // ExtTextOutA with ETO_GLYPH_INDEX while recording to a Meta file under // Windows 9x. And in that case Windows 9x fails to record it. // The code in Windows 9x is recording glyph indexes only if we are spooling // only otherwise it will not record. GpStatus DpDriver::GdiText( HDC hdc, INT angle, // Tenths of a degree const UINT16 *glyphs, const PointF *glyphOrigins, INT glyphCount, BOOL rightToLeft, UINT16 blankGlyph ) { UINT16 lastTwoGlyphs[2]; if ( glyphCount > 1 && angle == 0) { // Try to optimise for horizintal text. (We don't try for vertical // text since GDI and GDI+ rotation semantics are not compatible) INT i=1; while ( i < glyphCount && abs(GpRound(glyphOrigins[i].Y - glyphOrigins[i-1].Y)) == 0) { i++; } if (i == glyphCount) { // All text is at the same dy AutoArray advances(new INT[glyphCount]); if (!advances) { return OutOfMemory; } if (rightToLeft && !Globals::IsNt && glyphCount>1) { // Windows 9x doesn't work with the negative advanced widths AutoArray bidiGlyphs(new UINT16[glyphCount]); if (!bidiGlyphs) { return OutOfMemory; } for (i=0; i 0 && (glyphCount & 1)) { if (!ExtTextOutW( hdc, GpRound(glyphOrigins[glyphCount - 1].X), GpRound(glyphOrigins[glyphCount - 1].Y), ETO_GLYPH_INDEX, NULL, (PWSTR)bidiGlyphs.Get(), glyphCount-1, advances.Get() )) { return Win32Error; } lastTwoGlyphs[0] = bidiGlyphs[glyphCount-1]; lastTwoGlyphs[1] = blankGlyph; if (!ExtTextOutW( hdc, GpRound(glyphOrigins[0].X), GpRound(glyphOrigins[0].Y), ETO_GLYPH_INDEX, NULL, (PWSTR)lastTwoGlyphs, 2, NULL )) { return Win32Error; } } else { if (!ExtTextOutW( hdc, GpRound(glyphOrigins[glyphCount - 1].X), GpRound(glyphOrigins[glyphCount - 1].Y), ETO_GLYPH_INDEX, NULL, (PWSTR)bidiGlyphs.Get(), glyphCount, advances.Get() )) { return Win32Error; } } return Ok; } INT offset = GpRound(glyphOrigins[0].X); for (i=0; i 0 && (glyphCount & 1)) { if (!ExtTextOutW( hdc, GpRound(glyphOrigins[0].X), GpRound(glyphOrigins[0].Y), ETO_GLYPH_INDEX, NULL, (PWSTR)glyphs, glyphCount-1, advances.Get() )) { return Win32Error; } lastTwoGlyphs[0] = glyphs[glyphCount-1]; lastTwoGlyphs[1] = blankGlyph; if (!ExtTextOutW( hdc, GpRound(glyphOrigins[glyphCount-1].X), GpRound(glyphOrigins[glyphCount-1].Y), ETO_GLYPH_INDEX, NULL, (PWSTR)lastTwoGlyphs, 2, NULL )) { return Win32Error; } } else { if (!ExtTextOutW( hdc, GpRound(glyphOrigins[0].X), GpRound(glyphOrigins[0].Y), ETO_GLYPH_INDEX, NULL, (PWSTR)glyphs, glyphCount, advances.Get() )) { return Win32Error; } } return Ok; } } if (blankGlyph > 0) { lastTwoGlyphs[1] = blankGlyph; for (INT i=0; i