PathStrokerVector Class Reference

Path stroker Converts a Trapezoid (See ProcessPathToTrapezoids, pathtrap.h) stroke definition into an output path. More...

#include <pathstrk.h>

Inheritance diagram for PathStrokerVector:

PathStroker List of all members.

Public Member Functions

 PathStrokerVector (ValueFunction *WidthFunction, INT32 LineWidth, LineCapType CapType, DocRect *pSourceBounds)
 Constructor.
void PrepareToStroke (TrapEdgeList *pTraps)
 MUST be called before any calls to StrokePath or MapCoord.
virtual BOOL StrokePath (TrapsList *pTraps, Path *pOutput)
 DO NOT CALL this variant of StrokePath!
virtual BOOL StrokePath (Path *pSourceVector, Path *pOutput)
 ** Call PrepareToStroke before calling this function ** "Strokes" a path according to the "style" set in construction.
DocCoord MapCoord (DocCoord *pCoord)
 ** Call PrepareToStroke before calling this function ** Maps the given point into the output stroke path, and returns the new coordinate to be output at your discretion.
double GetScaleFactor (void)
 ** Call PrepareToStroke before calling this function ** Determines a rough scaling factor by which the stroke is changing size during the mapping process. Used to map things like LineWidth attributes so that their size remains roughly proportional to the new size of the stroke. The scale factor is based on the source and destination line widths, which may not always be brilliant, but is the best we can do.

Static Public Member Functions

static StrokeHandle GetCurrentBrush (void)
static void BodgeRipSelection (BOOL Repeating=FALSE)

Static Public Attributes

static StrokeHandle CurrentStroke = StrokeHandle_NoStroke

Protected Member Functions

void FindEdgeForCoord (DocCoord *pCoord, UINT32 StartIndex, INT32 Direction, TrapEdge *pOutput)
 ** Call PrepareToStroke before calling this function ** Determines a TrapEdge Centre point and Normal to be used when mapping the given point into the destination path.
void MapMove (DocCoord *pCoord)
 Maps the given point into the output stroke path, writing a MOVETO element.
void MapLine (DocCoord *pCoord1, DocCoord *pCoord2)
 Maps the given line into the output stroke path Assumes that the first point (pCoord1) has already been output to the destination path (i.e. it appends the line to pOutput).
void MapBezier (DocCoord *pCoords)
 Maps the given bezier curve into the output stroke path.
void RecursiveMapLine (TrapEdge *pEdge1, DocCoord &Point1, double Width1, TrapEdge *pEdge2, DocCoord &Point2, double Width2, INT32 Direction, Path *pOutput)
 Recursive method which maps points within a given trapezoid by recursively "flattening" the curve between the two trapezoid edges.
void RecursiveMapBezier (DocCoord *pCoords, DocCoord *p1, double t1, DocCoord *p2, double t2)
 Maps the given bezier curve into the output stroke path, recursing as necessary to produce a nice smooth result.

Private Member Functions

 CC_DECLARE_MEMDUMP (PathStrokerVector)

Private Attributes

PathpPath
DocRectpBounds
double BoundsWidth
double BoundsHalfHeight
double BoundsYCentre
PathpOutput
TrapEdgeListpCurrentEdgeList
UINT32 FirstEdge
UINT32 LastEdge

Detailed Description

Path stroker Converts a Trapezoid (See ProcessPathToTrapezoids, pathtrap.h) stroke definition into an output path.

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
20/2/97
This "moulds" all the elements (beziers/lines) of a given path to lie within a stroke envelope defined by the provided trapezoid list

Definition at line 196 of file pathstrk.h.


Constructor & Destructor Documentation

PathStrokerVector::PathStrokerVector ValueFunction WidthFunction,
INT32  LineWidth,
LineCapType  CapType,
DocRect pSourceBounds
 

Constructor.

Parameters:
WidthFunction - NULL, or a pointer to a width function for the stroke [INPUTS] LineWidth - The maximum width, in millipoints, of the stroke CapType - IGNORED pSourceBounds - The bounding rectangle of the source brush. A brush may consist of many paths, and this is the bounding rectangle of the entire brush. (May not be NULL)

Definition at line 860 of file pathstrk.cpp.

00862                   : PathStroker(WidthFunction, LineWidth, CapType)
00863     
00864 {
00865     ERROR3IF(pSourceBounds == NULL , "Illegal NULL params");
00866 
00867     pPath            = NULL;
00868     pBounds          = pSourceBounds;
00869     BoundsYCentre    = (double) (pSourceBounds->lo.y/2 + pSourceBounds->hi.y/2);
00870     BoundsWidth      = (double) pSourceBounds->Width();
00871     BoundsHalfHeight = (double) pSourceBounds->Height() / 2.0;
00872     pOutput          = NULL;
00873     pCurrentEdgeList = NULL;
00874     FirstEdge        = 0;
00875     LastEdge         = 0;
00876 }


Member Function Documentation

void PathStrokerVector::BodgeRipSelection BOOL  Repeating = FALSE  )  [static]
 

Definition at line 1704 of file pathstrk.cpp.

