You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2263 lines
68 KiB
2263 lines
68 KiB
/**************************************************************************\
|
|
*
|
|
* 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 <typename MASKTYPE>
|
|
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 <class OUTPUT_SPAN>
|
|
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<OUTPUT_SPAN::SCANMASKTYPE> 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<ARGB>
|
|
{
|
|
typedef DpOutputOptimizedSpan<ARGB> 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<BYTE>
|
|
{
|
|
typedef DpOutputOptimizedSpan<BYTE> 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<BYTE *>(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<BYTE *>(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<DpOutputSpan> 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<BYTE>
|
|
{
|
|
typedef DpOutputOptimizedSpan<BYTE> 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<INT> 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<UINT16> bidiGlyphs(new UINT16[glyphCount]);
|
|
if (!bidiGlyphs)
|
|
{
|
|
return OutOfMemory;
|
|
}
|
|
|
|
for (i=0; i<glyphCount-1; i++)
|
|
{
|
|
bidiGlyphs[i] = glyphs[glyphCount - i - 1];
|
|
advances[i] = GpRound(glyphOrigins[glyphCount- i - 2].X - glyphOrigins[glyphCount- i - 1].X);
|
|
}
|
|
|
|
bidiGlyphs[glyphCount-1] = glyphs[0];
|
|
advances[glyphCount-1] = 0;
|
|
|
|
if (blankGlyph > 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<glyphCount-1; i++)
|
|
{
|
|
advances[i] = GpRound(glyphOrigins[i+1].X) - offset;
|
|
offset += advances[i];
|
|
}
|
|
advances[glyphCount-1] = 0;
|
|
|
|
|
|
if (blankGlyph > 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<glyphCount; i++)
|
|
{
|
|
if (glyphs[i] != 0xffff) // 0xffff is never displayed
|
|
{
|
|
lastTwoGlyphs[0] = glyphs[i];
|
|
|
|
if (!ExtTextOutW(
|
|
hdc,
|
|
GpRound(glyphOrigins[i].X),
|
|
GpRound(glyphOrigins[i].Y),
|
|
ETO_GLYPH_INDEX,
|
|
NULL,
|
|
(PWSTR)lastTwoGlyphs,
|
|
2,
|
|
NULL
|
|
)) {
|
|
return Win32Error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Failed to optimise ...
|
|
for (INT i=0; i<glyphCount; i++)
|
|
{
|
|
if (glyphs[i] != 0xffff) // 0xffff is never displayed
|
|
{
|
|
if (!ExtTextOutW(
|
|
hdc,
|
|
GpRound(glyphOrigins[i].X),
|
|
GpRound(glyphOrigins[i].Y),
|
|
ETO_GLYPH_INDEX,
|
|
NULL,
|
|
(PWSTR)glyphs+i,
|
|
1,
|
|
NULL
|
|
)) {
|
|
return Win32Error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
|