/**************************************************************************\ * * Copyright (c) 1998 Microsoft Corporation * * Abstract: * * Implementation of GpPen class * * Revision History: * * 12/08/1998 andrewgo * Initial placeholders. * * 01/06/1999 ikkof * Added the implementation of GpGeometricPen. \**************************************************************************/ #include "precomp.hpp" //------------------------------------------------------------- // GetMajorAndMinorAxis() is defined in PathWidener.cpp. //------------------------------------------------------------- extern GpStatus GetMajorAndMinorAxis( REAL* majorR, REAL* minorR, const GpMatrix* matrix ); /**************************************************************************\ * * Function Description: * * This converts the given width with the given physical unit to * the device unit. You cannot use this function when * unit is WorldUnit. * * Arguments: * * [IN] width - the width in the given unit. * [IN] unit - the unit of the width (must not be WorldUnit). * [IN] dpi - dots per inch of the device. * * Return Value: * * The device width. * * 04/15/1999 ikkof * Created it. * \**************************************************************************/ VOID GpPen::Set(const GpColor& color, REAL penWidth, GpUnit unit) { // UnitDisplay is device-dependent and cannot be used for a pen size ASSERT(unit != UnitDisplay); if(DevicePen.CustomStartCap) delete DevicePen.CustomStartCap; if(DevicePen.CustomEndCap) delete DevicePen.CustomEndCap; if(DevicePen.DashArray) GpFree(DevicePen.DashArray); if(DevicePen.CompoundArray) GpFree(DevicePen.CompoundArray); InitDefaultState(penWidth, unit); if(Brush) { SetColor((GpColor *) &color); } else { Brush = new GpSolidFill(color); if(Brush) { DevicePen.Brush = Brush->GetDeviceBrush(); } else { SetValid(FALSE); } } UpdateUid(); } GpPen::GpPen(const GpColor& color, REAL penWidth, GpUnit unit) { // UnitDisplay is device-dependent and cannot be used for a pen size ASSERT(unit != UnitDisplay); InitDefaultState(penWidth, unit); Brush = new GpSolidFill(color); if(Brush) { DevicePen.Brush = Brush->GetDeviceBrush(); } else { SetValid(FALSE); } } GpPen::GpPen(const GpBrush* brush, REAL penWidth, GpUnit unit) { // UnitDisplay is device-dependent and cannot be used for a pen size ASSERT(unit != UnitDisplay); InitDefaultState(penWidth, unit); Brush = brush->Clone(); if(Brush) { DevicePen.Brush = Brush->GetDeviceBrush(); } else { SetValid(FALSE); } } GpPen::GpPen(GpLineTexture* lineTexture, REAL penWidth, GpUnit unit) { // UnitDisplay is device-dependent and cannot be used for a pen size ASSERT(unit != UnitDisplay); // !!! Needs to be implemented. // !!! Remember to change GdipCreatePen3 - it currently just returns // NotImplemented. RIP(("GpPen with line texture not implemented")); SetValid(FALSE); } VOID GpPen::InitDefaultState(REAL penWidth, GpUnit unit) { // UnitDisplay is device-dependent and cannot be used for a pen size ASSERT(unit != UnitDisplay); // !! Look at DeviceBrush.Type DevicePen.Type = PenTypeSolidColor; DevicePen.Width = penWidth; DevicePen.Unit = unit; DevicePen.StartCap = LineCapFlat; DevicePen.EndCap = LineCapFlat; DevicePen.Join = LineJoinMiter; DevicePen.MiterLimit = 10; // PS's default miter limit. DevicePen.PenAlignment = PenAlignmentCenter; DevicePen.DashStyle = DashStyleSolid; DevicePen.DashCap = LineCapFlat; DevicePen.DashCount = 0; DevicePen.DashOffset = 0; DevicePen.DashArray = NULL; DevicePen.CompoundCount = 0; DevicePen.CompoundArray = NULL; DevicePen.CustomStartCap = NULL; DevicePen.CustomEndCap = NULL; DevicePen.Xform.Reset(); SetValid(TRUE); UpdateUid(); } GpPen::GpPen(const GpPen* pen) { GpStatus status = Ok; // Initialize pointer members so that we don't delete garbage Brush = NULL; DevicePen.Brush = NULL; DevicePen.DashArray = NULL; DevicePen.CompoundArray = NULL; DevicePen.CustomStartCap = NULL; DevicePen.CustomEndCap = NULL; if(pen && pen->IsValid()) { // Copy the base state. DevicePen = pen->DevicePen; // Don't copy pointer references to other objects. Brush = NULL; DevicePen.Brush = NULL; DevicePen.DashArray = NULL; DevicePen.CompoundArray = NULL; DevicePen.CustomStartCap = NULL; DevicePen.CustomEndCap = NULL; // Explicitly clone the pointer references to other objects. if(pen->Brush) { Brush = pen->Brush->Clone(); DevicePen.Brush = Brush->GetDeviceBrush(); } else { status = GenericError; } if( status == Ok ) { if( (pen->DevicePen.DashArray) && (DevicePen.DashCount > 0) ) { DevicePen.DashArray = (REAL*) GpMalloc(DevicePen.DashCount*sizeof(REAL)); if(DevicePen.DashArray) { GpMemcpy(DevicePen.DashArray, pen->DevicePen.DashArray, DevicePen.DashCount*sizeof(REAL)); } else { status = OutOfMemory; } } else { // If there is no dash array data, this must be a solid line. ASSERT(DevicePen.DashStyle == DashStyleSolid); DevicePen.DashCount = 0; DevicePen.DashArray = NULL; } } // Set the compound array if necessary. if( status == Ok ) { if( (pen->DevicePen.CompoundArray) && (DevicePen.CompoundCount > 0) ) { DevicePen.CompoundArray = (REAL*) GpMalloc(DevicePen.CompoundCount*sizeof(REAL)); if(DevicePen.CompoundArray) { GpMemcpy(DevicePen.CompoundArray, pen->DevicePen.CompoundArray, DevicePen.CompoundCount*sizeof(REAL)); } else { status = OutOfMemory; } } else { DevicePen.CompoundCount = 0; DevicePen.CompoundArray = NULL; } } // Copy the start custom cap. if( status == Ok ) { if( DevicePen.StartCap == LineCapCustom ) { // This could happen with our metafile recorder, // because saving Custom Line Caps was not implemented. if (pen->DevicePen.CustomStartCap == NULL) { WARNING1("CustomStartCap type with NULL pointer"); DevicePen.StartCap = LineCapFlat; } else { GpCustomLineCap* clonedCap = static_cast (pen->DevicePen.CustomStartCap)->Clone(); if(clonedCap) { DevicePen.CustomStartCap = clonedCap; } else { status = OutOfMemory; } } } } // Copy the end custom cap. if( status == Ok ) { if( DevicePen.EndCap == LineCapCustom ) { // This could happen with our metafile recorder, // because saving Custom Line Caps was not implemented. if (pen->DevicePen.CustomEndCap == NULL) { WARNING1("CustomEndCap type with NULL pointer"); DevicePen.EndCap = LineCapFlat; } else { GpCustomLineCap* clonedCap = static_cast (pen->DevicePen.CustomEndCap)->Clone(); if(clonedCap) { DevicePen.CustomEndCap = clonedCap; } else { status = OutOfMemory; } } } } } else { // Can't make a valid pen from an invalid input pen. status = GenericError; } if(status == Ok) { SetValid(TRUE); } else { // Failed cloning the pen. // Clean up possible memory allocation so we don't leak even under // low memory conditions. Note we rely on GpFree and delete handling // NULL pointers here. delete Brush; Brush = NULL; // InitializeDefaultState() does not set DevicePen.Brush = NULL; // these fields - clear them explicitly. GpFree(DevicePen.DashArray); GpFree(DevicePen.CompoundArray); delete DevicePen.CustomStartCap; delete DevicePen.CustomEndCap; // Clean the pen. InitDefaultState(1.0f, UnitWorld); // This is not a valid object. SetValid(FALSE); } } // Clone() return NULL if the cloning fails. GpPen* GpPen::Clone() { GpPen* clonedPen = new GpPen(this); if(clonedPen && clonedPen->IsValid()) return clonedPen; else { if(clonedPen) delete clonedPen; return NULL; } } GpStatus GpPen::GetMaximumWidth( REAL* width, const GpMatrix* matrix) const { if(DevicePen.Unit != UnitWorld) return InvalidParameter; GpMatrix trans; if(matrix) trans = *matrix; if(!DevicePen.Xform.IsTranslate()) trans.Prepend(DevicePen.Xform); REAL majorR, minorR; ::GetMajorAndMinorAxis(&majorR, &minorR, &trans); majorR *= DevicePen.Width; minorR *= DevicePen.Width; if(minorR < 1.42f) // This is a litte bit larger than sqrt(2). { minorR = 1.42f; majorR = 1.42f; } *width = majorR; return Ok; } /**************************************************************************\ * * Function Description: * * This function takes a join angle and computes the length of the miter * based on this angle and a given miter length limit. * This can be scaled by the pen width to give the length of an arbitrary * pen miter. * * In this picture, 2a is the angle of the join. The pen width is w and the * desired output is the length of the miter join (l). * * Note that the line labled w is perpendecular to the inside and outside * widended lines. Then the formula is derived as follows: * * sin(a) = w/l [opposite over hypotenuse on right angled triangle] * <=> l = w/sin(a) * * * /|\ * /a|a\ * / | \ * / | \ * / |l \ * / | \ <-- right angle * /--__ | __--\ * / w --|-- w \ * / / \ \ * / / \ \ * outside inside outside * * NOTE: * * This routine returns the miter length (l) for a pen width w==1.0f. * The caller is responsible for scaling length by the pen width. * * If the length of 1/sin(a) is greater than the miterLimit, the miterLimit * is returned. (including infinite length joins). * * Arguments: * * [IN] angle - join angle in radians * [IN] miterLimit - maximum miter length (not scaled by pen width). * * Return Value: * * Pen width independent miter length. * * 10/02/2000 asecchia * Created it. * \**************************************************************************/ REAL GpPen::ComputeMiterLength( REAL angle, REAL miterLimit ) { // use the simple miter join formula // length = (penwidth)/sin(angle/2) // because we're pen independent, use 1.0 for pen width and rely // on the caller to scale by the pen width. REAL length = (REAL)sin(0.5*angle); // Check for an infinite miter... if(REALABS(length) < REAL_EPSILON) { return miterLimit; } length = 1.0f / length; return min(miterLimit, length); } REAL GpPen::GetMaximumJoinWidth( REAL sharpestAngle, const GpMatrix* matrix, REAL dpiX, REAL dpiY) const { REAL delta; if ((matrix != NULL) && (DevicePen.IsOnePixelWideSolid(matrix, dpiX))) { delta = 0.5; } else { REAL maximumWidth; REAL delta0; REAL scale = 1.0; switch(DevicePen.PenAlignment) { case PenAlignmentCenter: scale = 0.5f; break; // use 1.0 for the inset pen. If the path is open, we render with a // center pen. // NOTE: a scale of 0.0 is sufficient for all inset pen rendering // provided all subpaths are closed. In the widener, we detect the // open subpaths and render them with a center pen. To accommodate // this, we increase the scale here. Theoretically we could use // scale = 0.5f for the inset pen (same as center pen), but this // bounds is an overestimate anyway and being wrong by one pixel too // small is way worse (crash) than being wrong and too big. case PenAlignmentInset: scale = 1.0f; break; } if(GetMaximumWidth(&maximumWidth, matrix) == Ok) { delta0 = maximumWidth; } else { maximumWidth = ::GetDeviceWidth( DevicePen.Width, DevicePen.Unit, dpiX); delta0 = maximumWidth; } if(DevicePen.Join == LineJoinMiter || DevicePen.Join == LineJoinMiterClipped) { REAL miterLimit = DevicePen.MiterLimit; delta = delta0*miterLimit; if(delta > 20) { delta = ComputeMiterLength( sharpestAngle, miterLimit ); // scale by the pen width. delta *= delta0; } } else { delta = delta0; } delta *= scale; } return delta; } REAL GpPen::GetMaximumCapWidth( const GpMatrix* matrix, REAL dpiX, REAL dpiY) const { REAL maximumWidth; REAL delta0; if(GetMaximumWidth(&maximumWidth, matrix) == Ok) { delta0 = maximumWidth; } else { maximumWidth = ::GetDeviceWidth( DevicePen.Width, DevicePen.Unit, dpiX); delta0 = maximumWidth; } REAL delta = delta0; GpLineCap startCap = DevicePen.StartCap; GpLineCap endCap = DevicePen.EndCap; REAL delta1; GpCustomLineCap* customCap = NULL; if(startCap == LineCapCustom && DevicePen.CustomStartCap) { customCap = static_cast (DevicePen.CustomStartCap); delta1 = customCap->GetRadius(delta0, 1.0f); } else { if(!(startCap & LineCapAnchorMask)) delta1 = 0.5f*delta0; else delta1 = 2.0f*(delta0 + 1); } if(delta < delta1) delta = delta1; if(endCap == LineCapCustom && DevicePen.CustomEndCap) { customCap = static_cast (DevicePen.CustomEndCap); delta1 = customCap->GetRadius(delta0, 1.0f); } else { if(!(endCap & LineCapAnchorMask)) delta1 = 0.5f*delta0; else delta1 = 2.0f*(delta0 + 2); } if(delta < delta1) delta = delta1; return delta; } VOID GpPen::SetDashCap(GpDashCap dashCap) { // Note: Internally we use a GpLineCap type to store the dash cap type. // So we need to convert between GpLineCap and GpDashCap. // However, we should change the internal usage to GpDashCap in v2. // - JBronsk GpLineCap lineCap = LineCapFlat; switch (dashCap) { case DashCapRound: lineCap = LineCapRound; break; case DashCapTriangle: lineCap = LineCapTriangle; break; // all others map to LineCapFlat } GpStatus status = SetDashStyleWithDashCap(DevicePen.DashStyle, lineCap); if(status == Ok) { DevicePen.DashCap = lineCap; } } GpStatus GpPen::SetDashStyle( GpDashStyle dashStyle ) { return SetDashStyleWithDashCap(dashStyle, DevicePen.DashCap); } GpStatus GpPen::SetDashStyleWithDashCap( GpDashStyle dashStyle, GpLineCap dashCap ) { GpStatus status = Ok; REAL style[6]; INT count; switch(dashStyle) { case DashStyleSolid: count = 0; break; case DashStyleDash: count = 2; style[0] = 3; // a dash style[1] = 1; // a space break; case DashStyleDot: count = 2; style[0] = 1; // a dot style[1] = 1; // a space break; case DashStyleDashDot: count = 4; style[0] = 3; // a dash style[1] = 1; // a space style[2] = 1; // a dot style[3] = 1; // a space break; case DashStyleDashDotDot: count = 6; style[0] = 3; // a dash style[1] = 1; // a space style[2] = 1; // a dot style[3] = 1; // a space style[4] = 1; // a dot style[5] = 1; // a space break; case DashStyleCustom: // We assume that the custom dash has been set at the API. // The remaining code in this routine is for initializing an appropriate // dash array, which we already have in this case, so we're done. DevicePen.DashStyle = dashStyle; return Ok; default: // The dash style must be one of the predefined ones. status = InvalidParameter; } if(status != Ok) { return status; } if(DevicePen.DashCount < count) { REAL* newArray = (REAL*) GpMalloc(count*sizeof(REAL)); if(newArray) { GpFree(DevicePen.DashArray); DevicePen.DashArray = newArray; } else { status = OutOfMemory; } } if(status == Ok) { // initialize the DashArray. GpMemcpy(DevicePen.DashArray, &style[0], count*sizeof(REAL)); DevicePen.DashStyle = dashStyle; DevicePen.DashCount = count; UpdateUid(); } return status; } GpStatus GpPen::SetDashArray( const REAL* dashArray, INT count ) { ASSERT(dashArray && count > 0); // Make sure the all elements are positive. INT i = 0; GpStatus status = Ok; while(status == Ok && i < count) { if(dashArray[i++] <= 0) status = InvalidParameter; } if(status != Ok) return status; REAL* newArray = (REAL*) GpRealloc(DevicePen.DashArray, count*sizeof(REAL)); if(!newArray) return OutOfMemory; GpMemcpy(newArray, dashArray, count*sizeof(REAL)); DevicePen.DashStyle = DashStyleCustom; DevicePen.DashArray = newArray; DevicePen.DashCount = count; UpdateUid(); return Ok; } GpStatus GpPen::GetDashArray( REAL* dashArray, INT count ) const { ASSERT(dashArray != NULL && count <= DevicePen.DashCount); GpStatus status = Ok; if(dashArray == NULL || count > DevicePen.DashCount) return InvalidParameter; if(DevicePen.DashArray) GpMemcpy(dashArray, DevicePen.DashArray, count*sizeof(REAL)); else status = OutOfMemory; return status; } GpStatus GpPen::SetCompoundArray( const REAL* compoundArray, INT count ) { // count must be a positive even number. if(compoundArray == NULL || count <= 0 || (count & 0x01)) { return InvalidParameter; } // count is 2 or more here... // Compound Inset pens aren't implemented yet. // The code for correctly handling minimum width compound sub lines // is missing. if(DevicePen.PenAlignment == PenAlignmentInset) { return NotImplemented; } // Make sure the all elements are monitonically increasing // and its values are between 0 and 1. GpStatus status = Ok; REAL lastValue, nextValue; lastValue = compoundArray[0]; if(lastValue < 0.0f || lastValue > 1.0f) status = InvalidParameter; INT i = 1; while(status == Ok && i < count) { nextValue = compoundArray[i++]; if(nextValue < lastValue || nextValue > 1.0f) status = InvalidParameter; lastValue = nextValue; } if(status != Ok) return status; REAL* newArray = (REAL*) GpRealloc(DevicePen.CompoundArray, count*sizeof(REAL)); if(!newArray) return OutOfMemory; GpMemcpy(newArray, compoundArray, count*sizeof(REAL)); DevicePen.CompoundArray = newArray; DevicePen.CompoundCount = count; UpdateUid(); return Ok; } GpStatus GpPen::GetCompoundArray( REAL* compoundArray, INT count ) { ASSERT(compoundArray != NULL && count <= DevicePen.CompoundCount); if(compoundArray == NULL || count > DevicePen.CompoundCount) return InvalidParameter; if(DevicePen.CompoundArray && count > 0) GpMemcpy(compoundArray, DevicePen.CompoundArray, count*sizeof(REAL)); return Ok; } GpStatus GpPen::SetCustomStartCap( const GpCustomLineCap* customCap ) { if(DevicePen.CustomStartCap) delete DevicePen.CustomStartCap; // Reset the standard start cap to the default one. DevicePen.CustomStartCap = NULL; DevicePen.StartCap = LineCapFlat; if(customCap) { DevicePen.CustomStartCap = customCap->Clone(); DevicePen.StartCap = LineCapCustom; } UpdateUid(); return Ok; } GpStatus GpPen::GetCustomStartCap( GpCustomLineCap** customCap ) { if(DevicePen.CustomStartCap) *customCap = static_cast (DevicePen.CustomStartCap)->Clone(); else *customCap = NULL; return Ok; } GpStatus GpPen::SetCustomEndCap( const GpCustomLineCap* customCap ) { if(DevicePen.CustomEndCap) delete DevicePen.CustomEndCap; // Reset the standard start cap to the default one. DevicePen.CustomEndCap = NULL; DevicePen.EndCap = LineCapFlat; if(customCap) { DevicePen.CustomEndCap = customCap->Clone(); DevicePen.EndCap = LineCapCustom; } UpdateUid(); return Ok; } GpStatus GpPen::GetCustomEndCap( GpCustomLineCap** customCap ) { if(DevicePen.CustomEndCap) *customCap = static_cast (DevicePen.CustomEndCap)->Clone(); else *customCap = NULL; return Ok; } GpStatus GpPen::MultiplyTransform(const GpMatrix& matrix, GpMatrixOrder order) { GpStatus status = Ok; if (matrix.IsInvertible()) { if (order == MatrixOrderPrepend) { DevicePen.Xform.Prepend(matrix); } else { DevicePen.Xform.Append(matrix); } } else status = InvalidParameter; return status; } /**************************************************************************\ * * Function Description: * * Answer true if the two pen instances are equivalent, meaning they * are indistinguishable when rendering. * * Arguments: * * [IN] pen - pen to compare this against * Return Value: * * TRUE if equivalent. * * Created: * * 6/14/1999 peterost * \**************************************************************************/ BOOL GpPen::IsEqual( const GpPen * pen ) const { ASSERT(pen != NULL); if (pen == this) return TRUE; BOOL isEqual = TRUE; if (DevicePen.IsEqual(&pen->DevicePen) && DevicePen.DashStyle == pen->DevicePen.DashStyle && DevicePen.CompoundCount == pen->DevicePen.CompoundCount && Brush->IsEqual(pen->Brush) && DevicePen.Xform.IsEqual(&pen->DevicePen.Xform)) { // We need to check the equality further if the dash style // is not a solid line. if (DevicePen.DashStyle != DashStyleSolid) { if(DevicePen.DashStyle != DashStyleCustom) { // A case of the preset dash pattern. // Check only for the offset difference. if(DevicePen.DashOffset != pen->DevicePen.DashOffset) isEqual = FALSE; } else { if (DevicePen.DashCount == pen->DevicePen.DashCount && DevicePen.DashOffset == pen->DevicePen.DashOffset && DevicePen.DashArray != NULL && pen->DevicePen.DashArray != NULL) { INT i = 0; while(i < DevicePen.DashCount && isEqual) { if (DevicePen.DashArray[i] != pen->DevicePen.DashArray[i]) { isEqual = FALSE; } i++; } } else { isEqual = FALSE; } } } // Check for the compound lines. if(isEqual && DevicePen.CompoundCount > 0) { if(DevicePen.CompoundArray && pen->DevicePen.CompoundArray) { INT j = 0; while(j < DevicePen.CompoundCount && isEqual) { if(DevicePen.CompoundArray[j] != pen->DevicePen.CompoundArray[j]) { isEqual = FALSE; } j++; } } else { isEqual = FALSE; } } } else { isEqual = FALSE; } return isEqual; } // For GetData and SetData methods #define GDIP_PENFLAGS_TRANSFORM 0x00000001 #define GDIP_PENFLAGS_STARTCAP 0x00000002 #define GDIP_PENFLAGS_ENDCAP 0x00000004 #define GDIP_PENFLAGS_JOIN 0x00000008 #define GDIP_PENFLAGS_MITERLIMIT 0x00000010 #define GDIP_PENFLAGS_DASHSTYLE 0x00000020 #define GDIP_PENFLAGS_DASHCAP 0x00000040 #define GDIP_PENFLAGS_DASHOFFSET 0x00000080 #define GDIP_PENFLAGS_DASHARRAY 0x00000100 #define GDIP_PENFLAGS_NONCENTER 0x00000200 #define GDIP_PENFLAGS_COMPOUNDARRAY 0x00000400 #define GDIP_PENFLAGS_CUSTOMSTARTCAP 0x00000800 #define GDIP_PENFLAGS_CUSTOMENDCAP 0x00001000 class PenData : public ObjectTypeData { public: INT32 Flags; INT32 Unit; REAL Width; }; /**************************************************************************\ * * Function Description: * * Get the pen data. * * Arguments: * * [IN] dataBuffer - fill this buffer with the data * [IN/OUT] size - IN - size of buffer; OUT - number bytes written * * Return Value: * * GpStatus - Ok or error code * * Created: * * 9/13/1999 DCurtis * \**************************************************************************/ GpStatus GpPen::GetData( IStream * stream ) const { if (Brush == NULL) { WARNING(("Brush is NULL")); return Ok; } ASSERT (stream != NULL); INT flags = 0; if (!DevicePen.Xform.IsIdentity()) { flags |= GDIP_PENFLAGS_TRANSFORM; } INT customStartCapSize = 0; INT customEndCapSize = 0; if (DevicePen.StartCap != LineCapFlat) { if (DevicePen.StartCap == LineCapCustom) { if ((DevicePen.CustomStartCap != NULL) && DevicePen.CustomStartCap->IsValid() && ((customStartCapSize = DevicePen.CustomStartCap->GetDataSize()) > 0)) { flags |= GDIP_PENFLAGS_STARTCAP | GDIP_PENFLAGS_CUSTOMSTARTCAP; } } else { flags |= GDIP_PENFLAGS_STARTCAP; } } if (DevicePen.EndCap != LineCapFlat) { if (DevicePen.EndCap == LineCapCustom) { if ((DevicePen.CustomEndCap != NULL) && DevicePen.CustomEndCap->IsValid() && ((customEndCapSize = DevicePen.CustomEndCap->GetDataSize()) > 0)) { flags |= GDIP_PENFLAGS_ENDCAP | GDIP_PENFLAGS_CUSTOMENDCAP; } } else { flags |= GDIP_PENFLAGS_ENDCAP; } } if (DevicePen.Join != LineJoinMiter) { flags |= GDIP_PENFLAGS_JOIN; } if (DevicePen.MiterLimit != 10) { flags |= GDIP_PENFLAGS_MITERLIMIT; } // DashStyleCustom is handled by hasDashArray if ((DevicePen.DashStyle != DashStyleSolid) && (DevicePen.DashStyle != DashStyleCustom)) { flags |= GDIP_PENFLAGS_DASHSTYLE; } if (DevicePen.DashCap != LineCapFlat) { flags |= GDIP_PENFLAGS_DASHCAP; } if (DevicePen.DashOffset != 0) { flags |= GDIP_PENFLAGS_DASHOFFSET; } if ((DevicePen.DashStyle == DashStyleCustom) && (DevicePen.DashArray != NULL) && (DevicePen.DashCount > 0)) { flags |= GDIP_PENFLAGS_DASHARRAY; } if (DevicePen.PenAlignment != PenAlignmentCenter) { flags |= GDIP_PENFLAGS_NONCENTER; } if ((DevicePen.CompoundArray != NULL) && (DevicePen.CompoundCount > 0)) { flags |= GDIP_PENFLAGS_COMPOUNDARRAY; } PenData penData; penData.Type = DevicePen.Type; penData.Flags = flags; penData.Unit = DevicePen.Unit; penData.Width = DevicePen.Width; stream->Write(&penData, sizeof(penData), NULL); if (flags & GDIP_PENFLAGS_TRANSFORM) { DevicePen.Xform.WriteMatrix(stream); } if (flags & GDIP_PENFLAGS_STARTCAP) { stream->Write(&DevicePen.StartCap, sizeof(INT32), NULL); } if (flags & GDIP_PENFLAGS_ENDCAP) { stream->Write(&DevicePen.EndCap, sizeof(INT32), NULL); } if (flags & GDIP_PENFLAGS_JOIN) { stream->Write(&DevicePen.Join, sizeof(INT32), NULL); } if (flags & GDIP_PENFLAGS_MITERLIMIT) { stream->Write(&DevicePen.MiterLimit, sizeof(REAL), NULL); } if (flags & GDIP_PENFLAGS_DASHSTYLE) { stream->Write(&DevicePen.DashStyle, sizeof(INT32), NULL); } if (flags & GDIP_PENFLAGS_DASHCAP) { stream->Write(&DevicePen.DashCap, sizeof(INT32), NULL); } if (flags & GDIP_PENFLAGS_DASHOFFSET) { stream->Write(&DevicePen.DashOffset, sizeof(REAL), NULL); } if (flags & GDIP_PENFLAGS_DASHARRAY) { stream->Write(&DevicePen.DashCount, sizeof(INT32), NULL); stream->Write(DevicePen.DashArray, DevicePen.DashCount * sizeof(REAL), NULL); } if (flags & GDIP_PENFLAGS_NONCENTER) { stream->Write(&DevicePen.PenAlignment, sizeof(INT32), NULL); } if (flags & GDIP_PENFLAGS_COMPOUNDARRAY) { stream->Write(&DevicePen.CompoundCount, sizeof(INT32), NULL); stream->Write(DevicePen.CompoundArray, DevicePen.CompoundCount * sizeof(REAL), NULL); } GpStatus status; if (flags & GDIP_PENFLAGS_CUSTOMSTARTCAP) { stream->Write(&customStartCapSize, sizeof(INT32), NULL); if ((status = DevicePen.CustomStartCap->GetData(stream)) != Ok) { return status; } } if (flags & GDIP_PENFLAGS_CUSTOMENDCAP) { stream->Write(&customEndCapSize, sizeof(INT32), NULL); if ((status = DevicePen.CustomEndCap->GetData(stream)) != Ok) { return status; } } status = Brush->GetData(stream); return status; } UINT GpPen::GetDataSize() const { if (Brush == NULL) { WARNING(("Brush is NULL")); return 0; } UINT dataSize = sizeof(PenData); if (!DevicePen.Xform.IsIdentity()) { dataSize += GDIP_MATRIX_SIZE; } INT customStartCapSize = 0; INT customEndCapSize = 0; if (DevicePen.StartCap != LineCapFlat) { if (DevicePen.StartCap == LineCapCustom) { if ((DevicePen.CustomStartCap != NULL) && DevicePen.CustomStartCap->IsValid() && ((customStartCapSize = DevicePen.CustomStartCap->GetDataSize()) > 0)) { // startcap + sizeof custom cap + custom cap dataSize += sizeof(INT32) + sizeof(INT32) + customStartCapSize; } } else { dataSize += sizeof(INT32); } } if (DevicePen.EndCap != LineCapFlat) { if (DevicePen.EndCap == LineCapCustom) { if ((DevicePen.CustomEndCap != NULL) && DevicePen.CustomEndCap->IsValid() && ((customEndCapSize = DevicePen.CustomEndCap->GetDataSize()) > 0)) { // endcap + sizeof custom cap + custom cap dataSize += sizeof(INT32) + sizeof(INT32) + customEndCapSize; } } else { dataSize += sizeof(INT32); } } if (DevicePen.Join != LineJoinMiter) { dataSize += sizeof(INT32); } if (DevicePen.MiterLimit != 10) { dataSize += sizeof(REAL); } // DashStyleCustom is handled by hasDashArray if ((DevicePen.DashStyle != DashStyleSolid) && (DevicePen.DashStyle != DashStyleCustom)) { dataSize += sizeof(INT32); } if (DevicePen.DashCap != LineCapFlat) { dataSize += sizeof(INT32); } if (DevicePen.DashOffset != 0) { dataSize += sizeof(REAL); } if ((DevicePen.DashStyle == DashStyleCustom) && (DevicePen.DashArray != NULL) && (DevicePen.DashCount > 0)) { dataSize += sizeof(INT32) + (DevicePen.DashCount * sizeof(REAL)); } if (DevicePen.PenAlignment != PenAlignmentCenter) { dataSize += sizeof(INT32); } if ((DevicePen.CompoundArray != NULL) && (DevicePen.CompoundCount > 0)) { dataSize += sizeof(INT32) + (DevicePen.CompoundCount * sizeof(REAL)); } dataSize += Brush->GetDataSize(); return dataSize; } /**************************************************************************\ * * Function Description: * * Read the pen object from memory. * * Arguments: * * [IN] dataBuffer - the data that was read from the stream * [IN] size - the size of the data * * Return Value: * * GpStatus - Ok or failure status * * Created: * * 4/26/1999 DCurtis * \**************************************************************************/ GpStatus GpPen::SetData( const BYTE * dataBuffer, UINT size ) { if (dataBuffer == NULL) { WARNING(("dataBuffer is NULL")); return InvalidParameter; } if (size < sizeof(PenData)) { WARNING(("size too small")); return InvalidParameter; } const PenData * penData = reinterpret_cast(dataBuffer); if (!penData->MajorVersionMatches()) { WARNING(("Version number mismatch")); return InvalidParameter; } InitDefaultState(penData->Width, static_cast(penData->Unit)); dataBuffer += sizeof(PenData); size -= sizeof(PenData); if (penData->Flags & GDIP_PENFLAGS_TRANSFORM) { if (size < GDIP_MATRIX_SIZE) { WARNING(("size too small")); goto ErrorExit; } DevicePen.Xform.SetMatrix((REAL *)dataBuffer); dataBuffer += GDIP_MATRIX_SIZE; size -= GDIP_MATRIX_SIZE; } if (penData->Flags & GDIP_PENFLAGS_STARTCAP) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } DevicePen.StartCap = (GpLineCap) ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); } if (penData->Flags & GDIP_PENFLAGS_ENDCAP) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } DevicePen.EndCap = (GpLineCap) ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); } if (penData->Flags & GDIP_PENFLAGS_JOIN) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } DevicePen.Join = (GpLineJoin) ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); } if (penData->Flags & GDIP_PENFLAGS_MITERLIMIT) { if (size < sizeof(REAL)) { WARNING(("size too small")); goto ErrorExit; } DevicePen.MiterLimit = ((REAL *)dataBuffer)[0]; dataBuffer += sizeof(REAL); size -= sizeof(REAL); } if (penData->Flags & GDIP_PENFLAGS_DASHSTYLE) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } this->SetDashStyle((GpDashStyle)((INT32 *)dataBuffer)[0]); dataBuffer += sizeof(INT32); size -= sizeof(INT32); } if (penData->Flags & GDIP_PENFLAGS_DASHCAP) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } DevicePen.DashCap = (GpLineCap) ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); } if (penData->Flags & GDIP_PENFLAGS_DASHOFFSET) { if (size < sizeof(REAL)) { WARNING(("size too small")); goto ErrorExit; } DevicePen.DashOffset = ((REAL *)dataBuffer)[0]; dataBuffer += sizeof(REAL); size -= sizeof(REAL); } if (penData->Flags & GDIP_PENFLAGS_DASHARRAY) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } INT count = ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); if (size < (count * sizeof(REAL))) { WARNING(("size too small")); goto ErrorExit; } this->SetDashArray((REAL *)dataBuffer, count); dataBuffer += (count * sizeof(REAL)); size -= (count * sizeof(REAL)); } if (penData->Flags & GDIP_PENFLAGS_NONCENTER) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } DevicePen.PenAlignment = (GpPenAlignment) ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); } if (penData->Flags & GDIP_PENFLAGS_COMPOUNDARRAY) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } INT count = ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); if (size < (count * sizeof(REAL))) { WARNING(("size too small")); goto ErrorExit; } this->SetCompoundArray((REAL *)dataBuffer, count); dataBuffer += (count * sizeof(REAL)); size -= (count * sizeof(REAL)); } if (penData->Flags & GDIP_PENFLAGS_CUSTOMSTARTCAP) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } UINT capSize = ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); if ((size < capSize) || (capSize < sizeof(ObjectTypeData))) { WARNING(("size too small")); goto ErrorExit; } ASSERT(DevicePen.CustomStartCap == NULL); DevicePen.CustomStartCap = (GpCustomLineCap *)GpObject::Factory(ObjectTypeCustomLineCap, (const ObjectData *)dataBuffer, capSize); if ((DevicePen.CustomStartCap == NULL) || (DevicePen.CustomStartCap->SetData(dataBuffer, capSize) != Ok) || !DevicePen.CustomStartCap->IsValid()) { WARNING(("Failure getting CustomStartCap")); goto ErrorExit; } dataBuffer += capSize; size -= capSize; } if (penData->Flags & GDIP_PENFLAGS_CUSTOMENDCAP) { if (size < sizeof(INT32)) { WARNING(("size too small")); goto ErrorExit; } UINT capSize = ((INT32 *)dataBuffer)[0]; dataBuffer += sizeof(INT32); size -= sizeof(INT32); if ((size < capSize) || (capSize < sizeof(ObjectTypeData))) { WARNING(("size too small")); goto ErrorExit; } ASSERT(DevicePen.CustomEndCap == NULL); DevicePen.CustomEndCap = (GpCustomLineCap *)GpObject::Factory(ObjectTypeCustomLineCap, (const ObjectData *)dataBuffer, capSize); if ((DevicePen.CustomEndCap == NULL) || (DevicePen.CustomEndCap->SetData(dataBuffer, capSize) != Ok) || !DevicePen.CustomEndCap->IsValid()) { WARNING(("Failure getting CustomEndCap")); goto ErrorExit; } dataBuffer += capSize; size -= capSize; } if (Brush != NULL) { Brush->Dispose(); Brush = NULL; } if (size >= sizeof(ObjectTypeData)) { Brush = (GpBrush *)GpObject::Factory(ObjectTypeBrush, (const ObjectData *)dataBuffer, size); if (Brush != NULL) { if ((Brush->SetData(dataBuffer, size) == Ok) && Brush->IsValid()) { DevicePen.Brush = Brush->GetDeviceBrush(); SetValid(TRUE); UpdateUid(); return Ok; } Brush->Dispose(); Brush = NULL; } } WARNING(("Failure getting brush")); ErrorExit: SetValid(FALSE); return GenericError; } GpStatus GpPen::ColorAdjust( GpRecolor * recolor, ColorAdjustType type ) { ASSERT(recolor != NULL); if (type == ColorAdjustTypeDefault) { type = ColorAdjustTypePen; } if (Brush != NULL) { Brush->ColorAdjust(recolor, type); } return Ok; } GpStatus GpPen::GetColor( ARGB *argb ) const { if (Brush->GetBrushType() == BrushTypeSolidColor) { GpSolidFill * solidBrush = (GpSolidFill *) Brush; *argb = solidBrush->GetColor().GetValue(); return Ok; } return InvalidParameter; } GpStatus GpPen::SetColor( GpColor * color ) { if (Brush->GetBrushType() == BrushTypeSolidColor) { GpSolidFill * solidBrush = (GpSolidFill *) Brush; if (solidBrush->GetColor().GetValue() == color->GetValue()) { return Ok; } // !!! bhouse why do we allocate another brush just to change the // pen's color !!!! } GpSolidFill *newBrush = new GpSolidFill(*color); if (newBrush != NULL) { if (newBrush->IsValid()) { delete Brush; Brush = newBrush; DevicePen.Brush = Brush->GetDeviceBrush(); UpdateUid(); return Ok; } delete newBrush; } return GenericError; } GpStatus GpPen::SetBrush( GpBrush * brush ) { // Don't set the brush if it is the same color as the current one, // because that makes metafiles unnecessarily large. if ((Brush->GetBrushType() == BrushTypeSolidColor) && (brush->GetBrushType() == BrushTypeSolidColor)) { GpSolidFill * solidBrush = (GpSolidFill *) Brush; GpSolidFill * newSolidBrush = (GpSolidFill *) brush; if(solidBrush->GetColor().GetValue() == newSolidBrush->GetColor().GetValue()) { return Ok; } } GpBrush * newBrush = brush->Clone(); if (newBrush != NULL) { if (newBrush->IsValid()) { delete Brush; Brush = newBrush; DevicePen.Brush = Brush->GetDeviceBrush(); UpdateUid(); return Ok; } delete newBrush; } return GenericError; } GpPenType GpPen::GetPenType( ) { GpPenType type = PenTypeUnknown; if(Brush) { switch(Brush->GetBrushType()) { case BrushTypeSolidColor: type = PenTypeSolidColor; break; case BrushTypeHatchFill: type = PenTypeHatchFill; break; case BrushTypeTextureFill: type = PenTypeTextureFill; break; /* case BrushRectGrad: type = PenFillRectGrad; break; case BrushRadialGrad: type = PenFillRadialGrad; break; case BrushTriangleGrad: type = PenFillTriangleGrad; break; */ case BrushTypePathGradient: type = PenTypePathGradient; break; case BrushTypeLinearGradient: type = PenTypeLinearGradient; break; default: break; } } // We must implement LineTexture case. return type; } /**************************************************************************\ * * Function Description: * * Adjust the dash array for dash caps if present. * * Note that unlike line caps, dash caps do not extend the length * of the subpath, they are inset. So we shorten the dash segments * that draw a line and lengthen the dash segments that are spaces * by a factor of 2x the dash unit in order to leave space for the * caps that will be added by the widener. * * This fixes Whistler bug #126476. * * Arguments: * * [IN] dashCap - dash cap type * [IN] dashUnit - dash size - typically the pen width * [IN/OUT] dashArray - array containing the dash pattern that is adjusted. * [IN] dashCount - count of elements in the dash array * * Return Value: * * None. * * History: * * 9/27/2000 jbronsk * Created. * * 12/06/2000 aaronlie * Moved from GpPath * \**************************************************************************/ VOID GpPen::AdjustDashArrayForCaps( REAL dashUnit, REAL *dashArray, INT dashCount ) const { REAL adjustmentLength = 2.0f * GetDashCapInsetLength(dashUnit); if (adjustmentLength > 0.0f) { const REAL minimumDashValue = dashUnit * 0.001f; // a small number for (int i = 0; i < dashCount; i++) { if (i & 0x1) // index is odd - so this is a space { // lengthen the spaces dashArray[i] += adjustmentLength; } else // index is even - so this is a line { // shorten the lines dashArray[i] -= adjustmentLength; // check if we have made the dash too small // (as in the case of 'dots') if (dashArray[i] < minimumDashValue) { dashArray[i] = minimumDashValue; } } } } } /**************************************************************************\ * * Function Description: * * Computes the length of the inset required to accomodate a particular * dash cap type, since dash caps are contained within the dash length. * * Arguments: * * [IN] dashUnit - pen width * * Return Value: * * The amount that a dash needs to be inset on each end in order to * accomodate any dash caps. * * History: * * 9/27/2000 jbronsk * Created. * * 12/06/2000 aaronlie * Moved from GpPath * \**************************************************************************/ REAL GpPen::GetDashCapInsetLength( REAL dashUnit ) const { REAL insetLength = 0.0f; // dash caps can only be flat, round, or triangle switch(GetDashCap()) { case LineCapFlat: insetLength = 0.0f; break; case LineCapRound: case LineCapTriangle: insetLength = dashUnit * 0.5f; break; } return insetLength; } /**************************************************************************\ * * Function Description: * * Does a quick check to see if the path can be rendered as a solid * pixel wide line. * * Arguments: * * [IN] cappedDpiX - the resolution of the x direction * [IN] worldToDevice - World transform * * Return Value: * * TRUE if okay to be rendered as a one pixel line * * History: * * 12/17/1999 ikkof * Created it. * \**************************************************************************/ BOOL DpPen::IsOnePixelWideSolid( const GpMatrix *worldToDevice, REAL dpiX ) const { return this->IsSimple() && this->IsOnePixelWide(worldToDevice, dpiX); } /**************************************************************************\ * * Function Description: * * Does a quick check to see if the path can be rendered as a one * pixel wide line. * * Arguments: * * [IN] cappedDpiX - the resolution of the x direction * [IN] worldToDevice - World transform * * Return Value: * * TRUE if okay to be rendered as a one pixel line * * History: * * 10/6/2000 - peterost - factored out fron IsOnePixelWideSolid * \**************************************************************************/ BOOL DpPen::IsOnePixelWide( const GpMatrix *worldToDevice, REAL dpiX ) const { BOOL useOnePixelPath = FALSE; const REAL minimumPenWidth = 1.5f; // !!![andrewgo] This determination of a single pixel wide line is // unbelievably expensive // !!![andrewgo] This width check should be done simply using // the world-to-device transform! It would be // faster and simpler! REAL width = this->Width; GpUnit unit = this->Unit; if(unit == UnitWorld) { if(worldToDevice == NULL || worldToDevice->IsTranslate()) { if(width <= minimumPenWidth) useOnePixelPath = TRUE; } else if(worldToDevice->IsTranslateScale()) { REAL m11 = worldToDevice->GetM11(); REAL m22 = worldToDevice->GetM22(); REAL maxScale = max(REALABS(m11), REALABS(m22)); if(width*maxScale <= minimumPenWidth) useOnePixelPath = TRUE; } else { // This is a general transform. REAL majorR, minorR; // Radii for major and minor axis. if(::GetMajorAndMinorAxis( &majorR, &minorR, worldToDevice) == Ok) { if(width*majorR <= minimumPenWidth) useOnePixelPath = TRUE; } } } else { // Since GDI+ only uses the World Uinit, this code is not called // any more. width = ::GetDeviceWidth(width, unit, dpiX); if(width <= minimumPenWidth) useOnePixelPath = TRUE; } return useOnePixelPath; }