01705 {
01706     SelRange *pSel = GetApplication()->FindSelection();
01707 
01708     Node *pNode = pSel->FindFirst();
01709     if (pNode == NULL || !IS_A(pNode, NodeGroup))
01710     {
01711         ERROR3("Brush can only be made from a selected GROUP");
01712         return;
01713     }
01714 
01715     NodeGroup* pNewGroup = new NodeGroup;
01716     if (pNewGroup == NULL)
01717     {
01718         ERROR3("Couldn't create new brush (1)");
01719         return;
01720     }
01721 
01722     NodeGroup* pNewGroup1 = new NodeGroup;
01723     if (pNewGroup1 == NULL)
01724     {
01725         ERROR3("Couldn't create new brush (2)");
01726         delete pNewGroup;
01727         return;
01728     }
01729     
01730     // Attach this new group to the tree
01731     pNewGroup->AttachNode(pNode, NEXT);
01732     pNewGroup1->AttachNode(pNewGroup, FIRSTCHILD);
01733 
01734     // Copy the children across to this new group, and
01735     // make sure we have all the attributes we need (We must ensure the default attrs
01736     // are copied too, else bits of the clipart relying on the default attrs will
01737     // use current attrs when rendered, giving unpredictanble results - to override
01738     // things like fill colours, we have to provide a better interface...)
01739     if (!pNode->CopyChildrenTo(pNewGroup1) ||
01740         !pNewGroup->MakeAttributeComplete(NULL, TRUE, NULL, TRUE))
01741     {
01742         // CascadeDelete unlinks the node from the tree so don't panic.
01743         ERROR3("Couldn't create new brush (3)");
01744         pNewGroup->CascadeDelete();
01745         delete pNewGroup;
01746         return;
01747     }
01748 
01749     // --- Now, convert all IndexedColours (which are document-dependant) into standalone DocColours
01750     // BLOCK
01751     {
01752         Node *pCurNode = pNewGroup->FindFirstDepthFirst();
01753         Node *pNextNode;
01754 
01755         while (pCurNode !=NULL)
01756         {
01757             // We may be about to chop this node out of the tree, so get the next node now
01758             pNextNode = pCurNode->FindNextDepthFirst(pNewGroup);
01759 
01760             // Use to scan the colour fields of the attribute.
01761             if (pCurNode->IsAnAttribute())
01762             {
01763                 UINT32 Context = 0;
01764                 NodeAttribute *pNodeAttr = (NodeAttribute *) pCurNode;
01765 
01766                 // Get the next colour field from the attribute
01767                 DocColour *pColour = pNodeAttr->EnumerateColourFields(Context++);
01768 
01769                 while (pColour != NULL)
01770                 {
01771                     // For each colour field, make sure the colour is a local DocColour so that
01772                     // the sub-tree is entirely stand-alone
01773                     if (pColour->FindParentIndexedColour() != NULL)
01774                     {
01775                         ColourGeneric ColDef;
01776                         ColourContext *cc = ColourManager::GetColourContext(pColour->GetColourModel());
01777                         ERROR3IF(cc == NULL, "Can't find colour context?!");
01778 
01779                         // Get the IndexedColour definition as a standalone colour definition
01780                         cc->ConvertColour(pColour->FindParentIndexedColour(), &ColDef);
01781 
01782                         // Make the DocColour into a simple standalone "lookalike" of the parent colour
01783                         *pColour = DocColour(pColour->GetColourModel(), &ColDef);
01784                     }
01785 
01786                     pColour = pNodeAttr->EnumerateColourFields(Context++);
01787                 }
01788             }
01789             pCurNode = pNextNode;
01790         }
01791     }
01792 
01793     // --- Rip the node out of the tree and stick it in our cache.
01794     pNewGroup->UnlinkNodeFromTree();
01795 
01796     // Create a new spread & layer to stick the group into
01797 
01798     Spread *pBrush = new Spread;
01799     if (pBrush == NULL)
01800     {
01801         ERROR3("Couldn't create new brush (4)");
01802         pNewGroup->CascadeDelete();
01803         delete pNewGroup;
01804         return;
01805     }
01806 
01807     Layer *pBrushLayer = new Layer(pBrush, FIRSTCHILD, String_256(TEXT("Jason did this")));
01808     if (pBrushLayer == NULL)
01809     {
01810         ERROR3("Couldn't create new brush (5)");
01811         delete pBrush;
01812         pNewGroup->CascadeDelete();
01813         delete pNewGroup;
01814         return;
01815     }
01816 
01817     // And attach the clipart tree to our new layer
01818     pNewGroup->AttachNode(pBrushLayer, FIRSTCHILD, FALSE, TRUE);
01819 
01820     StrokeDefinition *pStrokeDef = new StrokeDefinition(pBrush, Repeating);
01821     if (pStrokeDef == NULL)
01822     {
01823         ERROR3("Couldn't create new brush (6)");
01824         pBrush->CascadeDelete();
01825         delete pBrush;
01826         return;
01827     }
01828 
01829     CurrentStroke = StrokeComponent::AddNewStroke(pStrokeDef);
01830 
01831 //  DebugTreeDlg::DumpSubTree(pBodgeBrush);
01832 }

PathStrokerVector::CC_DECLARE_MEMDUMP PathStrokerVector   )  [private]
 

void PathStrokerVector::FindEdgeForCoord DocCoord pCoord,
UINT32  StartIndex,
INT32  Direction,
TrapEdge pOutput
[protected]
 

** Call PrepareToStroke before calling this function ** Determines a TrapEdge Centre point and Normal to be used when mapping the given point into the destination path.

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
21/2/97
Parameters:
pCoord - The coordinate in the source path to be mapped [INPUTS] StartIndex - The index in the TrapsList from which to start searching. The closer this is to the right place, the faster it goes Direction - +1 if this edge is being used on a forward outline, or -1 if it is being used on a reverse outline (needed to duplicate join style flags correctly)
pOutput - A trapezoid edge structure, which will be filled in with [OUTPUTS] details of an edge to use for mapping this point. (The edge will lie exactly on the Position the point maps to in the path)
Notes: Note that only "floating" endpoints need to be mapped in this computationally expensive manner - midpoints are mapped elsewhere (by intersecting the source path with TrapEdges, and mapping on those exact intersections, thus eliminating the need for so much interpolation)

See also:
PathStrokerVector::PrepareToStroke

Definition at line 1103 of file pathstrk.cpp.

