pathstrk.cpp

Go to the documentation of this file.
00001 // $Id: pathstrk.cpp 1516 2006-07-24 21:03:00Z gavin $
00002 /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE
00003 ================================XARAHEADERSTART===========================
00004  
00005                Xara LX, a vector drawing and manipulation program.
00006                     Copyright (C) 1993-2006 Xara Group Ltd.
00007        Copyright on certain contributions may be held in joint with their
00008               respective authors. See AUTHORS file for details.
00009 
00010 LICENSE TO USE AND MODIFY SOFTWARE
00011 ----------------------------------
00012 
00013 This file is part of Xara LX.
00014 
00015 Xara LX is free software; you can redistribute it and/or modify it
00016 under the terms of the GNU General Public License version 2 as published
00017 by the Free Software Foundation.
00018 
00019 Xara LX and its component source files are distributed in the hope
00020 that it will be useful, but WITHOUT ANY WARRANTY; without even the
00021 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00022 See the GNU General Public License for more details.
00023 
00024 You should have received a copy of the GNU General Public License along
00025 with Xara LX (see the file GPL in the root directory of the
00026 distribution); if not, write to the Free Software Foundation, Inc., 51
00027 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
00028 
00029 
00030 ADDITIONAL RIGHTS
00031 -----------------
00032 
00033 Conditional upon your continuing compliance with the GNU General Public
00034 License described above, Xara Group Ltd grants to you certain additional
00035 rights. 
00036 
00037 The additional rights are to use, modify, and distribute the software
00038 together with the wxWidgets library, the wxXtra library, and the "CDraw"
00039 library and any other such library that any version of Xara LX relased
00040 by Xara Group Ltd requires in order to compile and execute, including
00041 the static linking of that library to XaraLX. In the case of the
00042 "CDraw" library, you may satisfy obligation under the GNU General Public
00043 License to provide source code by providing a binary copy of the library
00044 concerned and a copy of the license accompanying it.
00045 
00046 Nothing in this section restricts any of the rights you have under
00047 the GNU General Public License.
00048 
00049 
00050 SCOPE OF LICENSE
00051 ----------------
00052 
00053 This license applies to this program (XaraLX) and its constituent source
00054 files only, and does not necessarily apply to other Xara products which may
00055 in part share the same code base, and are subject to their own licensing
00056 terms.
00057 
00058 This license does not apply to files in the wxXtra directory, which
00059 are built into a separate library, and are subject to the wxWindows
00060 license contained within that directory in the file "WXXTRA-LICENSE".
00061 
00062 This license does not apply to the binary libraries (if any) within
00063 the "libs" directory, which are subject to a separate license contained
00064 within that directory in the file "LIBS-LICENSE".
00065 
00066 
00067 ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS
00068 ----------------------------------------------
00069 
00070 Subject to the terms of the GNU Public License (see above), you are
00071 free to do whatever you like with your modifications. However, you may
00072 (at your option) wish contribute them to Xara's source tree. You can
00073 find details of how to do this at:
00074   http://www.xaraxtreme.org/developers/
00075 
00076 Prior to contributing your modifications, you will need to complete our
00077 contributor agreement. This can be found at:
00078   http://www.xaraxtreme.org/developers/contribute/
00079 
00080 Please note that Xara will not accept modifications which modify any of
00081 the text between the start and end of this header (marked
00082 XARAHEADERSTART and XARAHEADEREND).
00083 
00084 
00085 MARKS
00086 -----
00087 
00088 Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara
00089 designs are registered or unregistered trademarks, design-marks, and/or
00090 service marks of Xara Group Ltd. All rights in these marks are reserved.
00091 
00092 
00093       Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK.
00094                         http://www.xara.com/
00095 
00096 =================================XARAHEADEREND============================
00097  */
00098 // Implementation of Path Stroking classes (used in stroke providing)
00099 
00100 #include "camtypes.h"
00101 
00102 #include "pathstrk.h"
00103 
00104 //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00105 #include "pathtrap.h"
00106 #include "valfunc.h"
00107 
00108 
00109 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00110 #include "colormgr.h"
00111 #include "dbugtree.h"
00112 //#include "fillattr.h"     // For debug/test code  - in camtypes.h [AUTOMATICALLY REMOVED]
00113 //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00114 #include "layer.h"
00115 #include "nodepath.h"
00116 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00117 
00118 
00119 CC_IMPLEMENT_MEMDUMP(PathStroker, CC_CLASS_MEMDUMP)
00120 CC_IMPLEMENT_MEMDUMP(PathStrokerVector, PathStroker)
00121 
00122 
00123 // Declare smart memory handling in Debug builds
00124 #define new CAM_DEBUG_NEW
00125 
00126 
00128 // Constants
00130 
00131 const double Flatness = 350.0;      // Flatness criteria in millipoints. (Could be based on zoom level later)
00132 const double Flatness2 = Flatness*Flatness;
00133 
00134 const INT32 MaxRecursionDepth = 20; // Recursion depth at which mapping of complex curves is stopped.
00135                                     // This stops stack overflows when the width function contains a discontinuity
00136 
00137 
00138 
00140 // PathStroker class
00142 
00143 
00144 /******************************************************************************************
00145 
00146 >   PathStroker::PathStroker()
00147 
00148     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00149     Created:    30/12/96
00150 
00151     Purpose:    Default Constructor - DO NOT USE THIS FORM!
00152 
00153 ******************************************************************************************/
00154 
00155 PathStroker::PathStroker()
00156 {
00157     ERROR3("Please don't use this PathStroker constructor again");
00158 }
00159 
00160 
00161 /******************************************************************************************
00162 
00163 >   PathStroker::PathStroker(ValueFunction *WidthFunction, INT32 LineWidth, LineCapType CapType)
00164 
00165     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00166     Created:    30/12/96
00167 
00168     Inputs:     WidthFunction - NULL, or a pointer to a width function for the stroke
00169                 LineWidth     - The maximum width, in millipoints, of the stroke
00170                 CapType       - The cap style (LineCapButt, LineCapRound, or LineCapSquare)
00171 
00172     Purpose:    Constructor
00173 
00174 ******************************************************************************************/
00175 
00176 PathStroker::PathStroker(ValueFunction *WidthFunction, INT32 LineWidth, LineCapType CapType)
00177 {
00178     ERROR3IF(WidthFunction == NULL, "Illegal NULL param");
00179     pWidthFunction = WidthFunction;
00180 
00181     MaxWidth = LineWidth / 2;   // MaxWidth is the RADIUS of the "brush", not diameter
00182 
00183     CapStyle = CapType;
00184 
00185     RecursionDepth = 0;         // Reset recursion depth counter to 0
00186 }
00187 
00188 
00189 
00190 /******************************************************************************************
00191 
00192 >   virtual BOOL PathStroker::StrokePath(LineCapType CapType, TrapsList *pTraps, Path *pOutput)
00193 
00194     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00195     Created:    30/12/96
00196 
00197     Inputs:     pTraps      - A trapezoidal description of the path to be stroked
00198                               (May not be NULL)
00199 
00200     Outputs:    pOutput     - This path is filled in with a "filled outline" of the stroke
00201                               It's IsFilled flag will be TRUE, IsStroked will be FALSE.
00202 
00203     Returns:    FALSE if it fails. (No error will be set at this level)
00204 
00205     Purpose:    "Strokes" a path according to the "style" set in construction.
00206                 
00207                 This is the second stage in the stroking process (the first stage being
00208                 breaking a path down into a trapezoidal description).
00209 
00210                 Stroking is achieved by mapping the intersections of trapezoid edges
00211                 with the width function, and then recursively flattening the mapped
00212                 curve between those points until required "flatness" is achieved.
00213 
00214                 The stroke outline is built up of 5 main components:
00215                                             A MOVETO point 1
00216                     1+---------------+2     A mapped outline between 1 & 2 ("FORWARDS")
00217                      |               |      A line cap between 2 & 3
00218                     4+---------------+3     A mapped outline between 3 & 4 ("BACKWARDS")
00219                                             A line cap between 4 & 1
00220 
00221                 The distance between points on the forwards & backwards outlines
00222                 (the stroke width) is governed by the LineWidth and WidthFunction
00223                 passed to the constructor.
00224 
00225     SeeAlso:    PathStroker::MapLineForwards;
00226                 PathStroker::MapLineBackwards;
00227                 PathStroker::MapLineCap
00228 
00229 ******************************************************************************************/
00230 
00231 BOOL PathStroker::StrokePath(TrapsList *pTraps, Path *pOutput)
00232 {
00233     ERROR3IF(pTraps == NULL || pOutput == NULL, "Illegal NULL params");
00234 
00235     // For each trapezoid list generated, add a subpath to the pOutput path
00236     for (UINT32 Index = 0; Index < pTraps->GetNumTraps(); Index++)
00237     {
00238         TrapEdgeList *pTrapEdges = pTraps->GetTrapEdgeList(Index);
00239         if (pTrapEdges->GetNumEdges() < 2)
00240         {
00241             ERROR3("No map traps when stroking! Subpath stripped\n");
00242             continue;
00243         }
00244 
00245 #if FALSE
00246         // Useful bit of code to dump out the TrapEdges
00247         {
00248             for (UINT32 i = 0; i < pTrapEdges->GetNumEdges(); i++)
00249             {
00250                 TrapEdge *pEdge = pTrapEdges->GetTrapEdge(i);
00251                 TRACE(_T("Edge % 5d  Position = %g  Type = %s  Centre = (%d, %d)  Normal = (%g, %g)\n"),
00252                     i, pEdge->Position, 
00253                     (pEdge->PrevTrapJoin == TrapJoin_None ? _T("N") : 
00254                         pEdge->PrevTrapJoin == TrapJoin_Round ? _T("R") :
00255                         pEdge->PrevTrapJoin == TrapJoin_MitredOrBevelled ? _T("M") : _T("U")),
00256                     pEdge->Centre.x, pEdge->Centre.y,
00257                     pEdge->Normal.x, pEdge->Normal.y);
00258             }
00259         }
00260 #endif
00261 
00262         // Check if this subpath is closed (ends in a join). If so, we must remove the line caps!
00263         LineCapType CapType = CapStyle;
00264         TrapEdge *pEdge = pTrapEdges->GetLastTrapEdge();
00265         if (pEdge->PrevTrapJoin != TrapJoin_None)
00266             CapType = LineCapButt;
00267 
00268         // Start a new sub-path with a MoveTo to the first point
00269         pEdge = pTrapEdges->GetTrapEdge(0);
00270         INT32 Width = (INT32) (MaxWidth * pWidthFunction->GetValue(pEdge->Position));
00271         INT32 dx       = (INT32) (pEdge->Normal.x * Width);
00272         INT32 dy       = (INT32) (pEdge->Normal.y * Width);
00273         DocCoord StartPoint(pEdge->Centre.x + dx, pEdge->Centre.y + dy);
00274         pOutput->AddMoveTo(StartPoint);
00275 
00276         // Map the "forwards" outline
00277         MapLineForwards(pTrapEdges, pOutput);
00278 
00279         // Add the line cap at the end of the line
00280         MapLineCap(CapType, pTrapEdges->GetLastTrapEdge(), FALSE, pOutput);
00281 
00282         // Map the "backwards" outline
00283         MapLineBackwards(pTrapEdges, pOutput);
00284 
00285         // Add the line cap at the end of the line, and we should meet back at the start!
00286         MapLineCap(CapType, pTrapEdges->GetTrapEdge(0), TRUE, pOutput);
00287 
00288         // And set the PT_CLOSEFIGURE flag on the last point, just to be safe
00289         INT32           indCoord = pOutput->GetNumCoords() - 1;
00290         PathVerb       *pVerbs = pOutput->GetVerbArray();
00291         pVerbs[indCoord] |= PT_CLOSEFIGURE;
00292     }
00293 
00294     pOutput->IsFilled   = TRUE;
00295     pOutput->IsStroked  = FALSE;
00296 
00297 #ifdef _DEBUG
00298     if(pTraps && pTraps->GetNumTraps() > 0 && pOutput)
00299     {
00300         TRACEUSER( "Matt", _T("Path stroked: %ld subpaths, (1st is %ld Traps) -> %ld Coords\n"),
00301                                 pTraps->GetNumTraps(), pTraps->GetTrapEdgeList(0)->GetNumEdges(),
00302                                 pOutput->GetNumCoords());
00303     }
00304 #endif
00305 
00306     return(TRUE);
00307 }
00308 
00309 
00310 
00311 /******************************************************************************************
00312 
00313 >   BOOL PathStroker::MapLineForwards(TrapEdgeList *pTraps, Path *pOutput)
00314 
00315     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00316     Created:    30/12/96
00317 
00318     Inputs:     pTraps  - A pointer to a trapezoid-descriptiuon of the path being stroked
00319                             (May not be NULL)
00320 
00321     Outputs:    pOutput - A pointer to a path in which the output will be generated
00322                             (May not be NULL)
00323 
00324     Returns:    FALSE if it fails. (No error will be set at this level)
00325 
00326     Purpose:    Maps points along the trapezoids, generating an outline path for the
00327                 "forwards" side of the stroke.
00328 
00329     Notes:      Expects the first point on this edge to have been written (as a MOVETO)
00330                 to the output path before this function was called.
00331 
00332 ******************************************************************************************/
00333 
00334 BOOL PathStroker::MapLineForwards(TrapEdgeList *pTraps, Path *pOutput)
00335 {
00336     TrapEdge *pEdge     = NULL;
00337     TrapEdge *pLastEdge = NULL;
00338     INT32    Width;
00339     DocCoord LastPoint;
00340     DocCoord NewPoint;
00341 
00342     for (UINT32 Index = 0; Index < pTraps->GetNumEdges(); Index++)
00343     {
00344         pLastEdge = pEdge;
00345         pEdge = pTraps->GetTrapEdge(Index);
00346 
00347         Width       = (INT32) (MaxWidth * pWidthFunction->GetValue(pEdge->Position));
00348         NewPoint.x  = pEdge->Centre.x + (INT32) (pEdge->Normal.x * Width);
00349         NewPoint.y  = pEdge->Centre.y + (INT32) (pEdge->Normal.y * Width);
00350 
00351         // The first point has already been added by the caller. The first iteration
00352         // of this loop is just to calculate the first "LastPoint" value.
00353         if (pLastEdge != NULL)
00354         {
00355             RecursionDepth = 0;                     // Reset recursion depth counter to 0
00356             RecursiveMapLineForwards(pLastEdge, LastPoint, pEdge, NewPoint, pOutput);
00357         }
00358 
00359         LastPoint = NewPoint;
00360     }
00361 
00362     return(TRUE);
00363 }
00364 
00365 
00366 
00367 /******************************************************************************************
00368 
00369 >   void PathStroker::RecursiveMapLineForwards( TrapEdge *pEdge1, DocCoord &Point1,
00370                                                 TrapEdge *pEdge2, DocCoord &Point2,
00371                                                 Path *pOutput)
00372     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00373     Created:    31/12/96
00374 
00375     Inputs:     pEdge1, Point1  - The source TrapEdge and coordinate of the first point
00376                                   (which must have already been added to the output path)
00377 
00378                 pEdge2, Point2  - The source TrapEdge and coordinate of the second point
00379                                   (which must NOT have been added to the output path)
00380 
00381     Outputs:    pOutput - A pointer to a path in which the output will be generated
00382                             (May not be NULL)
00383 
00384     Purpose:    Recursive method which maps points within a given trapezoid by
00385                 recursively "flattening" the curve between the two trapezoid edges.
00386 
00387                 It inserts extra mapped points between the two original points
00388                 passed in to it, until the flatness criteria is met.
00389 
00390 ******************************************************************************************/
00391 
00392 void PathStroker::RecursiveMapLineForwards( TrapEdge *pEdge1, DocCoord &Point1, TrapEdge *pEdge2, DocCoord &Point2, Path *pOutput)
00393 {
00394     ERROR3IF(pEdge1 == NULL || pEdge2 == NULL || pOutput == NULL, "Illegal NULL params");
00395 
00396     // --- Calculate the midpoint of the curve we are mapping
00397     // Calculate the midpoint "position" value
00398     TrapEdge MidEdge;
00399     MidEdge.Position = (pEdge1->Position + pEdge2->Position) / 2.0;
00400 
00401     // Calculate the midpoint normal vector
00402     MidEdge.Normal.x = (pEdge1->Normal.x + pEdge2->Normal.x) / 2.0;
00403     MidEdge.Normal.y = (pEdge1->Normal.y + pEdge2->Normal.y) / 2.0;
00404 
00405     MidEdge.PrevTrapJoin = pEdge2->PrevTrapJoin;
00406     if (MidEdge.PrevTrapJoin == TrapJoin_MitredOrBevelled)
00407     {
00408         // Work out if this on the inside or outside of the join
00409         double Val = (pEdge1->Normal.x * pEdge2->Normal.y) - (pEdge1->Normal.y * pEdge2->Normal.x);
00410         if (Val > 0.0)
00411         {
00412             // We are doing the inside of a join
00413 
00414             // If this is the special mitre join edge then just skip it
00415             // This removes the artifacts from the inside of mitre joins
00416             if (fabs(pEdge2->Normal.GetLength() - 1.0) > 0.0001)
00417                 return;
00418 
00419             MidEdge.Normal.Normalise();
00420         }
00421     }
00422     else
00423     {
00424         MidEdge.Normal.Normalise();
00425     }
00426 
00427     // Calculate the (approximate) centreline midpoint
00428     MidEdge.Centre.x = (pEdge1->Centre.x / 2) + (pEdge2->Centre.x / 2);
00429     MidEdge.Centre.y = (pEdge1->Centre.y / 2) + (pEdge2->Centre.y / 2);
00430 
00431     // Now, calculate the mapped midpoint
00432     const INT32 Width = (INT32) (MaxWidth * pWidthFunction->GetValue(MidEdge.Position));
00433     DocCoord MidPoint(MidEdge.Centre);
00434     MidPoint.x += (INT32) (MidEdge.Normal.x * Width);
00435     MidPoint.y += (INT32) (MidEdge.Normal.y * Width);
00436 
00437     // --- Calculate the midpoint of the straight line segment
00438     DocCoord ApproximateMidPoint(Point1.x/2 + Point2.x/2, Point1.y/2 + Point2.y/2);
00439 
00440     // --- Calculate the error in the midpoint position
00441     const double dx = MidPoint.x - ApproximateMidPoint.x;
00442     const double dy = MidPoint.y - ApproximateMidPoint.y;
00443     const double Dist2 = dx * dx + dy * dy;
00444 
00445     RecursionDepth++;
00446 
00447     // --- If the straight-line approximation is not close enough to the curve, recurse,
00448     // Don't bother to recurse for mitred or bevelled joins as they should be straight lines
00449     if ( MidEdge.PrevTrapJoin!=TrapJoin_MitredOrBevelled && 
00450          (Dist2>Flatness2 || RecursionDepth<=pWidthFunction->GetMinimumRecursionDepth()) &&
00451          RecursionDepth<=MaxRecursionDepth )
00452     {
00453         // Recurse on the left and right sides of the new midpoint
00454         RecursiveMapLineForwards(pEdge1,   Point1,   &MidEdge, MidPoint, pOutput);
00455         RecursiveMapLineForwards(&MidEdge, MidPoint, pEdge2,   Point2,   pOutput);
00456     }
00457     else
00458     {
00459         pOutput->AddLineTo(Point2);
00460     }
00461 
00462     RecursionDepth--;
00463 }
00464 
00465 
00466 
00467 /******************************************************************************************
00468 
00469 >   BOOL PathStroker::MapLineBackwards(TrapEdgeList *pTraps, Path *pOutput)
00470 
00471     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00472     Created:    30/12/96
00473 
00474     Inputs:     pTraps  - A pointer to a trapezoid-descriptiuon of the path being stroked
00475                             (May not be NULL)
00476 
00477     Outputs:    pOutput - A pointer to a path in which the output will be generated
00478                             (May not be NULL)
00479 
00480     Returns:    FALSE if it fails. (No error will be set at this level)
00481 
00482     Purpose:    Maps points along the trapezoids, generating an outline path for the
00483                 "Backwards" side of the stroke.
00484 
00485     Notes:      Expects the first point on this edge to have been written (as a LINETO)
00486                 to the output path before this function was called.
00487 
00488 ******************************************************************************************/
00489 
00490 BOOL PathStroker::MapLineBackwards(TrapEdgeList *pTraps, Path *pOutput)
00491 {
00492     TrapEdge *pEdge     = NULL;
00493     TrapEdge *pLastEdge = NULL;
00494     INT32    Width;
00495     DocCoord LastPoint;
00496     DocCoord NewPoint;
00497 
00498     for (INT32 Index = pTraps->GetNumEdges() - 1; Index >= 0; Index--)
00499     {
00500         pLastEdge = pEdge;
00501         pEdge = pTraps->GetTrapEdge(Index);
00502 
00503         Width       = (INT32) (MaxWidth * pWidthFunction->GetValue(pEdge->Position));
00504         NewPoint.x  = pEdge->Centre.x - (INT32) (pEdge->Normal.x * Width);
00505         NewPoint.y  = pEdge->Centre.y - (INT32) (pEdge->Normal.y * Width);
00506 
00507         // The first point has already been added by the caller. The first iteration
00508         // of this loop is just to calculate the first "LastPoint" value.
00509         if (pLastEdge != NULL)
00510         {
00511             RecursionDepth = 0;                     // Reset recursion depth counter to 0
00512             RecursiveMapLineBackwards(pLastEdge, LastPoint, pEdge, NewPoint, pOutput);
00513         }
00514 
00515         LastPoint = NewPoint;
00516     }
00517 
00518     return(TRUE);
00519 }
00520 
00521 
00522 
00523 /******************************************************************************************
00524 
00525 >   void PathStroker::RecursiveMapLineBackwards(TrapEdge *pEdge1, DocCoord &Point1,
00526                                                 TrapEdge *pEdge2, DocCoord &Point2,
00527                                                 Path *pOutput)
00528     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00529     Created:    31/12/96
00530 
00531     Inputs:     pEdge1, Point1  - The source TrapEdge and coordinate of the first point
00532                                   (which must have already been added to the output path)
00533 
00534                 pEdge2, Point2  - The source TrapEdge and coordinate of the second point
00535                                   (which must NOT have been added to the output path)
00536 
00537     Outputs:    pOutput - A pointer to a path in which the output will be generated
00538                             (May not be NULL)
00539 
00540     Purpose:    Recursive method which maps points within a given trapezoid by
00541                 recursively "flattening" the curve between the two trapezoid edges.
00542 
00543                 It inserts extra mapped points between the two original points
00544                 passed in to it, until the flatness criteria is met.
00545 
00546 ******************************************************************************************/
00547 
00548 void PathStroker::RecursiveMapLineBackwards(TrapEdge *pEdge1, DocCoord &Point1, TrapEdge *pEdge2, DocCoord &Point2, Path *pOutput)
00549 {
00550     ERROR3IF(pEdge1 == NULL || pEdge2 == NULL || pOutput == NULL, "Illegal NULL params");
00551 
00552     // --- Calculate the midpoint of the curve we are mapping
00553     // Calculate the midpoint "position" value
00554     TrapEdge MidEdge;
00555     MidEdge.Position = (pEdge1->Position + pEdge2->Position) / 2.0;
00556 
00557     // Calculate the midpoint normal vector
00558     MidEdge.Normal.x = (pEdge1->Normal.x + pEdge2->Normal.x) / 2.0;
00559     MidEdge.Normal.y = (pEdge1->Normal.y + pEdge2->Normal.y) / 2.0;
00560 
00561     // (Note in the 'backwards' case the "PrevTrap" flag is in pEdge1)
00562     MidEdge.PrevTrapJoin = pEdge1->PrevTrapJoin;
00563     if (MidEdge.PrevTrapJoin == TrapJoin_MitredOrBevelled)
00564     {
00565         // Work out if this on the inside or outside of the join
00566         double Val = (pEdge1->Normal.x * pEdge2->Normal.y) - (pEdge1->Normal.y * pEdge2->Normal.x);
00567         if (Val > 0.0)
00568         {
00569             // We are doing the inside of a join
00570 
00571             // If this is the special mitre join edge then just skip it
00572             // This removes the artifacts from the inside of mitre joins
00573             if (fabs(pEdge2->Normal.GetLength() - 1.0) > 0.0001)
00574                 return;
00575 
00576             MidEdge.Normal.Normalise();
00577         }
00578     }
00579     else
00580     {
00581         MidEdge.Normal.Normalise();
00582     }
00583 
00584     // Calculate the (approximate) centreline midpoint
00585     MidEdge.Centre.x = (pEdge1->Centre.x / 2) + (pEdge2->Centre.x / 2);
00586     MidEdge.Centre.y = (pEdge1->Centre.y / 2) + (pEdge2->Centre.y / 2);
00587 
00588     // Now, calculate the mapped midpoint
00589     const INT32 Width = (INT32) (MaxWidth * pWidthFunction->GetValue(MidEdge.Position));
00590     DocCoord MidPoint(MidEdge.Centre);
00591     MidPoint.x -= (INT32) (MidEdge.Normal.x * Width);
00592     MidPoint.y -= (INT32) (MidEdge.Normal.y * Width);
00593 
00594     // --- Calculate the midpoint of the straight line segment
00595     DocCoord ApproximateMidPoint(Point1.x/2 + Point2.x/2, Point1.y/2 + Point2.y/2);
00596 
00597     // --- Calculate the error in the midpoint position
00598     const double dx = MidPoint.x - ApproximateMidPoint.x;
00599     const double dy = MidPoint.y - ApproximateMidPoint.y;
00600     const double Dist2 = dx * dx + dy * dy;
00601 
00602     RecursionDepth++;
00603 
00604     // --- If the straight-line approximation is not close enough to the curve, recurse,
00605     // Don't bother to recurse for mitred or bevelled joins as they should be straight lines
00606     if ( MidEdge.PrevTrapJoin!=TrapJoin_MitredOrBevelled &&
00607          (Dist2>Flatness2 || RecursionDepth<=pWidthFunction->GetMinimumRecursionDepth()) &&
00608          RecursionDepth<=MaxRecursionDepth )
00609     {
00610 
00611         // Recurse on the left and right sides of the new midpoint
00612         RecursiveMapLineBackwards(pEdge1,   Point1,  &MidEdge, MidPoint, pOutput);
00613         RecursiveMapLineBackwards(&MidEdge, MidPoint, pEdge2,   Point2,  pOutput);
00614     }
00615     else
00616     {
00617         pOutput->AddLineTo(Point2);
00618     }
00619 
00620     RecursionDepth--;
00621 }
00622 
00623 
00624 
00625 /******************************************************************************************
00626 
00627 >   BOOL PathStroker::MapLineCap(LineCapType CapType, TrapEdge *pEdge, BOOL IsStartCap,
00628                                     Path *pOutput)
00629     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00630     Created:    3/1/97
00631 
00632     Inputs:     CapType     - The cap style (LineCapButt, LineCapRound, or LineCapSquare)
00633                 pEdge       - A pointer to the end trapezoid for this end of the stroke
00634                               (May not be NULL)
00635                 IsStartCap  - TRUE if this is the start cap, FALSE if it's the end cap.
00636                              This lets us get the cap orientation right
00637 
00638     Outputs:    pOutput     - A pointer to a path in which the output will be generated
00639                               (May not be NULL)
00640 
00641     Returns:    FALSE if it fails. (No error will be set at this level)
00642 
00643     Purpose:    Maps a line cap onto the path by appending appropriate extra 
00644                 path elements to the end of it.
00645 
00646     Notes:      This maps points from the end of the backward outline to the start
00647                 of the forward outline. It is expected that the StartPoint of the cap
00648                 (the end point of the backward outline) has been written to the
00649                 output immediately beforehand.
00650 
00651                 e.g. for the "leftmost" round cap, we have a situation like this:
00652                              EndPoint  +----> forward outline
00653                                      /
00654                                     |   
00655                         (MidPoint) +   + ----centreline (pEdge->Centre)
00656                                     |
00657                                      \
00658                             StartPoint +<---- backward outline
00659 
00660 ******************************************************************************************/
00661 
00662 BOOL PathStroker::MapLineCap(LineCapType CapType, TrapEdge *pEdge, BOOL IsStartCap, Path *pOutput)
00663 {
00664     ERROR3IF(pEdge == NULL || pOutput == NULL, "Illegal NULL params");
00665 
00666     // Work out dx,dy. These are tangential (dx) and perpendicular (dy) to the centreline
00667     // Orientation of the End cap is in the opposite direction to that of the start cap
00668     const INT32 Width       = (INT32) (MaxWidth * pWidthFunction->GetValue(pEdge->Position));
00669     const INT32 Orientation = (IsStartCap) ? 1 : -1;
00670     const INT32 dx          = Orientation * (INT32) (pEdge->Normal.x * Width);
00671     const INT32 dy          = Orientation * (INT32) (pEdge->Normal.y * Width);
00672 
00673     // Calculate the point at the "end" of the cap outline
00674     DocCoord EndPoint(pEdge->Centre.x + dx, pEdge->Centre.y + dy);                  // Last point on cap
00675 
00676     switch (CapType)
00677     {
00678         case LineCapButt:
00679             // Simply join the 2 outlines with a straight line
00680             pOutput->AddLineTo(EndPoint);
00681             break;
00682 
00683 
00684         case LineCapRound:
00685             {
00686                 // We are adding a semicircular arc onto the end of the stroke. This is done
00687                 // as two quarter circles: from the end of the outline to the extended centreline,
00688                 // then from the centreline to the start of the opposite outline.
00689                 DocCoord StartPoint(pEdge->Centre.x - dx, pEdge->Centre.y - dy);    // 1st point on cap
00690                 DocCoord MidPoint(pEdge->Centre.x - dy, pEdge->Centre.y + dx);      // midpoint on the arc
00691 
00692                 // Determine the distance the control points should be away from the endpoints
00693                 // to get a reasonable circular arc out of the bezier curve.
00694                 const INT32 CPdx    = (INT32) ( ((double)dx) * 0.552 );
00695                 const INT32 CPdy    = (INT32) ( ((double)dy) * 0.552 );
00696 
00697                 // Draw an arc from the backward outline of the stroke to the centreline
00698                 DocCoord Control1(StartPoint.x - CPdy, StartPoint.y + CPdx);
00699                 DocCoord Control2(MidPoint.x   - CPdx, MidPoint.y   - CPdy);
00700                 pOutput->AddCurveTo(Control1, Control2, MidPoint);
00701 
00702                 // Draw an arc from the centreline to the start of the forward stroke
00703                 DocCoord Control3(MidPoint.x + CPdx, MidPoint.y + CPdy);
00704                 DocCoord Control4(EndPoint.x - CPdy, EndPoint.y + CPdx);
00705                 pOutput->AddCurveTo(Control3, Control4, EndPoint);
00706             }
00707             break;
00708 
00709 
00710         case LineCapSquare:
00711             {
00712                 // Add 3 lines making up a half square centred on the centreline endpoint
00713                 DocCoord StartPoint(pEdge->Centre.x - dx, pEdge->Centre.y - dy);    // 1st point on cap
00714 
00715                 pOutput->AddLineTo(DocCoord(StartPoint.x - dy, StartPoint.y + dx));
00716                 pOutput->AddLineTo(DocCoord(EndPoint.x - dy, EndPoint.y + dx));
00717                 pOutput->AddLineTo(EndPoint);
00718             }
00719             break;
00720 
00721         default:
00722             ERROR3("Unknown line cap style");
00723             break;
00724     }
00725 
00726     return(TRUE);
00727 }
00728 
00729 
00730 
00731 /******************************************************************************************
00732 
00733 >   virtual BOOL PathStroker::StrokeSmoothPath(LineCapType CapType, TrapsList *pTraps, Path *pOutput)
00734 
00735     Author:     Priestley (Xara Group Ltd) <camelotdev@xara.com> - totally ripped from Jason above
00736     Created:    18/11/2000
00737 
00738     Inputs:     pTraps      - A trapezoidal description of the path to be stroked (May not be NULL)
00739 
00740     Outputs:    pOutput     - This path is filled in with a "filled outline" of the stroke
00741                               It's IsFilled flag will be TRUE, IsStroked will be FALSE.
00742 
00743     Returns:    FALSE if it fails. (No error will be set at this level)
00744 
00745     Purpose:    "Strokes" a path as above, but SMOOTHS its forwards and backwards paths to reduce points
00746 
00747 *******************************************************************************************/
00748 
00749 BOOL PathStroker::StrokeSmoothPath(TrapsList *pTraps, Path *pOutput)
00750 {
00751     ERROR3IF(pTraps == NULL || pOutput == NULL, "Illegal NULL params");
00752 
00753     // For each trapezoid list generated, add a subpath to the pOutput path
00754     for (UINT32 Index = 0; Index < pTraps->GetNumTraps(); Index++)
00755     {
00756         TrapEdgeList *pTrapEdges = pTraps->GetTrapEdgeList(Index);
00757         if (pTrapEdges->GetNumEdges() < 2)
00758         {
00759             ERROR3("No map traps when stroking! Subpath stripped\n");
00760             continue;
00761         }
00762 
00763         // Check if this subpath is closed (ends in a join). If so, we must remove the line caps!
00764         LineCapType CapType = CapStyle;
00765         TrapEdge *pEdge = pTrapEdges->GetLastTrapEdge();
00766         if (pEdge->PrevTrapJoin != TrapJoin_None)
00767             CapType = LineCapButt;
00768 
00769         // Start a new sub-path with a MoveTo to the first point
00770         pEdge = pTrapEdges->GetTrapEdge(0);
00771         INT32 Width = (INT32) (MaxWidth * pWidthFunction->GetValue(pEdge->Position));
00772         INT32 dx       = (INT32) (pEdge->Normal.x * Width);
00773         INT32 dy       = (INT32) (pEdge->Normal.y * Width);
00774         DocCoord StartPoint(pEdge->Centre.x + dx, pEdge->Centre.y + dy);
00775         pOutput->AddMoveTo(StartPoint);
00776 
00777         INT32 first = 0;
00778         INT32 last = -1;
00779 
00780         // Map the "forwards" outline
00781         MapLineForwards(pTrapEdges, pOutput);
00782         pOutput->SmoothSection(first, &last, 1.25 * Flatness/*2500*/, 0);
00783 
00784         // Add the line cap at the end of the line (if the height at this point of the value function is greater
00785         // than some threshold above zero).
00786         double height = pWidthFunction->GetValue(1);
00787         if (height > 0.05)
00788         {
00789             MapLineCap(CapType, pTrapEdges->GetLastTrapEdge(), FALSE, pOutput);
00790         }
00791 
00792         // Map the "backwards" outline
00793         first = pOutput->GetNumCoords() - 1;
00794         MapLineBackwards(pTrapEdges, pOutput);
00795         last = -1l;
00796         pOutput->SmoothSection(first, &last, 1.25 * Flatness/*2500*/, 0);
00797         
00798 
00799         INT32           indCoord;
00800         PathVerb       *pVerbs = pOutput->GetVerbArray();
00801 
00802         // Check all but the last verb to see if the path has been closed - if so, UNclose it!!!
00803         for( indCoord = 0; indCoord < pOutput->GetNumCoords() - 2; indCoord++ )
00804         {
00805             pVerbs[indCoord] &= ~PT_CLOSEFIGURE;
00806         }
00807 
00808         indCoord = pOutput->GetNumCoords() - 1;
00809         
00810         // Check to see if the last verb in the array has a CLOSEFIGURE type - if so then don't close it again!!!
00811         if (!(pVerbs[indCoord] & PT_CLOSEFIGURE))
00812         {
00813             // Add the line cap at the end of the line, and we should meet back at the start!
00814             MapLineCap(CapType, pTrapEdges->GetTrapEdge(0), TRUE, pOutput);
00815 
00816             // And set the PT_CLOSEFIGURE flag on the last point, just to be safe
00817             indCoord = pOutput->GetNumCoords() - 1;
00818             PathVerb *pVerbs = pOutput->GetVerbArray();
00819             pVerbs[indCoord] |= PT_CLOSEFIGURE;
00820         }
00821     }
00822 
00823     pOutput->IsFilled   = TRUE;
00824     pOutput->IsStroked  = FALSE;
00825 
00826 TRACEUSER( "Matt", _T("Path SMOOTHLY stroked: %ld subpaths, (1st is %ld Traps) -> %ld Coords\n"),
00827                             pTraps->GetNumTraps(), pTraps->GetTrapEdgeList(0)->GetNumEdges(),
00828                             pOutput->GetNumCoords());
00829 
00830     return(TRUE);
00831 }
00832 
00833 
00834 
00835 
00836 
00837 
00839 // PathStrokerVector class
00841 
00842 
00843 /******************************************************************************************
00844 
00845 >   PathStrokerVector::PathStrokerVector(ValueFunction *WidthFunction, INT32 LineWidth,
00846                                          LineCapType CapType, DocRect *pSourceBounds)
00847     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00848     Created:    20/2/97
00849 
00850     Inputs:     WidthFunction - NULL, or a pointer to a width function for the stroke
00851                 LineWidth     - The maximum width, in millipoints, of the stroke
00852                 CapType       - IGNORED
00853                 pSourceBounds - The bounding rectangle of the source brush. A brush may consist
00854                                 of many paths, and this is the bounding rectangle of the entire brush.
00855                                 (May not be NULL)
00856     Purpose:    Constructor
00857 
00858 ******************************************************************************************/
00859 
00860 PathStrokerVector::PathStrokerVector(ValueFunction *WidthFunction, INT32 LineWidth,
00861                                      LineCapType CapType, DocRect *pSourceBounds)
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 }
00877 
00878 
00879 
00880 /******************************************************************************************
00881 
00882 >   void PathStrokerVector::PrepareToStroke(TrapEdgeList *pTraps);
00883 
00884     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00885     Created:    20/2/97
00886 
00887     Inputs:     pTraps      - A trapezoidal description of the path envelope into which
00888                               the source path must be "moulded".
00889                               (May not be NULL)
00890 
00891     Purpose:    MUST be called before any calls to StrokePath or MapCoord.
00892 
00893                 This sets the current stroke TrapsList which will be used for all
00894                 future mappings. It is separate from StrokePath so that you can set
00895                 this up for moulding of attributes which affect the paths which you
00896                 will mould, and this needs to be done on a per-stroke basis.
00897 
00898     SeeAlso:    PathStrokerVector::StrokePath; PathStrokerVector::MapCoord
00899 
00900 ******************************************************************************************/
00901 
00902 void PathStrokerVector::PrepareToStroke(TrapEdgeList *pTraps)
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 }
00911 
00912 
00913 
00914 /******************************************************************************************
00915 
00916 >   virtual BOOL PathStrokerVector::StrokePath(TrapsList *pTraps, Path *pOutput)
00917 
00918     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00919     Created:    20/2/97
00920 
00921     Purpose:    DO NOT CALL this variant of StrokePath!
00922 
00923 ******************************************************************************************/
00924 
00925 BOOL PathStrokerVector::StrokePath(TrapsList *pTraps, Path *pOutput)
00926 {
00927     ERROR2(FALSE, "Do not call this variant of Strokepath()");
00928 }
00929 
00930 
00931 
00932 /******************************************************************************************
00933 
00934 >   virtual BOOL PathStrokerVector::StrokePath(Path *pSourceVector, Path *pOutput)
00935 
00936     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00937     Created:    20/2/97
00938 
00939     Inputs:     pSourceVector
00940                         - A source path to be moulded (the "vector brush" path). This will
00941                           be moulded to line in the stroke envelope, and output into pOutput
00942                           (May not be NULL)
00943 
00944     Outputs:    pOutput - This path is filled in with a "filled outline" of the stroke
00945                           It's IsFilled flag will be TRUE, IsStroked will be FALSE.
00946                           NOTE - the new path elements are APPENDED to this path, so
00947                           you must clear it beforehand if you feel this would be handy.
00948 
00949     Returns:    FALSE if it fails. (No error will be set at this level)
00950 
00951     Purpose:    ** Call PrepareToStroke before calling this function **
00952                 "Strokes" a path according to the "style" set in construction.
00953 
00954                 This is the second stage in the stroking process (the first stage being
00955                 breaking a path down into a trapezoidal description).
00956 
00957                 Stroking is achieved by mapping the intersections of trapezoid edges
00958                 with the width function, and then recursively flattening the mapped
00959                 curve between those points until required "flatness" is achieved.
00960 
00961                 Vector stroking is achieved by moulding path(s) to lie inside the stroke
00962                 envelope, in order to produce media (brush, pen, crayon, etc) simulations.
00963 
00964     Notes:      This function must be called individually for each source path of
00965                 the brush, for each stroke in the trapezoid list. (This is to allow
00966                 attributes to be mapped alongside, as the mappings are different
00967                 for different subpaths of the path being stroked)
00968 
00969     SeeAlso:    PathStrokerVector::PrepareToStroke
00970 
00971 ******************************************************************************************/
00972 
00973 BOOL PathStrokerVector::StrokePath(Path *pSourceVector, Path *pOutputPath)
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 }
01068 
01069 
01070 
01071 /******************************************************************************************
01072 
01073 >   void PathStrokerVector::FindEdgeForCoord(DocCoord *pCoord, UINT32 StartIndex,
01074                                              INT32 Direction, TrapEdge *pOutput)
01075 
01076     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01077     Created:    21/2/97
01078 
01079     Inputs:     pCoord      - The coordinate in the source path to be mapped
01080                 StartIndex  - The index in the TrapsList from which to start searching.
01081                               The closer this is to the right place, the faster it goes
01082                 Direction   - +1 if this edge is being used on a forward outline, or
01083                               -1 if it is being used on a reverse outline
01084                               (needed to duplicate join style flags correctly)
01085 
01086     Outputs:    pOutput     - A trapezoid edge structure, which will be filled in with
01087                               details of an edge to use for mapping this point. (The edge
01088                               will lie exactly on the Position the point maps to in the path)
01089 
01090     Purpose:    ** Call PrepareToStroke before calling this function **
01091                 Determines a TrapEdge Centre point and Normal to be used when mapping the
01092                 given point into the destination path.
01093 
01094     Notes:      Note that only "floating" endpoints need to be mapped in this computationally
01095                 expensive manner - midpoints are mapped elsewhere (by intersecting the
01096                 source path with TrapEdges, and mapping on those exact intersections,
01097                 thus eliminating the need for so much interpolation)
01098 
01099     SeeAlso:    PathStrokerVector::PrepareToStroke
01100 
01101 ******************************************************************************************/
01102 
01103 void PathStrokerVector::FindEdgeForCoord(DocCoord *pCoord, UINT32 StartIndex, INT32 Direction,
01104                                             TrapEdge *pOutput)
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 }
01199 
01200 
01201 
01202 /******************************************************************************************
01203 
01204 >   DocCoord PathStrokerVector::MapCoord(DocCoord *pCoord)
01205 
01206     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01207     Created:    20/2/97
01208 
01209     Inputs:     pCoord - The coordinate in the source path to be mapped
01210 
01211     Returns:    The mapped coordinate
01212 
01213     Purpose:    ** Call PrepareToStroke before calling this function **
01214                 Maps the given point into the output stroke path, and returns the
01215                 new coordinate to be output at your discretion.
01216 
01217     Notes:      Mapping is achieved by the following algorithm:
01218                 * Find the trapezoid in which the point lies. This is done by calculating
01219                   the point's "Position" (X coord) in the source brush, and searching to
01220                   find the TrapEdge structures on either side of that position.
01221                 * Linearly interpolate within the trapezoid to get a centreline point and
01222                   normal for this exact position
01223                 * Determine how far the point is from the brush centreline (Y coord) and
01224                   map that distance onto the new TrapEdge, scaling by the Variable Width
01225                   function.
01226 
01227                 Note that only "floating" endpoints need to be mapped in this computationally
01228                 expensive manner - midpoints are mapped elsewhere (by intersecting the
01229                 source path with TrapEdges, and mapping on those exact intersections,
01230                 thus eliminating the need for so much interpolation)
01231 
01232     SeeAlso:    PathStrokerVector::PrepareToStroke;
01233                 FillGeometryAttribute::MouldIntoStroke
01234 
01235 ******************************************************************************************/
01236 
01237 DocCoord PathStrokerVector::MapCoord(DocCoord *pCoord)
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 }
01255 
01256 
01257 
01258 /******************************************************************************************
01259 
01260 >   double PathStrokerVector::GetScaleFactor(void)
01261 
01262     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01263     Created:    24/2/97
01264 
01265     Returns:    A scale factor indicating by how much a brush (or element thereof)
01266                 will grow or shrink when mapped into the current stroke. Multiplying
01267                 a size value by this factor will generate an approximate new size to use.
01268 
01269     Purpose:    ** Call PrepareToStroke before calling this function **
01270                 Determines a rough scaling factor by which the stroke is changing size
01271                 during the mapping process. Used to map things like LineWidth attributes
01272                 so that their size remains roughly proportional to the new size of the
01273                 stroke. The scale factor is based on the source and destination line
01274                 widths, which may not always be brilliant, but is the best we can do.
01275 
01276     SeeAlso:    PathStrokerVector::PrepareToStroke;
01277                 LineWidthAttribute::MouldIntoStroke
01278 
01279 ******************************************************************************************/
01280 
01281 double PathStrokerVector::GetScaleFactor(void)
01282 {
01283     if (BoundsHalfHeight < 1.0)     // Avoid that nasty div by zero!
01284         return(1.0);
01285 
01286     return( ((double) MaxWidth) / BoundsHalfHeight );
01287 }
01288 
01289 
01290 
01291 /******************************************************************************************
01292 
01293 >   void PathStrokerVector::MapMove(DocCoord *pCoord)
01294 
01295     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01296     Created:    20/2/97
01297 
01298     Inputs:     pCoord - The coordinate in the SOURCE path to be mapped
01299 
01300     Purpose:    Maps the given point into the output stroke path, writing a MOVETO element
01301 
01302 ******************************************************************************************/
01303 
01304 void PathStrokerVector::MapMove(DocCoord *pCoord)
01305 {
01306     pOutput->AddMoveTo(MapCoord(pCoord));
01307 }
01308 
01309 
01310 
01311 /******************************************************************************************
01312 
01313 >   void PathStrokerVector::MapLine(DocCoord *pCoord1, DocCoord *pCoord2)
01314 
01315     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01316     Created:    20/2/97
01317 
01318     Inputs:     pCoord1     - } 2 coordinates representing the line to map
01319                 pCoord2     - } (these are coords in the SOURCE brush space)
01320 
01321     Purpose:    Maps the given line into the output stroke path
01322                 Assumes that the first point (pCoord1) has already been output to
01323                 the destination path (i.e. it appends the line to pOutput)
01324 
01325 ******************************************************************************************/
01326 
01327 void PathStrokerVector::MapLine(DocCoord *pCoord1, DocCoord *pCoord2)
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 }
01449 
01450 
01451 
01452 /******************************************************************************************
01453 
01454 >   void PathStrokerVector::RecursiveMapLine(TrapEdge *pEdge1, DocCoord &Point1, double Width1,
01455                                              TrapEdge *pEdge2, DocCoord &Point2, double Width2,
01456                                              INT32 Direction, Path *pOutput)
01457     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01458     Created:    21/2/97
01459 
01460     Inputs:     pEdge1, Point1  - The source TrapEdge and coordinate of the first point
01461                                   (which must have already been added to the output path)
01462                 Width1          - The width (NOT including the effect of pWidthFunction)
01463                                   of the stroke at Point1
01464 
01465                 pEdge2, Point2  - The source TrapEdge and coordinate of the second point
01466                                   (which must NOT have been added to the output path)
01467                 Width2          - The width (NOT including the effect of pWidthFunction)
01468                                   of the stroke at Point2
01469 
01470                 Direction       - +1 when traversing the path in a forwards direction
01471                                   -1 when traversing the path in a backwards direction
01472 
01473     Outputs:    pOutput - A pointer to a path in which the output will be generated
01474                             (May not be NULL)
01475 
01476     Purpose:    Recursive method which maps points within a given trapezoid by
01477                 recursively "flattening" the curve between the two trapezoid edges.
01478 
01479                 It inserts extra mapped points between the two original points
01480                 passed in to it, until the flatness criteria is met.
01481 
01482 ******************************************************************************************/
01483 
01484 void PathStrokerVector::RecursiveMapLine(TrapEdge *pEdge1, DocCoord &Point1, double Width1,
01485                                          TrapEdge *pEdge2, DocCoord &Point2, double Width2,
01486                                          INT32 Direction, Path *pOutput)
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 }
01563 
01564 
01565 
01566 /******************************************************************************************
01567 
01568 >   void PathStrokerVector::MapBezier(DocCoord *pCoords)
01569 
01570     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01571     Created:    20/2/97
01572 
01573     Inputs:     pCoords     - An array of 4 coordinates representing the bezier to map,
01574                               in the order [knot1] [control1] [control2] [knot2]
01575 
01576     Purpose:    Maps the given bezier curve into the output stroke path
01577 
01578 ******************************************************************************************/
01579 
01580 void PathStrokerVector::MapBezier(DocCoord *pCoords)
01581 {
01582     RecursiveMapBezier(pCoords, &pCoords[0], 0.0, &pCoords[3], 1.0);
01583 }
01584 
01585 
01586 
01587 /******************************************************************************************
01588 
01589 >   void PathStrokerVector::RecursiveMapBezier(DocCoord *pCoords,
01590                                                 DocCoord *p1, double t1, DocCoord *p2, double t2)
01591     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01592     Created:    6/3/97
01593 
01594     Inputs:     pCoords     - An array of 4 coordinates representing the bezier to map,
01595                               in the order [knot1] [control1] [control2] [knot2]
01596 
01597                 p1          - The first point on the SOURCE curve
01598                 t1          - The parametric position (between 0.0 & 1.0 inclusive) of p1
01599 
01600                 p2          - The second point on the SOURCE curve
01601                 t2          - The parametric position of p2
01602 
01603     Purpose:    Maps the given bezier curve into the output stroke path, recursing
01604                 as necessary to produce a nice smooth result.
01605 
01606     Notes:      Always recurses to subdivide the curve into at least 2 segments, to guarantee
01607                 that no features of the bezier are missed.
01608 
01609 ******************************************************************************************/
01610 
01611 void PathStrokerVector::RecursiveMapBezier(DocCoord *pCoords, DocCoord *p1, double t1, DocCoord *p2, double t2)
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 }
01693 
01694 
01695 
01696 
01697 
01699 // !!!!**** Debug stuff
01701 
01702 StrokeHandle PathStrokerVector::CurrentStroke = StrokeHandle_NoStroke;
01703 
01704 void PathStrokerVector::BodgeRipSelection(BOOL Repeating)
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 }
01833 
01834 StrokeHandle PathStrokerVector::GetCurrentBrush(void)
01835 {
01836     return(CurrentStroke);
01837 }
01838 

Generated on Sat Nov 10 03:46:29 2007 for Camelot by  doxygen 1.4.4