01105 {
01106     ERROR3IF(pCurrentEdgeList == NULL || pBounds == NULL,  "Call PrepareToStroke first!");
01107     ERROR3IF(pCoord == NULL || pOutput == NULL, "Illegal NULL params");
01108 
01109     // Find the parametric position of the coordinate in the source brush
01110     pOutput->Position = ((double)(pCoord->x - pBounds->lo.x)) / BoundsWidth;
01111 
01112     // --- If this is a grad fill point, or somebody screwed up the brush bounds, then
01113     // the point could lie outside the "legal" range - but we must try to map it sensibly,
01114     // or else things like fills will screw up big time!
01115     if (pOutput->Position < 0.0 || pOutput->Position > 1.0)
01116     {
01117         // Find the nearest TrapEdge to the point, and adjust Position to be the "distance" from that TrapEdge
01118         TrapEdge *pEdge1;
01119         if (pOutput->Position > 1.0)
01120         {
01121             pEdge1 = pCurrentEdgeList->GetLastTrapEdge();
01122             pOutput->Position -= 1.0;
01123         }
01124         else
01125             pEdge1 = pCurrentEdgeList->GetTrapEdge(0);
01126 
01127         // Calculate a new centre position
01128         // (pretend that the path continues as an infinite straight line in its last known direction)
01129         const double PathLength = pCurrentEdgeList->GetPathLength();
01130         pOutput->Centre.x = pEdge1->Centre.x - (INT32) (pOutput->Position * PathLength * (-pEdge1->Normal.y));
01131         pOutput->Centre.y = pEdge1->Centre.y - (INT32) (pOutput->Position * PathLength * pEdge1->Normal.x);
01132 
01133         // All the other variables are the same as the end TrapEdge
01134         pOutput->Normal = pEdge1->Normal;
01135         pOutput->PrevTrapJoin = pEdge1->PrevTrapJoin;
01136 
01137         // Now clip the position value so the caller thinks we're within the stroke bounds
01138         // (Necessary so that they use the correct Position with Width ValueFunctions, etc)
01139         if (pOutput->Position < 0.0)
01140             pOutput->Position = 0.0;
01141         else
01142             pOutput->Position = 1.0;
01143         return;
01144     }
01145 
01146 
01147     // --- Find the trapezoid edges on either side of that position. Start from FirstEdge,
01148     // as that is where the path starts, so vastly reduces needless searching for
01149     // points lying within the path. (Note that fill points can lie outside the
01150     // path... see the if statement below)
01151     UINT32 TrapIndex = pCurrentEdgeList->FindTrapEdge(pOutput->Position, StartIndex, 0);
01152     TrapEdge *pEdge1 = pCurrentEdgeList->GetTrapEdge(TrapIndex);
01153 
01154     // --- Generate the new TrapEdge information
01155     if (pOutput->Position <= pEdge1->Position || (TrapIndex >= pCurrentEdgeList->GetNumEdges() - 1))
01156     {
01157         // The point lies exactly on the pEdge position, so return the exact edge
01158         // (The if statement checks for it lying outside the trapezoid, but this should never happen,
01159         // due to the position checks at the top of this function, so it's safe for us to use
01160         // a catch-all at this point.
01161         pOutput->Normal = pEdge1->Normal;
01162         pOutput->Centre = pEdge1->Centre;
01163         if (Direction > 0 && TrapIndex < pCurrentEdgeList->GetNumEdges() - 1)
01164         {
01165             TrapEdge *pEdge2 = pCurrentEdgeList->GetTrapEdge(TrapIndex+1);
01166             pOutput->PrevTrapJoin = pEdge2->PrevTrapJoin;
01167         }
01168         else
01169             pOutput->PrevTrapJoin = pEdge1->PrevTrapJoin;
01170     }
01171     else
01172     {
01173         // The point lies between 2 TrapEdges, so we interpolate the trapezoid centre and
01174         // normal to approximate a trapedge for this exact Position on the path
01175         TrapEdge *pEdge2 = pCurrentEdgeList->GetTrapEdge(TrapIndex+1);
01176         ERROR3IF(pEdge2->Position <= pEdge1->Position, "Non-ascending trapezoid positions?!");
01177 
01178         // Determine how far between the 2 positions the point lies
01179         const double Fraction = (pOutput->Position - pEdge1->Position) / (pEdge2->Position - pEdge1->Position);
01180         const double InvFraction = 1.0 - Fraction;
01181 
01182         // Compute an interpolated normal
01183         pOutput->Normal.x = (InvFraction * pEdge1->Normal.x) + (Fraction * pEdge2->Normal.x);
01184         pOutput->Normal.y = (InvFraction * pEdge1->Normal.y) + (Fraction * pEdge2->Normal.y);
01185         pOutput->Normal.Normalise();
01186 
01187         // Compute an interpolated centreline point
01188         pOutput->Centre.x = (INT32) ((InvFraction * (double)pEdge1->Centre.x) + (Fraction * (double)pEdge2->Centre.x));
01189         pOutput->Centre.y = (INT32) ((InvFraction * (double)pEdge1->Centre.y) + (Fraction * (double)pEdge2->Centre.y));
01190     
01191         // Duplicate any join flags. We must take care to replicate in the correct
01192         // direction or joins will be stroked incorrectly
01193         if (Direction > 0)
01194             pOutput->PrevTrapJoin = pEdge2->PrevTrapJoin;
01195         else
01196             pOutput->PrevTrapJoin = pEdge1->PrevTrapJoin;
01197     }
01198 }

StrokeHandle PathStrokerVector::GetCurrentBrush void   )  [static]
 

Definition at line 1834 of file pathstrk.cpp.

01835 {
01836     return(CurrentStroke);
01837 }

double PathStrokerVector::GetScaleFactor void   ) 
 

** Call PrepareToStroke before calling this function ** Determines a rough scaling factor by which the stroke is changing size during the mapping process. Used to map things like LineWidth attributes so that their size remains roughly proportional to the new size of the stroke. The scale factor is based on the source and destination line widths, which may not always be brilliant, but is the best we can do.

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
24/2/97
Returns:
A scale factor indicating by how much a brush (or element thereof) will grow or shrink when mapped into the current stroke. Multiplying a size value by this factor will generate an approximate new size to use.
See also:
PathStrokerVector::PrepareToStroke; LineWidthAttribute::MouldIntoStroke

Definition at line 1281 of file pathstrk.cpp.

01282 {
01283     if (BoundsHalfHeight < 1.0)     // Avoid that nasty div by zero!
01284         return(1.0);
01285 
01286     return( ((double) MaxWidth) / BoundsHalfHeight );
01287 }

void PathStrokerVector::MapBezier DocCoord pCoords  )  [protected]
 

Maps the given bezier curve into the output stroke path.

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
20/2/97
Parameters:
pCoords - An array of 4 coordinates representing the bezier to map, [INPUTS] in the order [knot1] [control1] [control2] [knot2]

Definition at line 1580 of file pathstrk.cpp.

01581 {
01582     RecursiveMapBezier(pCoords, &pCoords[0], 0.0, &pCoords[3], 1.0);
01583 }

DocCoord PathStrokerVector::MapCoord DocCoord pCoord  ) 
 

** Call PrepareToStroke before calling this function ** Maps the given point into the output stroke path, and returns the new coordinate to be output at your discretion.

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
20/2/97
Parameters:
pCoord - The coordinate in the source path to be mapped [INPUTS]
Returns:
The mapped coordinate
Notes: Mapping is achieved by the following algorithm: Find the trapezoid in which the point lies. This is done by calculating the point's "Position" (X coord) in the source brush, and searching to find the TrapEdge structures on either side of that position. Linearly interpolate within the trapezoid to get a centreline point and normal for this exact position Determine how far the point is from the brush centreline (Y coord) and map that distance onto the new TrapEdge, scaling by the Variable Width function.

Note that only "floating" endpoints need to be mapped in this computationally expensive manner - midpoints are mapped elsewhere (by intersecting the source path with TrapEdges, and mapping on those exact intersections, thus eliminating the need for so much interpolation)

See also:
PathStrokerVector::PrepareToStroke; FillGeometryAttribute::MouldIntoStroke

Definition at line 1237 of file pathstrk.cpp.

01238 {
01239     ERROR3IF(pCurrentEdgeList == NULL || pBounds == NULL,  "Call PrepareToStroke first!");
01240 
01241     // Get a trapezoid edge description which lies exactly on this point's mapping Position
01242     TrapEdge MapEdge;
01243     FindEdgeForCoord(pCoord, FirstEdge, +1, &MapEdge);
01244     ERROR3IF(MapEdge.Position < 0.0 || MapEdge.Position > 1.0, "Position value was not properly clipped");
01245 
01246     // Calculate how far the point is from the Y centre of the brush, and scale
01247     // this distance by the variable width value for its Position
01248     const double SourceDist = (((double)pCoord->y) - BoundsYCentre) / BoundsHalfHeight;
01249     const double MappedDist = ((double)MaxWidth) * SourceDist * pWidthFunction->GetValue(MapEdge.Position);
01250 
01251     // Map that width onto the destination path
01252     return(DocCoord(MapEdge.Centre.x + (INT32)(MapEdge.Normal.x * MappedDist),
01253                     MapEdge.Centre.y + (INT32)(MapEdge.Normal.y * MappedDist) ));
01254 }

void PathStrokerVector::MapLine DocCoord pCoord1,
DocCoord pCoord2
[protected]
 

Maps the given line into the output stroke path Assumes that the first point (pCoord1) has already been output to the destination path (i.e. it appends the line to pOutput).

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
20/2/97
Parameters:
pCoord1 - } 2 coordinates representing the line to map [INPUTS] pCoord2 - } (these are coords in the SOURCE brush space)

Definition at line 1327 of file pathstrk.cpp.

01328 {
01329     // Find the parametric positions of the coordinates in the source brush
01330     double Position1 = ((double)(pCoord1->x - pBounds->lo.x)) / BoundsWidth;
01331     double Position2 = ((double)(pCoord2->x - pBounds->lo.x)) / BoundsWidth;
01332 
01333     // Work out the range of positions this line covers. If this is non-zero then we will map
01334     // a point at each trapezoid edge intersected by the line - otherwise, the line is vertical
01335     // and can just be output directly, so we don't bother interpolating
01336     const double PosRange = Position2 - Position1;
01337     if (PosRange == 0.0)
01338     {
01339         pOutput->AddLineTo(MapCoord(pCoord2));
01340         return;
01341     }
01342 
01343     // Find the trapezoid edge relating to the first position
01344     UINT32 TrapIndex = pCurrentEdgeList->FindTrapEdge(Position1, FirstEdge, LastEdge);
01345     ERROR3IF(TrapIndex < FirstEdge || TrapIndex > LastEdge, "TrapIndex outside expected range");
01346 
01347     UINT32 EndIndex = pCurrentEdgeList->FindTrapEdge(Position2, FirstEdge, LastEdge);
01348     ERROR3IF(EndIndex < FirstEdge || EndIndex > LastEdge, "Trap EndIndex outside expected range");
01349 
01350     // Calculate TrapEdge data for the first & last points
01351     TrapEdge FirstEdgeData;
01352     TrapEdge LastEdgeData;
01353     const INT32 Direction = (PosRange > 0.0) ? +1 : -1;
01354     FindEdgeForCoord(pCoord1, TrapIndex, Direction, &FirstEdgeData);
01355     FindEdgeForCoord(pCoord2, EndIndex,  Direction, &LastEdgeData);
01356 
01357     // Calculate the values for the 1st point
01358     TrapEdge *pLastEdge = &FirstEdgeData;
01359     double LastWidth = ((double)MaxWidth) *
01360                         (((double)pCoord1->y - BoundsYCentre) / BoundsHalfHeight);
01361     double BaseWidth = LastWidth * pWidthFunction->GetValue(pLastEdge->Position);
01362     DocCoord LastPoint( (INT32) (pLastEdge->Centre.x + pLastEdge->Normal.x * BaseWidth),
01363                         (INT32) (pLastEdge->Centre.y + pLastEdge->Normal.y * BaseWidth) );
01364 
01365     TrapEdge *pEdge = NULL;
01366 
01367     // Now, map the line through each trapezoid in turn. There are 2 variants of this
01368     // loop - 1 for the "forwards" outline, and one for the "reverse" outline.
01369     if (PosRange > 0.0)
01370     {
01371         // Because TrapIndex and EndIndex point at the entry edge of the relevant trapezoids,
01372         // we want to increment them both to refer to the exit edge instead.
01373         TrapIndex++;
01374 
01375         while (TrapIndex <= EndIndex)
01376         {
01377             pEdge = pCurrentEdgeList->GetTrapEdge(TrapIndex);
01378 
01379             // Interpolate the line between its intersections with the sides of this trapezoid
01380             // The LastXXXX variables hold the intersection values for the entry-intersection,
01381             // so now we find the values for the exit-intersection
01382             const double Fraction = (pEdge->Position - Position1) / PosRange;
01383             const double Y = ((1.0 - Fraction) * (double)pCoord1->y) + (Fraction * (double)pCoord2->y);
01384             const double CentreDist = (Y - BoundsYCentre) / BoundsHalfHeight;
01385             BaseWidth = ((double)MaxWidth) * CentreDist;
01386             const double Width = BaseWidth * pWidthFunction->GetValue(pEdge->Position);
01387 
01388             DocCoord NewPoint(pEdge->Centre.x + (INT32) (pEdge->Normal.x * Width),
01389                               pEdge->Centre.y + (INT32) (pEdge->Normal.y * Width) );
01390 
01391             RecursiveMapLine(pLastEdge, LastPoint, LastWidth,
01392                              pEdge, NewPoint, BaseWidth,
01393                              +1, pOutput);
01394 
01395             pLastEdge = pEdge;
01396             LastWidth = BaseWidth;
01397             LastPoint = NewPoint;
01398 
01399             TrapIndex++;
01400         }
01401     }
01402     else
01403     {
01404         // Now, map the line through each trapezoid in turn
01405         // This condition is really nasty; Translation: while (TrapIndex has not passed EndIndex)
01406         while (TrapIndex > EndIndex)
01407         {
01408             pEdge = pCurrentEdgeList->GetTrapEdge(TrapIndex);
01409 
01410             // Interpolate the line between its intersections with the sides of this trapezoid
01411             // The LastXXXX variables hold the intersection values for the entry-intersection,
01412             // so now we find the values for the exit-intersection
01413             const double Fraction = (pEdge->Position - Position1) / PosRange;
01414             const double Y = ((1.0 - Fraction) * (double)pCoord1->y) + (Fraction * (double)pCoord2->y);
01415             const double CentreDist = (Y - BoundsYCentre) / BoundsHalfHeight;
01416             BaseWidth = ((double)MaxWidth) * CentreDist;
01417             const double Width = BaseWidth * pWidthFunction->GetValue(pEdge->Position);
01418 
01419             DocCoord NewPoint(pEdge->Centre.x + (INT32) (pEdge->Normal.x * Width),
01420                               pEdge->Centre.y + (INT32) (pEdge->Normal.y * Width) );
01421 
01422             RecursiveMapLine(pLastEdge, LastPoint, LastWidth,
01423                              pEdge, NewPoint, BaseWidth,
01424                              -1, pOutput);
01425 
01426             pLastEdge = pEdge;
01427             LastWidth = BaseWidth;
01428             LastPoint = NewPoint;
01429 
01430             TrapIndex--;
01431         }
01432     }
01433 
01434     // And then map the last trapezoid
01435     pEdge = &LastEdgeData;
01436 
01437 //  const double Fraction = (pEdge->Position - Position1) / PosRange;
01438     const double CentreDist = (pCoord2->y - BoundsYCentre) / BoundsHalfHeight;
01439     BaseWidth = ((double)MaxWidth) * CentreDist;
01440     const double Width = BaseWidth * pWidthFunction->GetValue(pEdge->Position);
01441 
01442     DocCoord NewPoint(pEdge->Centre.x + (INT32) (pEdge->Normal.x * Width),
01443                       pEdge->Centre.y + (INT32) (pEdge->Normal.y * Width) );
01444 
01445     RecursiveMapLine(pLastEdge, LastPoint, LastWidth,
01446                      pEdge, NewPoint, BaseWidth,
01447                      (PosRange > 0) ? +1 : -1, pOutput);
01448 }

void PathStrokerVector::MapMove DocCoord pCoord  )  [protected]
 

Maps the given point into the output stroke path, writing a MOVETO element.

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
20/2/97
Parameters:
pCoord - The coordinate in the SOURCE path to be mapped [INPUTS]

Definition at line 1304 of file pathstrk.cpp.

01305 {
01306     pOutput->AddMoveTo(MapCoord(pCoord));
01307 }

void PathStrokerVector::PrepareToStroke TrapEdgeList pTraps  ) 
 

MUST be called before any calls to StrokePath or MapCoord.

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
20/2/97
Parameters:
pTraps - A trapezoidal description of the path envelope into which [INPUTS] the source path must be "moulded". (May not be NULL)
This sets the current stroke TrapsList which will be used for all future mappings. It is separate from StrokePath so that you can set this up for moulding of attributes which affect the paths which you will mould, and this needs to be done on a per-stroke basis.

See also:
PathStrokerVector::StrokePath; PathStrokerVector::MapCoord

Definition at line 902 of file pathstrk.cpp.

00903 {
00904     // Remember the new TrapsList
00905     ERROR3IF(pTraps == NULL, "Illegal NULL param");
00906     pCurrentEdgeList = pTraps;
00907 
00908     // And reset our edge markers to make sure they're in range and safe to use
00909     FirstEdge = LastEdge = 0;
00910 }

void PathStrokerVector::RecursiveMapBezier DocCoord pCoords,
DocCoord p1,
double  t1,
DocCoord p2,
double  t2
[protected]
 

Maps the given bezier curve into the output stroke path, recursing as necessary to produce a nice smooth result.

Parameters:
pCoords - An array of 4 coordinates representing the bezier to map, [INPUTS] in the order [knot1] [control1] [control2] [knot2]
p1 - The first point on the SOURCE curve t1 - The parametric position (between 0.0 & 1.0 inclusive) of p1

p2 - The second point on the SOURCE curve t2 - The parametric position of p2

Notes: Always recurses to subdivide the curve into at least 2 segments, to guarantee that no features of the bezier are missed.

Definition at line 1611 of file pathstrk.cpp.

01612 {
01613     // --- Watch out for infinite recursion. This can come about when there is a discontinuity
01614     // in the width function, where no amount of flattening will reduce the distance between p1 & p2
01615     RecursionDepth++;
01616     if (RecursionDepth > MaxRecursionDepth)
01617     {
01618         TRACEUSER( "Jason", _T(">> Recursion limit hit when stroking\n"));
01619         pOutput->AddLineTo(MapCoord(p2));
01620         return;
01621     }
01622 
01623 #define x0 (pCoords[0].x)
01624 #define y0 (pCoords[0].y)
01625 #define x1 (pCoords[1].x)
01626 #define y1 (pCoords[1].y)
01627 #define x2 (pCoords[2].x)
01628 #define y2 (pCoords[2].y)
01629 #define x3 (pCoords[3].x)
01630 #define y3 (pCoords[3].y)
01631 
01632     // Calculate the point at the middle t value
01633     const double t = (t1 + t2) / 2.0;
01634 
01635     const double tx = x1+t*(x2-x1);
01636     const double ty = y1+t*(y2-y1);
01637 
01638     const double Lx1 = x0  + t*(x1-x0);
01639     const double Ly1 = y0  + t*(y1-y0);
01640     const double Rx2 = x2  + t*(x3-x2);
01641     const double Ry2 = y2  + t*(y3-y2);
01642     const double Lx2 = Lx1 + t*(tx-Lx1);
01643     const double Ly2 = Ly1 + t*(ty-Ly1);
01644     const double Rx1 = tx  + t*(Rx2-tx);
01645     const double Ry1 = ty  + t*(Ry2-ty);
01646     const double Rx0 = Lx2 + t*(Rx1-Lx2);
01647     const double Ry0 = Ly2 + t*(Ry1-Ly2);
01648 
01649     DocCoord MidPoint((INT32)(Rx0+0.5), (INT32)(Ry0+0.5));
01650 
01651 #undef x0
01652 #undef y0
01653 #undef x1
01654 #undef y1
01655 #undef x2
01656 #undef y2
01657 #undef x3
01658 #undef y3
01659 
01660     // If the distance between the actual point and the current approximation is too great,
01661     // we will recursively flatten. However, we make sure that we divide the bezier into at
01662     // least 2 sections, so if the difference in t parameters is too great, we set Dist
01663     // large enough to cause a recursive flattening.
01664     double Dist2 = Flatness2;
01665 
01666     if (t2 - t1 < 0.45)
01667     {
01668         // calculate the mid point of the straight-line approximation
01669         DocCoord ApproxMidPoint(p1->x/2 + p2->x/2, p1->y/2 + p2->y/2);
01670 
01671         // Now map both mid-points into the stroke envelope
01672         DocCoord MappedMid    = MapCoord(&MidPoint);
01673         DocCoord MappedApprox = MapCoord(&ApproxMidPoint);
01674 
01675         // And see how far the approximation is from the ideal position
01676         const double dx = MappedMid.x - MappedApprox.x;
01677         const double dy = MappedMid.y - MappedApprox.y;
01678         Dist2 = dx*dx + dy*dy;
01679     }
01680 
01681     // If we're too far away, then flatten further, else map the flattened line segment
01682     if (Dist2 >= Flatness2)
01683     {
01684         RecursiveMapBezier(pCoords, p1, t1, &MidPoint, t);
01685         RecursiveMapBezier(pCoords, &MidPoint, t, p2, t2);
01686     }
01687     else
01688         MapLine(p1, p2);
01689 
01690     // And decrement the recursion depth counter
01691     RecursionDepth--;
01692 }

void PathStrokerVector::RecursiveMapLine TrapEdge pEdge1,
DocCoord Point1,
double  Width1,
TrapEdge pEdge2,
DocCoord Point2,
double  Width2,
INT32  Direction,
Path pOutput
[protected]
 

Recursive method which maps points within a given trapezoid by recursively "flattening" the curve between the two trapezoid edges.

Parameters:
pEdge1,Point1 - The source TrapEdge and coordinate of the first point [INPUTS] (which must have already been added to the output path) Width1 - The width (NOT including the effect of pWidthFunction) of the stroke at Point1
pEdge2, Point2 - The source TrapEdge and coordinate of the second point (which must NOT have been added to the output path) Width2 - The width (NOT including the effect of pWidthFunction) of the stroke at Point2

Direction - +1 when traversing the path in a forwards direction -1 when traversing the path in a backwards direction

Parameters:
pOutput - A pointer to a path in which the output will be generated [OUTPUTS] (May not be NULL)
It inserts extra mapped points between the two original points passed in to it, until the flatness criteria is met.

Definition at line 1484 of file pathstrk.cpp.

01487 {
01488     ERROR3IF(pEdge1 == NULL || pEdge2 == NULL || pOutput == NULL, "Illegal NULL params");
01489 
01490     // --- Watch out for infinite recursion. This can come about when there is a discontinuity
01491     // in the width function, where no amount of flattening will reduce the distance between
01492     // Point1 and Point2. Note that we are flattening into a very small (already flattened)
01493     // region of space, so we don't need very great recursion depth to get a decent approximation
01494     // to the actual curve required, hence I stop the recursion after 20 iterations.
01495     RecursionDepth++;
01496     if (RecursionDepth > MaxRecursionDepth)
01497     {
01498         TRACEUSER( "Jason", _T(">> Recursion limit hit when stroking\n"));
01499         pOutput->AddLineTo(Point2);
01500         return;
01501     }
01502 
01503     // --- Calculate the midpoint of the curve we are mapping
01504     // Calculate the midpoint "position" value
01505     TrapEdge MidEdge;
01506     MidEdge.Position = (pEdge1->Position + pEdge2->Position) / 2.0;
01507 
01508     // Calculate the midpoint normal vector
01509     MidEdge.Normal.x = (pEdge1->Normal.x + pEdge2->Normal.x) / 2.0;
01510     MidEdge.Normal.y = (pEdge1->Normal.y + pEdge2->Normal.y) / 2.0;
01511 
01512     // We should normalise the normal vector, but in the case of mitred/bevelled joins
01513     // we want a straight edge, which means leaving the normal vector with an interpolated
01514     // length as well as direction. This is indicated by the end-edge of the trapezoid 
01515     // having the PrevTrapIsJoin flag set. We also have to duplicate the flag on all
01516     // intermediate points so that they are also handled correctly.
01517     // (Note in the 'forwards' case the relevant "PrevTrap" flag is in pEdge2)
01518     if (Direction > 0)
01519         MidEdge.PrevTrapJoin = pEdge2->PrevTrapJoin;
01520     else
01521         MidEdge.PrevTrapJoin = pEdge1->PrevTrapJoin;
01522     if (MidEdge.PrevTrapJoin != TrapJoin_MitredOrBevelled)
01523         MidEdge.Normal.Normalise();
01524 
01525     // Calculate the (approximate) centreline midpoint
01526     // (approximate because we're using the flattened line rather than the source curve,
01527     // but this is still close enough to give acceptable quality).
01528     // We take care with the average to avoid overflowing our INT32s
01529     MidEdge.Centre.x = (pEdge1->Centre.x / 2) + (pEdge2->Centre.x / 2);
01530     MidEdge.Centre.y = (pEdge1->Centre.y / 2) + (pEdge2->Centre.y / 2);
01531 
01532     // Now, calculate the mapped midpoint
01533     const INT32 MidWidth = (INT32) ((Width1 + Width2) / 2.0);
01534     const INT32 MidWidthAll = (INT32) ((double)MidWidth * pWidthFunction->GetValue(MidEdge.Position));
01535     DocCoord MidPoint(MidEdge.Centre);
01536     MidPoint.x += (INT32) (MidEdge.Normal.x * MidWidthAll);
01537     MidPoint.y += (INT32) (MidEdge.Normal.y * MidWidthAll);
01538 
01539     // --- Calculate the midpoint of the straight line segment
01540     DocCoord ApproximateMidPoint(Point1.x/2 + Point2.x/2, Point1.y/2 + Point2.y/2);
01541 
01542     // --- Calculate the error in the midpoint position
01543     const double dx = MidPoint.x - ApproximateMidPoint.x;
01544     const double dy = MidPoint.y - ApproximateMidPoint.y;
01545     const double Dist2 = dx * dx + dy * dy;
01546 
01547     // --- If the straight-line approximation is not close enough to the curve, recurse,
01548     // else (recursion-tail) output the end-point that was passed in. We also recurse
01549     // if the Position distance between the endpoints is large, as otherwise it can
01550     // fail to flatten width functions properly.
01551     if ( Dist2>Flatness2 || fabs(pEdge1->Position-pEdge2->Position)>0.10 )
01552     {
01553         // Recurse on the left and right sides of the new midpoint
01554         RecursiveMapLine(pEdge1,   Point1,   Width1,    &MidEdge, MidPoint, MidWidth,  Direction, pOutput);
01555         RecursiveMapLine(&MidEdge, MidPoint, MidWidth,  pEdge2,   Point2,   Width2,    Direction, pOutput);
01556     }
01557     else
01558         pOutput->AddLineTo(Point2);
01559 
01560     // And decrement the recursion depth counter
01561     RecursionDepth--;
01562 }

BOOL PathStrokerVector::StrokePath Path pSourceVector,
Path pOutputPath
[virtual]
 

** Call PrepareToStroke before calling this function ** "Strokes" a path according to the "style" set in construction.

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
20/2/97
Parameters:
pSourceVector [INPUTS]
  • A source path to be moulded (the "vector brush" path). This will be moulded to line in the stroke envelope, and output into pOutput (May not be NULL)
pOutput - This path is filled in with a "filled outline" of the stroke [OUTPUTS] It's IsFilled flag will be TRUE, IsStroked will be FALSE. NOTE - the new path elements are APPENDED to this path, so you must clear it beforehand if you feel this would be handy.
Returns:
FALSE if it fails. (No error will be set at this level)
This is the second stage in the stroking process (the first stage being breaking a path down into a trapezoidal description).

Stroking is achieved by mapping the intersections of trapezoid edges with the width function, and then recursively flattening the mapped curve between those points until required "flatness" is achieved.

Vector stroking is achieved by moulding path(s) to lie inside the stroke envelope, in order to produce media (brush, pen, crayon, etc) simulations.

Notes: This function must be called individually for each source path of the brush, for each stroke in the trapezoid list. (This is to allow attributes to be mapped alongside, as the mappings are different for different subpaths of the path being stroked)

See also:
PathStrokerVector::PrepareToStroke

Definition at line 973 of file pathstrk.cpp.

00974 {
00975     ERROR3IF(pCurrentEdgeList == NULL || pBounds == NULL,  "Call PrepareToStroke first!");
00976     ERROR3IF(pSourceVector == NULL || pOutputPath == NULL, "Illegal NULL params");
00977 
00978     if (BoundsWidth <= 0.0 || BoundsHalfHeight <= 0.0)
00979         return(FALSE);
00980 
00981     pPath   = pSourceVector;
00982     pOutput = pOutputPath;
00983 
00984     // Precalculate the position values for the extents of the path within the brush
00985     DocRect PathBounds = pPath->GetBoundingRect();
00986     const double MinPathPos = ((double)(PathBounds.lo.x - pBounds->lo.x)) / BoundsWidth;
00987     const double MaxPathPos = ((double)(PathBounds.hi.x - pBounds->lo.x)) / BoundsWidth;
00988 
00989     // When doing repeating brushes, the last repeat can be clipped at the end of the path,
00990     // so we check if this entire path should be clipped off
00991     // BLOCK
00992     {
00993         TrapEdge *pLastEdge = pCurrentEdgeList->GetLastTrapEdge();
00994         if (pLastEdge != NULL && MinPathPos > pLastEdge->Position)
00995             return(TRUE);
00996     }
00997 
00998     // For each curve segment in the source path, we output a mapped curve, which
00999     // consists of a number of flattened straight line segments.
01000     DocCoord *pCoords    = pPath->GetCoordArray();
01001     PathVerb *pVerbs     = pPath->GetVerbArray();
01002     const INT32 NumCoords = pPath->GetNumCoords();
01003 
01004     // Determine the range of trapezoids this path will map into in the current list
01005     // This makes searching for appropriate trapezoids much more efficient.
01006     FirstEdge = pCurrentEdgeList->FindTrapEdge(MinPathPos, 0, 0);
01007     LastEdge  = pCurrentEdgeList->FindTrapEdge(MaxPathPos, FirstEdge, pCurrentEdgeList->GetNumEdges() - 1);
01008 
01009     // And map the path into this stroke
01010     INT32 Element = 0;
01011     while (Element < NumCoords)
01012     {
01013         switch (pVerbs[Element] & ~PT_CLOSEFIGURE)
01014         {
01015             case PT_MOVETO:
01016                 MapMove(&pCoords[Element]);
01017                 break;
01018 
01019             case PT_LINETO:
01020                 {
01021                     ERROR3IF(Element < 1, "Path has LINETO as first element?!");
01022 
01023                     RecursionDepth = 0;                     // Reset recursion depth counter to 0
01024                     MapLine(&pCoords[Element-1], &pCoords[Element]);
01025 
01026                     if ((pVerbs[Element] & PT_CLOSEFIGURE) != 0)
01027                     {
01028                         // If this was a CLOSEFIGURE verb, then set the last output point to be CloseFigure
01029                         PathVerb *pOutVerbs = pOutput->GetVerbArray();
01030                         ERROR3IF(pOutVerbs == NULL || pOutput->GetNumCoords() < 1, "Oh dear. That shouldn't be like that");
01031                         pOutVerbs[pOutput->GetNumCoords() - 1] |= PT_CLOSEFIGURE;
01032                     }
01033                 }
01034                 break;
01035 
01036             case PT_BEZIERTO:
01037                 {
01038                     ERROR3IF(Element < 1, "Path has BEZIERTO as first element?!");
01039 
01040                     RecursionDepth = 0;                     // Reset recursion depth counter to 0
01041                     MapBezier(&pCoords[Element-1]);
01042 
01043                     if ((pVerbs[Element+2] & PT_CLOSEFIGURE) != 0)
01044                     {
01045                         // If this was a CLOSEFIGURE verb, then set the last output point to be CloseFigure
01046                         PathVerb *pOutVerbs = pOutput->GetVerbArray();
01047                         ERROR3IF(pOutVerbs == NULL || pOutput->GetNumCoords() < 1, "Oh dear. That shouldn't be like that");
01048                         pOutVerbs[pOutput->GetNumCoords() - 1] |= PT_CLOSEFIGURE;
01049                     }
01050 
01051                     Element += 2;       // Skip the 2 bezier control coordinates
01052                 }
01053                 break;
01054 
01055             default:
01056                 ERROR3("PathStrokerVector::StrokePath() - unknown path verb!");
01057                 break;
01058         }
01059 
01060         Element++;
01061     }
01062 
01063     pOutput->IsFilled   = pSourceVector->IsFilled;
01064     pOutput->IsStroked  = pSourceVector->IsStroked;
01065 
01066     return(TRUE);
01067 }

BOOL PathStrokerVector::StrokePath TrapsList pTraps,
Path pOutput
[virtual]
 

DO NOT CALL this variant of StrokePath!

Author:
Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
Date:
20/2/97

Reimplemented from PathStroker.

Definition at line 925 of file pathstrk.cpp.

00926 {
00927     ERROR2(FALSE, "Do not call this variant of Strokepath()");
00928 }


Member Data Documentation

double PathStrokerVector::BoundsHalfHeight [private]
 

Definition at line 256 of file pathstrk.h.

double PathStrokerVector::BoundsWidth [private]
 

Definition at line 255 of file pathstrk.h.

double PathStrokerVector::BoundsYCentre [private]
 

Definition at line 257 of file pathstrk.h.

StrokeHandle PathStrokerVector::CurrentStroke = StrokeHandle_NoStroke [static]
 

Definition at line 248 of file pathstrk.h.

UINT32 PathStrokerVector::FirstEdge [private]
 

Definition at line 262 of file pathstrk.h.

UINT32 PathStrokerVector::LastEdge [private]
 

Definition at line 263 of file pathstrk.h.

DocRect* PathStrokerVector::pBounds [private]
 

Definition at line 254 of file pathstrk.h.

TrapEdgeList* PathStrokerVector::pCurrentEdgeList [private]
 

Definition at line 261 of file pathstrk.h.

Path* PathStrokerVector::pOutput [private]
 

Definition at line 259 of file pathstrk.h.

Path* PathStrokerVector::pPath [private]
 

Definition at line 253 of file pathstrk.h.


The documentation for this class was generated from the following files:
Generated on Sat Nov 10 03:59:40 2007 for Camelot by  doxygen 1.4.4