pathtrap.cpp

Go to the documentation of this file.
00001 // $Id: pathtrap.cpp 1282 2006-06-09 09:46:49Z alex $
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 Trapezoid classes (used in stroke providing)
00099 
00100 #include "camtypes.h"
00101 
00102 #include "pathtrap.h"
00103 
00104 //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00105 
00106 
00107 CC_IMPLEMENT_DYNAMIC(NormCoord, CCObject)
00108 CC_IMPLEMENT_DYNAMIC(TrapEdgeList, CCObject)
00109 CC_IMPLEMENT_DYNAMIC(TrapsList, CCObject)
00110 
00111 // Declare smart memory handling in Debug builds
00112 #define new CAM_DEBUG_NEW
00113 
00114 
00115 
00117 // TrapEdgeList class
00119 
00120 
00121 
00122 /******************************************************************************************
00123 
00124 >   TrapEdgeList::TrapEdgeList(TrapsList *pParent = NULL)
00125 
00126     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00127     Created:    27/12/97
00128 
00129     Inputs:     pParent - The parent TrapsList in which this TrapEdgeList resides.
00130                 Must be non-NULL
00131 
00132     Purpose:    Constructor
00133 
00134 ******************************************************************************************/
00135 
00136 TrapEdgeList::TrapEdgeList(TrapsList *pParent)
00137 {
00138     ERROR3IF(pParent == NULL, "Illegal NULL param");
00139 
00140     Used            = 0;
00141     CurrentSize     = 0;
00142     pEdges          = NULL;
00143     PathLength      = 1.0;
00144     pParentList     = pParent;
00145 }
00146 
00147 
00148 
00149 /******************************************************************************************
00150 
00151 >   TrapEdgeList::~TrapEdgeList()
00152 
00153     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00154     Created:    30/12/96
00155 
00156     Purpose:    Destructor
00157 
00158 ******************************************************************************************/
00159 
00160 TrapEdgeList::~TrapEdgeList()
00161 {
00162     if (pEdges != NULL)
00163         CCFree(pEdges);
00164 }
00165 
00166 
00167 
00168 /******************************************************************************************
00169 
00170 >   UINT32 TrapEdgeList::FindTrapEdge(double Position, UINT32 LoIndex = 0, UINT32 HiIndex = 0)
00171 
00172     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00173     Created:    20/2/97
00174 
00175     Inputs:     Position    - The position to search for
00176                 LoIndex     }
00177                 HiIndex     } Bounds in the list within which to start the search.
00178                               The search will go outside these bounds if necessary,
00179                               but good start bounds can significantly increase the
00180                               speed of the search. If you don't know specific bounds,
00181                               then you can pass a 0 in either/both variables.
00182 
00183     Returns:    The index of the found trapezoid edge (0 if error such as empty list)
00184 
00185     Purpose:    Searches the trapedge list for the trapezoid "containing" the
00186                 given position value. It starts its search from the given index
00187                 and searches upwards until it finds the last edge which has a position
00188                 less than or equal to the given position.
00189 
00190     Notes:      It may return the last edge, in which case the position value
00191                 lies outside the range of positions recorded in the list.
00192 
00193 ******************************************************************************************/
00194 
00195 UINT32 TrapEdgeList::FindTrapEdge(double Position, UINT32 LoIndex, UINT32 HiIndex)
00196 {
00197     if (Used < 2)
00198         return(0);
00199 
00200     // Special cases - if the point is outside the array bounds, then return the end immediately.
00201     // These boost speed greatly when clipping part of a vector stroke off the end, etc
00202     if (Position <= pEdges[0].Position)
00203         return(0);
00204 
00205     if (Position >= pEdges[Used-1].Position)
00206         return(Used-1);
00207 
00208     // Make sure HiIndex is greater than LoIndex - if not, set it to the last array entry
00209     if (HiIndex < LoIndex)
00210         HiIndex = Used-1;
00211 
00212     ERROR3IF(LoIndex >= Used || HiIndex >= Used || LoIndex > HiIndex, "Illegal start index(es)");
00213 
00214     // --- Check if the position is out of the original search bounds, or if it
00215     // is silly, and expand the bounds as necessary to make sure we will search all
00216     // relevant TrapEdges
00217     if (LoIndex >= HiIndex || LoIndex >= Used || Position <= pEdges[LoIndex].Position)
00218         LoIndex = 0;
00219 
00220     if (HiIndex <= LoIndex || HiIndex >= Used || Position >= pEdges[HiIndex].Position)
00221         HiIndex = Used-1;
00222 
00223     // --- Search the TrapEdges. This is done using a binary chop - we find the
00224     // midpoint between LoIndex & HiIndex, and move either Lo or Hi to that point,
00225     // so that the range reduces toward a single edge. This is far more efficient
00226     // than a linear search, especially on the large lists we typically deal with.
00227     while (HiIndex > LoIndex+1)
00228     {
00229         const UINT32 MidIndex = (LoIndex + HiIndex) / 2;
00230         if (pEdges[MidIndex].Position > Position)
00231             HiIndex = MidIndex;
00232         else
00233             LoIndex = MidIndex;
00234     }
00235 
00236     ERROR3IF(pEdges[LoIndex].Position > Position || pEdges[HiIndex].Position < Position,
00237                 "I think the binary chop has screwed up");
00238 
00239     return(LoIndex);
00240 }
00241 
00242 
00243 
00244 /******************************************************************************************
00245 
00246 >   void TrapEdgeList::AddEdge(DocCoord *pPoint, TrapJoinType JoinType = TrapJoin_None)
00247 
00248     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00249     Created:    30/12/96
00250 
00251     Inputs:     pPoint      - The coordinate of the new centreline point
00252                 JoinType    - Indicates if this edge completes a trapezoid which is part
00253                               of a join (and that join's type). This can be used by the
00254                               path stroker to correctly handle mitred and bevelled joins,
00255                               (and also to determine if line caps are needed, by looking
00256                               to see if the last TrapEdge is a join or a cap)
00257 
00258     Purpose:    Add a new TrapEdge structure to the list
00259 
00260                 If this is a repeating stroke, this may create new TrapEdgeList(s)
00261                 and start filling them in. The caller should always append edges
00262                 to the last TrapEdgeList in their TrapsList to ensure it works OK.
00263 
00264                 The added TrapEdge structure will be filled in with:
00265                         Centre       (will be pPoint)
00266                         Position     (will be the distance of the point, in millipoints,
00267                                      from the start of the stroke)
00268                         PrevTrapJoin (will be filled in with JoinType)
00269 
00270                 NOTE that the Normal will NOT be initialised - see ProcessEdgeNormals()
00271 
00272 ******************************************************************************************/
00273 
00274 BOOL TrapEdgeList::AddEdge(DocCoord *pPoint, TrapJoinType JoinType)
00275 {
00276     // --- Make sure we have room to add the new point to our array
00277     DocCoord Temp;                  // Temporary safe storage for the point
00278     if (Used >= CurrentSize)
00279     {
00280         // I pass in a pointer to the coord for efficiency, to save copying points until
00281         // absolutely necessary. This is safe under normal circumstances, except when
00282         // we realloc the array and the input pointer points INTO that array! Most of
00283         // the time, it works fine, but every now and then NT maps out the memory page
00284         // and we get a nasty access violation! Rather than copying the Coord every
00285         // time, I prefer therefore to copy it into a temporary point on those few
00286         // occasions where we actually have to realloc the array.
00287         Temp = *pPoint;             // Copy the point into safe, temporary storage
00288         pPoint = &Temp;             // And point the input value pointer at the safe copy
00289 
00290         if (!ExpandArray())         // Try to allocate more
00291             return FALSE;                   // And return if we failed
00292     }
00293 
00294     // --- Find the new entry and allocate it
00295     TrapEdge *pEdge = &pEdges[Used];
00296 
00297     // --- Record the point & join type
00298     pEdge->Centre = *pPoint;
00299     pEdge->PrevTrapJoin = JoinType;
00300 
00301     // --- Calculate position, and check for repeating strokes
00302     if (Used > 0)
00303     {
00304         TrapEdge *pPrevEdge = &pEdges[Used-1];
00305         const double dx = (double) (pPrevEdge->Centre.x - pPoint->x);
00306         const double dy = (double) (pPrevEdge->Centre.y - pPoint->y);
00307         double Travel = sqrt(dx*dx + dy*dy);
00308         if (Travel < 1.0)       // Make sure all positions increment slightly to keep everyone happy
00309             Travel = 1.0;
00310         pEdge->Position = pPrevEdge->Position + Travel;
00311 
00312         // Now check for (and handle) repeating strokes
00313         const INT32 RepeatLength = (pParentList == NULL) ? 0 : pParentList->GetRepeatLength();
00314         if (RepeatLength > 0 && JoinType == TrapJoin_None)
00315         {
00316             // If it's a repeating stroke, and we're not in the middle of a join, then
00317             // we compare the current path travel to the repeat distance to see if we've gone
00318             // past the end of this repeat. If we have, then we calculate the point where the
00319             // (first) repeat in this trapezoid should happen, and break this trapezoid there,
00320             // ending the current TrapEdgeList, and starting a new TrapEdgeList.
00321             // Note: We recursively call AddEdge on each new TrapEdgeList so that if the
00322             // trapezoid contains many repeats, we will subdivide it further.
00323 
00324             if (pEdge->Position >= (double)RepeatLength)
00325             {
00326                 // We've gone too far. Time to subdivide.
00327                 ERROR3IF(pEdge->Position - pPrevEdge->Position <= 0.0, "Position calculation is screwed up!");
00328 
00329                 const double Split = (((double)RepeatLength) - pPrevEdge->Position) / (pEdge->Position - pPrevEdge->Position);
00330 
00331                 // Calculate the split point & position value into our last Edge
00332                 pEdge->Centre.x = (INT32) (((1.0 - Split) * (double)pPrevEdge->Centre.x) + (Split * (double)pPoint->x));
00333                 pEdge->Centre.y = (INT32) (((1.0 - Split) * (double)pPrevEdge->Centre.y) + (Split * (double)pPoint->y));
00334                 pEdge->Position = (double) RepeatLength;
00335 
00336                 // And create a new TrapEdgelist, and initialise it to go from the split point to the
00337                 // edge which we were orignally passed - NOTE that this may RECURSE.
00338                 TrapEdgeList *pNewEdgeList = pParentList->AddEdgeList();
00339                 if (pNewEdgeList != NULL)
00340                 {
00341                     pNewEdgeList->AddEdge(&pEdge->Centre, TrapJoin_None);   // Add intersection point
00342                     pNewEdgeList->AddEdge(pPoint, TrapJoin_None);           // then add the original end Edge
00343                 }
00344             }
00345         }
00346     }
00347     else
00348         pEdge->Position = 0.0;
00349 
00350     // Finally, increment Used to move on to the next TrapEdge entry in our array
00351     Used++;
00352 
00353     return TRUE;
00354 }
00355 
00356 
00357 
00358 /******************************************************************************************
00359 
00360 >   BOOL TrapEdgeList::ExpandArray(void)
00361 
00362     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00363     Created:    30/12/96
00364 
00365     Outputs:    On succesful exit, the member array of TrapEdge structures will be bigger
00366     Returns:    FALSE if it failed to allocate memory
00367                 
00368     Purpose:    (Internal method)
00369                 Expands the storage structure of the list to allow more entries to be
00370                 used. Called automatically by AddEdge if it needs more edges.
00371 
00372     Notes:      Internal storage is an array of TrapEdge structures
00373 
00374                 NOTE that the array is not initialised, as it is expected that each
00375                 entry will be initialised on its first use. Initialisation would just
00376                 double the overhead of creating an entry, and we do a lot of that!
00377 
00378 ******************************************************************************************/
00379 
00380 BOOL TrapEdgeList::ExpandArray(void)
00381 {
00382     // Work out how many entries to allocate. Note that I use a fairly large number
00383     // so that we don't have to reallocate the array very often. Each structure is
00384     // small, so the memory usage is fairly low, although it must be noted that
00385     // a dashed line could potentially generate quite a pile of these lists!
00386     INT32 AllocSize = CurrentSize + 512;
00387     if (pParentList != NULL && pParentList->GetRepeatLength() > 0)
00388     {
00389         // If it's a repeating stroke, then we must be far more careful about memory usage!
00390         // (i.e. if there are 1000 repeats along a stroke, each taking 15kB, we'll eat 15MB!)
00391         // We assume that repeats are small and hence will not need very many entries, and
00392         // then if (and only if) we find we have to realloc to make the array bigger, we jump
00393         // in bigger steps.
00394         if (CurrentSize == 0)
00395             AllocSize = CurrentSize + 4;
00396         else
00397             AllocSize = CurrentSize + 16;
00398     }
00399 
00400     if (pEdges == NULL)
00401     {
00402         // We have no array yet, so allocate one
00403         pEdges = (TrapEdge *) CCMalloc(AllocSize * sizeof(TrapEdge));
00404         if (pEdges == NULL)
00405             return(FALSE);
00406     }
00407     else
00408     {
00409         // We have an array - we must make it larger
00410         TrapEdge *pNewBuf = (TrapEdge *) CCRealloc(pEdges, AllocSize * sizeof(TrapEdge));
00411         if (pNewBuf == NULL)
00412             return(FALSE);
00413 
00414         pEdges = pNewBuf;
00415     }
00416 
00417     // Success. Update the current size value
00418     CurrentSize = AllocSize;
00419     return(TRUE);
00420 }
00421 
00422 
00423 
00424 /******************************************************************************************
00425 
00426 >   BOOL TrapEdgeList::ProcessEdgeNormals(ProcessPathToTrapezoids *pProcessor)
00427 
00428     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00429     Created:    30/12/96
00430 
00431     Inputs:     pProcessor  - The object which is calling us
00432 
00433     Outputs:    On succesful exit, the member array of TrapEdge structures will
00434                 contain correct Normal values.
00435 
00436     Returns:    FALSE if it failed (Note: does not set an error at this level)
00437 
00438     Purpose:    Scans the recorded list of edges (added by AddEdge) and fills in the
00439                 edge Normal information.
00440 
00441                 MUST be called on the list before trying to use the edge structures!
00442 
00443                 Call this before calling ProcessEdgePositions if you want positions as well.
00444 
00445 ******************************************************************************************/
00446 
00447 BOOL TrapEdgeList::ProcessEdgeNormals(ProcessPathToTrapezoids *pProcessor)
00448 {
00449     if (Used < 2)                           // Sanity check
00450     {
00451         ERROR3("Not enough coordinates in trapezoid list!");
00452         return(FALSE);
00453     }
00454 
00455     TrapEdge *pPrevEdge = NULL;
00456     TrapEdge *pEdge     = NULL;
00457     NormCoord PrevNormal(1.0, 0.0);
00458     BOOL CompletingAMitre = FALSE;
00459 
00460     // --- First, scan the trapezoid list, and calculate the normal of each edge
00461     for (UINT32 Index = 0; Index < Used; Index++)
00462     {
00463         // Start by finding a pointer to the edge record and previous record (if any)
00464         pPrevEdge = pEdge;
00465         pEdge = &(pEdges[Index]);
00466 
00467         // Calculate the path normal for this trapezoid edge.
00468         if (pPrevEdge == NULL)
00469         {
00470             // It's the very first point. The normal is just the normal to the first segment
00471             TrapEdge *pNextEdge = &(pEdges[Index+1]);
00472             pEdge->Normal.SetNormalToLine(pEdge->Centre, pNextEdge->Centre);
00473 
00474             PrevNormal = pEdge->Normal;     // Remember this edge's normal for the next pass
00475         }
00476         else if (pEdge->Centre == pPrevEdge->Centre)
00477         {
00478             // Two points are coincident. That means a joint (or a degenerate source path element).
00479             //
00480             // The following cases can apply:
00481             //  if (the next point is ALSO coincident)
00482             //      * We have a mitred join. The normal is the average of the previous and next normals
00483             //        with some jiggery-pokery to make the normal also have a length which will get the
00484             //        outline out to the mitre intersection point (rather than being normalised)
00485             //  else
00486             //      * We have the last 2 points of the 3-point mitred join, or
00487             //      * We have a simple bevelled or rounded join
00488             //          (both of which are treated in the same manner)
00489 
00490             // Find the "next" edge. Care must be taken for the last join to make sure
00491             // that the first edge is treated as "next". Note that we don't use point 0
00492             // in this case, as that is coincident! We use point 1 which is the point
00493             // at the end of that first line.
00494             TrapEdge *pNextEdge = &pEdges[1];
00495             if (Index < Used-1)
00496                 pNextEdge = &pEdges[Index+1];
00497 
00498             if (pNextEdge->Centre == pEdge->Centre)
00499             {
00500                 // We have found 3 coincident points, so we've hit a mitred join.
00501                 // The middle Edge of the mitred join has a normal which is in the direction
00502                 // of the average of the preceeding/next edge normals (i.e. points towards the
00503                 // mitre intersection), but it has a non-unit-length, so as to stretch the
00504                 // outline out to the mitre point.
00505 
00506                 // In this case, we don't bother to do anything yet, as we'll calculate
00507                 // the next normal on the next pass, so we just flag the case and let
00508                 // the next pass come back and fix up our normal once it has all the facts.
00509                 CompletingAMitre = TRUE;
00510 
00511                 // NOTE that we leave PrevNormal containing the previous normal - we'll need it
00512                 // on the next pass.
00513             }
00514             else
00515             {
00516                 // The completing trap of any join simply uses the normal of the "next" edge,
00517                 // so we simply calculate this for all cases.
00518                 if (Index >= Used-1)
00519                 {
00520                     // At the end of the curve - we can just copy the normal from the first point
00521                     pEdge->Normal = pEdges[0].Normal;
00522                 }
00523                 else
00524                 {
00525                     // Inside the path - just use the next edge to generate a normal
00526                     pEdge->Normal.SetNormalToLine(pEdge->Centre, pNextEdge->Centre);
00527                 }
00528 
00529                 // However, now we must check if we've just completed a mitred join, in
00530                 // which case we have to go back to fill in the previous edge normal
00531                 if (CompletingAMitre)
00532                 {
00533                     // Find the vector pointing towards the mitre point
00534                     pPrevEdge->Normal.Average(PrevNormal, pEdge->Normal);
00535 
00536                     // We now know the direction the mitre intersection (pointy bit) lies in.
00537                     // Now, we will "stretch" that normal by the ratio of the distance to
00538                     // the intersection over the stroke width. 
00539                     // We pass in the point before the join, the join centre, and the point
00540                     // after the join. (Note that the join centre point occupies 3 edge entries)
00541                     double MitreChordRatio = 1.0;
00542                     pProcessor->CalculateMitreIntersection( &pEdges[Index-3].Centre,
00543                                                             &pEdge->Centre,
00544                                                             &pNextEdge->Centre,
00545                                                     /*TO*/  &MitreChordRatio);
00546 
00547                     pPrevEdge->Normal.x *= MitreChordRatio;
00548                     pPrevEdge->Normal.y *= MitreChordRatio;
00549                 }
00550 
00551                 CompletingAMitre = FALSE;       // Clear the mitre flag
00552                 PrevNormal = pEdge->Normal;     // Remember this edge's normal for the next pass
00553             }
00554         }
00555         else
00556         {
00557             // This isn't the first point. It also is not "inside" a joint (although it could be
00558             // the last edge preceeding a join)
00559             if (Index >= Used-1)
00560             {
00561                 // Last point - the normal is merely at right-angles to last line, so = (lineY,-lineX)
00562                 // NOTE that this is not true if this is a closed path, but we will fix that after
00563                 // the loop if it needs fixing
00564                 pEdge->Normal = PrevNormal;
00565             }
00566             else
00567             {
00568                 // Find normal to next line
00569                 NormCoord NextNormal(pEdge->Centre.y - pEdges[Index+1].Centre.y, -(pEdge->Centre.x - pEdges[Index+1].Centre.x));
00570 
00571                 if (NextNormal.x == 0.0 && NextNormal.y == 0.0)
00572                 {
00573                     // This point and the next point are coincident, so we must have hit a cusp join
00574                     // (or rampant discontinuity) The normal is simply perpendicular to the last line
00575                     pEdge->Normal = PrevNormal;
00576                 }
00577                 else
00578                 {
00579                     // This point lies between 2 flattened source path segments, so we simply average
00580                     // their normals to get the path normal at the point.
00581                     NextNormal.Normalise();
00582                     pEdge->Normal.Average(PrevNormal, NextNormal);
00583 
00584                     PrevNormal = NextNormal;        // Remember this normal for the next pass
00585                 }
00586             }
00587         }
00588     }
00589 
00590     return(TRUE);
00591 }
00592 
00593 
00594 
00595 /******************************************************************************************
00596 
00597 >   BOOL TrapEdgeList::ProcessEdgePositions(TrapTravelType TravelType)
00598 
00599     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00600     Created:    30/12/96
00601 
00602     Inputs:     TravelType  - Describes how to record "positions" along the path:
00603                                 TrapTravel_None         Don't record travel (FASTEST)
00604                                 TrapTravel_Parametric   0.0 to 1.0 parametric range
00605                                 TrapTravel_Millipoint   Absolute millipoints distance
00606 
00607     Outputs:    On succesful exit, the member array of TrapEdge structures will contain
00608                 properly calculated Position values.
00609 
00610     Returns:    FALSE if it failed (Note: does not set an error at this level)
00611 
00612     Purpose:    Scans the recorded list of edges (added by AddEdge) and fills in the
00613                 position information according to TravelType. If you don't need Position
00614                 values, then don't call this function (which will leave Postions totally
00615                 uninitialised)
00616 
00617     Notes:      TrapTravel_None will leave edge "Position" values TOTALLY UNINITIALISED
00618 
00619                 Call this AFTER calling ProcessEdgeNormals, as it relies on the normals
00620                 calculated in that function to generate position values.
00621 
00622 ******************************************************************************************/
00623 
00624 BOOL TrapEdgeList::ProcessEdgePositions(TrapTravelType TravelType)
00625 {
00626     // We don't need to do anything unless the user wants parametric positions
00627     if (TravelType != TrapTravel_Parametric || Used < 1)
00628         return(TRUE);
00629 
00630     // --- Record the maximum position value as the millipoint length of the path
00631     // Note: When repeating, we record the length of the repeat rather than the
00632     // actual path length, as this makes the final repeat partial rather than squashed
00633     // when it doesn't all fit in.
00634     if (pParentList != NULL && pParentList->GetRepeatLength() > 0)
00635         PathLength = (double) pParentList->GetRepeatLength();
00636     else
00637         PathLength = pEdges[Used-1].Position;
00638     ERROR2IF(PathLength <= 0.0, FALSE, "No 'travel' in Stroke");
00639 
00640     // --- Normalise the Travel values to generate Position values in range 0.0 to 1.0
00641     // This is done by simply dividing them all by the position of the final edge
00642     if (PathLength > 0.0)
00643     {
00644         double R = 1.0/PathLength;
00645         for (UINT32 Index = 0; Index < Used; Index++)
00646             pEdges[Index].Position *= R;
00647     }
00648 
00649     return(TRUE);
00650 }
00651 
00652 
00653 
00654 
00655 
00656 
00657 
00658 
00659 
00661 // TrapsList class
00663 
00664 
00665 
00666 /******************************************************************************************
00667 
00668 >   TrapsList::TrapsList(INT32 Repeat = 0)
00669 
00670     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00671     Created:    30/12/96
00672 
00673     Inputs:     Repeat  - 0 to generate a single stroke (TrapEdgeList) per subpath, which
00674                           will cause the stroke to be stretched along the entire path, or...
00675 
00676                           a millipoint repeat distance (which will cause many TrapEdgelists
00677                           to be generated), such that the stroke repeats along the path.
00678 
00679     Purpose:    Constructor
00680 
00681 ******************************************************************************************/
00682 
00683 TrapsList::TrapsList(INT32 Repeat)
00684 {
00685     Used         = 0;
00686     CurrentSize  = 0;
00687     pTraps       = NULL;
00688     RepeatLength = Repeat;
00689 }
00690 
00691 
00692 
00693 /******************************************************************************************
00694 
00695 >   TrapsList::~TrapsList()
00696 
00697     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00698     Created:    30/12/96
00699 
00700     Purpose:    Destructor
00701 
00702 ******************************************************************************************/
00703 
00704 TrapsList::~TrapsList()
00705 {
00706     if (pTraps != NULL)
00707     {
00708         for (UINT32 Index = 0; Index < Used; Index++)
00709             delete pTraps[Index];
00710 
00711         CCFree(pTraps);
00712     }
00713 }
00714 
00715 
00716 
00717 /******************************************************************************************
00718 
00719 >   TrapEdgeList *TrapsList::AddEdgeList(void)
00720 
00721     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00722     Created:    30/12/96
00723 
00724     Returns:    NULL if it failed to allocate memory, else
00725                 a pointer to a new TrapEdgeList object for you to use
00726                 
00727     Purpose:    Add a new TrapEdgeList object to this list
00728 
00729 ******************************************************************************************/
00730 
00731 TrapEdgeList *TrapsList::AddEdgeList(void)
00732 {
00733     if (Used >= CurrentSize)        // If we've run out of room in the array
00734     {
00735         if (!ExpandArray())         // Try to allocate more
00736             return(NULL);           // And return NULl if we failed
00737     }
00738 
00739     TrapEdgeList *pObject = new TrapEdgeList(this);
00740     if (pObject == NULL)
00741         return(NULL);
00742 
00743     pTraps[Used] = pObject;
00744     Used++;
00745 
00746     return(pObject);
00747 }
00748 
00749 
00750 
00751 /******************************************************************************************
00752 
00753 >   BOOL TrapsList::ExpandArray(void)
00754 
00755     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00756     Created:    30/12/96
00757 
00758     Outputs:    On succesful exit, the member array of TrapEdgeList objects will be bigger
00759     Returns:    FALSE if it failed to allocate memory
00760                 
00761     Purpose:    (Internal method)
00762                 Expands the storage structure of the list to allow more entries to be
00763                 used. Called automatically by AddEdgeList if it needs more edges.
00764 
00765     Notes:      Internal storage is an array of TrapEdgeList _pointers_
00766                 To use an item, you must therefore 
00767 
00768 ******************************************************************************************/
00769 
00770 BOOL TrapsList::ExpandArray(void)
00771 {
00772     // Work out how many entries to allocate.
00773     // Each item is small, so we allocate a reasonably large number
00774     // Note that potential implementation of dashed lines could create a lot of 
00775     // TrapEdgeLists (one per dash), so we need lots of spare capacity to cope
00776     // with this situation. I've therefore erred on the generous side.
00777     const INT32 AllocSize = CurrentSize + 64;
00778 
00779     if (pTraps == NULL)
00780     {
00781         // We have no array yet, so allocate one
00782         pTraps = (TrapEdgeList **) CCMalloc(AllocSize * sizeof(TrapEdgeList *));
00783         if (pTraps == NULL)
00784             return(FALSE);
00785     }
00786     else
00787     {
00788         // We have an array - we must make it larger
00789         TrapEdgeList **pNewBuf = (TrapEdgeList **) CCRealloc(pTraps, AllocSize * sizeof(TrapEdgeList *));
00790         if (pNewBuf == NULL)
00791             return(FALSE);
00792 
00793         pTraps = pNewBuf;
00794     }
00795 
00796     // Success. Update the current size value
00797     CurrentSize = AllocSize;
00798 
00799     // Initialise the pointers to safe values
00800     for (UINT32 Index = Used; Index < CurrentSize; Index++)
00801         pTraps[Index] = NULL;
00802 
00803     return(TRUE);
00804 }
00805 
00806 
00807 
00808 /******************************************************************************************
00809 
00810 >   BOOL TrapsList::PostProcessLists(ProcessPathToTrapezoids *pProcessor,
00811                                     TrapTravelType TravelType)
00812 
00813     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00814     Created:    30/12/96
00815 
00816     Inputs:     pProcessor  - The object which is calling us
00817                 TravelType  - Describes how to record "positions" along the path:
00818                                 TrapTravel_None         Don't record travel (FASTEST)
00819                                 TrapTravel_Parametric   0.0 to 1.0 parametric range
00820                                 TrapTravel_Millipoint   Absolute millipoints distance
00821 
00822 
00823     Outputs:    On succesful exit, the member TrapEdgeList objects will be complete
00824 
00825     Returns:    FALSE if it failed to complete the operation
00826                 (Note: No error is set at this level)
00827 
00828     Purpose:    Scans the recorded list of TrapEdgeLists and fills in the
00829                 remaining information (normals and position values) for all
00830                 trapezoids therein.
00831 
00832                 MUST be called on the list before trying to use the edge structures!
00833 
00834     Notes:      TrapTravel_None will leave edge "Position" values TOTALLY UNINITIALISED
00835 
00836 ******************************************************************************************/
00837 
00838 BOOL TrapsList::PostProcessLists(ProcessPathToTrapezoids *pProcessor,
00839                                     TrapTravelType TravelType)
00840 {
00841 // There is a problem with fixed repeating vector brushes sometimes doing a bit
00842 // more than they should, in other words, a 6 repeat brush starting on the 7th
00843 // repeat, or at least adding 1 trap to the list for it...
00844 // The commented out stuff below was the last ever bit of code written by Jason.
00845 // It was intended as a fix, but I (Richard) never got round to getting it compiling
00846 // let alone testing it. Since the stroking job is now no more, and also since this
00847 // comment is still here, I suspect the problem still remains. To see it in action
00848 // you need to draw a wibbly filled freehand shape and pop the gallery up. The
00849 // symptoms are a bizarre line to the right of the preview. It's tricky to get a
00850 // repeating case...
00851 // Jason, if you decided to come back, good on you mate... If not, may I wish
00852 // whoever is reading this all the best with the stroking system - blimmin
00853 // fabby, innit ?
00854 /*  BOOL DiscardPartialRepeats = TRUE;
00855 
00856     if (RepeatDist > 0 && DiscardPartialRepeats && Used > 1)
00857     {
00858         TrapEdge *pEdge = pTraps[Used-1]->GetLastTrapEdge();
00859         if (pEdge != NULL && pEdge->Position < RepeatDist/2)
00860         {
00861             delete pTraps[Used-1];
00862             pTraps[Used-1] = NULL;
00863             Used--;
00864         }
00865     }*/
00866 
00867     for (UINT32 Index = 0; Index < Used; Index++)
00868     {
00869         if (pTraps[Index] != NULL)
00870         {
00871             if (!pTraps[Index]->ProcessEdgeNormals(pProcessor))
00872                 return(FALSE);
00873 
00874             if (!pTraps[Index]->ProcessEdgePositions(TravelType))
00875                 return(FALSE);
00876         }
00877     }
00878 
00879     return(TRUE);
00880 }
00881 
00882 
00883 
00884 
00885 
00886 
00887 
00888 
00889 
00890 
00891 
00892 
00893 
00895 // ProcessPathToTrapezoids  class
00897 
00898 
00899 
00900 /******************************************************************************************
00901 
00902 >   ProcessPathToTrapezoids::ProcessPathToTrapezoids(const double flat, TrapsList *pOutputList)
00903 
00904     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00905     Created:    30/12/96
00906 
00907     Inputs:     flat        - Flatness value to use (see PathProcessor class)
00908 
00909     Purpose:    The One True Constructor. Don't call any other graven constructors.
00910 
00911 ******************************************************************************************/
00912 
00913 ProcessPathToTrapezoids::ProcessPathToTrapezoids(const double flat)
00914                         : ProcessPath(flat)
00915 {
00916     pTraps           = NULL;
00917     PointFollowsJoin = FALSE;
00918     JoinType         = RoundJoin;
00919 }
00920 
00921 
00922 
00923 /******************************************************************************************
00924 
00925 >   virtual BOOL ProcessPathToTrapezoids::Init(Path* pSource, TrapsList *pOutputList)
00926 
00927     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00928     Created:    30/12/96
00929 
00930     Inputs:     pSource     - The path for which you want to build trapezoid lists
00931 
00932                 pOutputList - A pointer to the trapezoid list to be filled in when you
00933                               invoke the Process() function.
00934                               Note that all generated trapezoid edge lists are APPENDED
00935                               to this TrapsList, rather than overwriting it.
00936                               
00937                 JoinStyle   - RoundJoin, MitredJoin, or BevelledJoin
00938 
00939     Purpose:    Initialises the PathProcessor in preparation for processing
00940 
00941     Notes:      Call this version of Init, not the base class one!
00942 
00943 ******************************************************************************************/
00944 
00945 BOOL ProcessPathToTrapezoids::Init(Path* pSource, TrapsList *pOutputList)
00946 {
00947     ERROR3IF(pOutputList == NULL, "Illegal NULL param");
00948     pTraps = pOutputList;
00949 
00950     // And init the base class
00951     return(ProcessPath::Init(pSource));
00952 }
00953 
00954 
00955 
00956 /******************************************************************************************
00957 
00958 >   virtual BOOL ProcessPathToTrapezoids::Process(const ProcessFlags &PFlags,
00959                                                     TrapTravelType TravelType,
00960                                                     JointType JoinStyle = RoundJoin)
00961 
00962     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00963     Created:    30/12/96
00964 
00965     Inputs:     PFlags      - the ProcessPath flags indicating how to process the path
00966                                 (Note that some flags should not be used with this process)
00967 
00968                 TravelType  - Describes how to record "positions" along the path:
00969                                 TrapTravel_None         Don't record travel (FASTEST)
00970                                 TrapTravel_Parametric   0.0 to 1.0 parametric range
00971                                 TrapTravel_Millipoint   Absolute millipoints distance
00972 
00973                 JoinStyle   - The type of joins in this path.
00974                               (RoundJoin, MitredJoin, or BevelledJoin)
00975 
00976     Purpose:    Processes the path given in Init() to produce a trapezoid list in the
00977                 TrapsList given to Init().
00978 
00979     Notes:      * Call this version of Process, not the base class one!
00980 
00981                 * To process a path, you should write code like this:
00982 
00983                     ProcessPathToTrapezoids GenerateTraps(64);
00984                     TrapsList OutputTraps;
00985                     if (GenerateTraps.Init(pPath, &OutputTraps))
00986                     {
00987                         // Flags are: Flatten, QuantiseLines, QuantiseAll
00988                         ProcessFlags PFlags(TRUE, FALSE, FALSE);
00989                         if (!GenerateTraps.Process(PFlags, TrapTravel_Parametric, JoinStyle))
00990                             return;
00991                     }
00992 
00993                 * TrapTravel_None will leave edge "Position" values TOTALLY UNINITIALISED
00994                   because to initialise them will waste time if you're not going to use 'em
00995 
00996 ******************************************************************************************/
00997 
00998 BOOL ProcessPathToTrapezoids::Process(const ProcessFlags &PFlags,
00999                                         TrapTravelType TravelType, JointType JoinStyle)
01000 {
01001     ERROR2IF(pTraps == NULL, FALSE, "Call Init to initialise the ProcessPathToTrapezoids object first!");
01002 
01003     JoinType         = JoinStyle;
01004     PointFollowsJoin = FALSE;
01005     LastPoint        = DocCoord(0,0);
01006 
01007     BOOL ok = ProcessPath::Process(PFlags);
01008 
01009     if (ok)
01010         ok = pTraps->PostProcessLists(this, TravelType);
01011 
01012     return(ok);
01013 }
01014 
01015 
01016 
01017 /******************************************************************************************
01018 
01019 >   virtual BOOL ProcessPathToTrapezoids::CloseElement(BOOL done, PathVerb Verb, INT32 Index)
01020 
01021     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01022     Created:    14/1/97
01023     Inputs:     done    = true if the NewPoint function procesed all new points in the
01024                           open element correctly,
01025                           false if it did not.
01026                 Verb    = verb of closing element.
01027                 index   = index of closing element.
01028     Outputs:
01029     Returns:    FALSE to continue processing the next element
01030                 TRUE to stop processing and return done
01031 
01032     Purpose:    Processes the close of LINETO and BEZIERTO elements to allow us
01033                 to correctly handle joins in the path.
01034 
01035 ******************************************************************************************/
01036 
01037 BOOL ProcessPathToTrapezoids::CloseElement(BOOL done, PathVerb Verb, INT32 Index)
01038 {
01039     // If it's the end of a line/bezier, then it's a proper join. In this case,
01040     // we set a flag so we will know that the next point added follows a join.
01041     // For some join types we need to know which direction the path takes after
01042     // the join in order to determine what to output.
01043     if (Verb == PT_LINETO || Verb == PT_BEZIERTO)
01044         PointFollowsJoin = TRUE;
01045 
01046     return(FALSE);      // continue processing - all is well
01047 }
01048 
01049 
01050 
01051 /******************************************************************************************
01052 
01053 >   virtual void ProcessPathToTrapezoids::CloseFigure(void)
01054 
01055     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01056     Created:    16/1/97
01057 
01058     Purpose:    Derived-class interface.
01059                 Called after closing a LINETO or BEZIERTO element which constitutes
01060                 the end of this figure (subpath) (as indicated by this element having
01061                 the PT_CLOSEFIGURE flag set).
01062 
01063                 Used by the path stroking ProcessPathToTrapezoids class to allow it
01064                 to correctly handle joining the start & end of a closed figure.
01065 
01066 ******************************************************************************************/
01067 
01068 void ProcessPathToTrapezoids::CloseFigure(void)
01069 {
01070     // Find this subpath's edge list
01071     TrapEdgeList *pEdgeList = pTraps->GetLastTrapEdgeList();
01072     if (pEdgeList == NULL)
01073         return;
01074 
01075     // Find the first and last TrapEdges for this subpath. If either is NULL
01076     // or if they are the same, then we don't have enough trapezoids to bake
01077     // a cake, so we throw in the towel.
01078     TrapEdge *pFirstEdge = pEdgeList->GetTrapEdge(0);
01079     TrapEdge *pLastEdge  = pEdgeList->GetLastTrapEdge();
01080     if (pFirstEdge == NULL || pLastEdge == NULL || pFirstEdge == pLastEdge)
01081         return;
01082 
01083     // Now, compare the edge points. If they are coincident, we must add a join
01084     // to complete the shape, but if they are not, we leave the path unclosed
01085     if (pFirstEdge->Centre == pLastEdge->Centre)
01086     {
01087         PointFollowsJoin = TRUE;
01088         NewPoint(PT_LINETO, NULL);
01089     }
01090 }
01091 
01092 
01093 
01094 /******************************************************************************************
01095 
01096 >   BOOL ProcessPathToTrapezoids::NewPoint(PathVerb Verb, DocCoord *pCoord)
01097 
01098     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01099     Created:    30/12/96
01100 
01101     Inputs:     Verb        - Descriptor indicating the type of point to add
01102                 pCoord      - The coordinate of the point (if NULL, this will
01103                               add a closing join to the trapezoid list)
01104 
01105     Outputs:    The new point will have been appended to the member trapezoid list
01106                 in an appropriate manner. If preceeded by a bezier knot (join), 
01107                 appropriate trapezoidal elements will have been added to represent
01108                 the join.
01109 
01110     Returns:    TRUE if it wishes to continue processing the path
01111                 (FALSE if it's run out of memory)
01112 
01113     Purpose:    Constructor
01114 
01115 ******************************************************************************************/
01116 
01117 BOOL ProcessPathToTrapezoids::NewPoint(PathVerb Verb, DocCoord *pCoord)
01118 {
01119     // Process any join we've just gone past. We have to delay processing of joins
01120     // until we know which way the curve headed off after the join, which is why
01121     // we are processing it now. If the new point is a MOVETO, then there is no join
01122     if (PointFollowsJoin && Verb == PT_LINETO)
01123     {
01124         // Find the last edge in the current edge list. If we can't find one, there is
01125         // nothing to "join to"
01126         TrapEdgeList *pEdgeList = pTraps->GetLastTrapEdgeList();
01127         TrapEdge *pEdge = NULL;
01128         if (pEdgeList != NULL)
01129             pEdge = pEdgeList->GetLastTrapEdge();
01130 
01131         if (pEdge != NULL)
01132         {
01133             switch(JoinType)
01134             {
01135                 case MitreJoin:
01136                     // A mitred join involves extending the previous and next segments of the outline
01137                     // to their intersection. If there is no intersection, or if the intersection
01138                     // is beyond the "MitreLimit", then we revert to a simple Bevelled join.
01139                     // Otherwise, we need to add 2 line segments (3 trapezoid edges) joining the
01140                     // end of the last segment to the intersection, and then from the intersection
01141                     // to the start of this new segment.
01142                     // These new segments are marked as being part of a join so that they will be
01143                     // stroked as straight line segments rather than interpolating normals to make
01144                     // a smoothed/curved join.
01145 
01146                     {
01147                         ERROR3IF(pEdgeList->GetNumEdges() < 2, "Not enough traps for mitred join");
01148                         TrapEdge *pPrevEdge = pEdgeList->GetTrapEdge(pEdgeList->GetNumEdges() - 2);
01149 
01150                         BOOL Mitred;
01151                         if (pCoord != NULL)
01152                         {
01153                             Mitred = CalculateMitreIntersection(&pPrevEdge->Centre, &pEdge->Centre, pCoord);
01154                         }
01155                         else
01156                         {
01157                             // No pCoord passed in, so this is the join at the end. Use the 1st point
01158                             // in the subpath as the "next point". (Well, the 2nd in the array, because point
01159                             // 1 is coincident! We use point 2 which is the end of the 1st line)
01160                             DocCoord NextCoord = pEdgeList->GetTrapEdge(1)->Centre;
01161                             Mitred = CalculateMitreIntersection(&pPrevEdge->Centre, &pEdge->Centre, &NextCoord);
01162                         }
01163 
01164                         //BLOCK
01165                         {
01166                             // Nasty bodge - Because AddEdge can re-alloc the array, we CANNOT keep pointers
01167                             // to array entries around over calls to AddEdge. We thus copy the edge point
01168                             // into a temporary variable which we can safely use over the 2 calls the AddEdge
01169                             DocCoord Temp = pEdge->Centre;
01170 
01171                             // Add a single point for this join - by default this gives a Bevelled join
01172                             pEdgeList->AddEdge(&Temp, TrapJoin_MitredOrBevelled);
01173 
01174                             // And if it's Mitred, then add another point for this join
01175                             if (Mitred)
01176                                 pEdgeList->AddEdge(&Temp, TrapJoin_MitredOrBevelled);
01177                         }
01178                     }
01179                     break;
01180 
01181                 case RoundJoin:
01182                     // To make a rounded join, you might think we need to output a number of trapezoids,
01183                     // but in fact the recursive flattened-mapping algorithm employed by the path
01184                     // stroker will "smooth" a single trapezoid into a proper round join!
01185                     // Thus, we simply insert another trapezoid on the join point (but do NOT mark
01186                     // it as "part of a join") so that it will be mapped as a round join.
01187                     pEdgeList->AddEdge(&pEdge->Centre, TrapJoin_Round);
01188                     break;
01189 
01190                 case BevelledJoin:
01191                     // To make a bevelled join, we simply add another TrapEdge on the join point for
01192                     // the start of the next segment, which will be joined with a straight line. 
01193                     // However, the stroking mechanism will actually output that "line" as a round
01194                     // join (due to its recursive flattened-mapping algorithm), so we have to
01195                     // mark this point as "part of a join" so that it simply plonks down the
01196                     // straight segment we want! (Hence the TRUE)
01197                     pEdgeList->AddEdge(&pEdge->Centre, TrapJoin_MitredOrBevelled);
01198                     break;
01199 
01200                 default:
01201                     ERROR3("Unsupported join type in ProcessPathToTrapezoids");
01202                     break;
01203             }           
01204         }
01205     }
01206 
01207     // Clear the join flag, as any pending join has been processed
01208     PointFollowsJoin = FALSE;
01209 
01210     // If the provided coordinate was NULL, then they only wanted to add the join
01211     // information, so we exit now
01212     if (pCoord == NULL)
01213         return(TRUE);
01214 
01215     // Add the new point to the current trapezoid list
01216     switch(Verb)
01217     {
01218         case PT_MOVETO:
01219             {
01220                 // A MoveTo signifies the start of a new (sub)path, so we start a new TrapList
01221                 TrapEdgeList *pEdgeList = pTraps->AddEdgeList();
01222                 if (pEdgeList == NULL)
01223                     return(FALSE);
01224 
01225                 pEdgeList->AddEdge(pCoord);
01226                 LastPoint = *pCoord;
01227             }
01228             break;
01229 
01230 
01231         case PT_LINETO:
01232             {
01233                 // Append each new point as a new trapezoid edge in the current TrapList
01234                 // Find the last TrapEdgeList
01235                 TrapEdgeList *pEdgeList = pTraps->GetLastTrapEdgeList();
01236 
01237                 if (pEdgeList == NULL)
01238                 {
01239                     // We have started a path with a LineTo!
01240                     // Create a new Trapezoid List (imply that this point is really a MOVETO)
01241                     ERROR3("LINETO added to traplist with no preceding MOVETO");
01242 
01243                     LastPoint = DocCoord(0,0);
01244                     pEdgeList = pTraps->AddEdgeList();
01245                     if (pEdgeList == NULL)
01246                         return(FALSE);
01247                 }
01248 
01249                 // Append the new point to the current trap list. Check first to eliminate
01250                 // coincident points (places where the source path has coincident points),
01251                 // as we only want coincident points to occur in the special case of joins.
01252                 if (pEdgeList->GetNumEdges() < 1 || LastPoint != *pCoord)
01253                 {
01254                     pEdgeList->AddEdge(pCoord);
01255                     LastPoint = *pCoord;
01256                 }
01257             }
01258             break;
01259 
01260         default:
01261             ERROR3("ProcessPathToTrapezoids only handles MOVETO and LINETO!");
01262             break;
01263     }
01264 
01265     return(TRUE);
01266 }
01267 
01268 
01269 
01270 
01271 /******************************************************************************************
01272 
01273 >   BOOL ProcessPathToTrapezoids::CalculateMitreIntersection(DocCoord *p1, DocCoord *p2, DocCoord *p3,
01274                                                              double *pMitreRatio = NULL)
01275 
01276     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01277     Created:    17/1/97
01278 
01279     Inputs:     p1  - Centreline point prior to the join (see the diagram below)
01280                 p2  - Centreline point where the join occurs
01281                 p3  - Centreline point subsequent to the join
01282 
01283     Outputs:    pMitreRatio - if non-NULL, and if the return value is TRUE, this will
01284                               be returned with the distance-ratio in it (see below)
01285 
01286     Returns:    TRUE    - if the join should be mitred, and the pIntersectionDist output contains
01287                           the mitre chord ratio.
01288                 FALSE   - if the join should be bevelled. The pIntersectionDist param will be
01289                           untouched in this case.
01290 
01291     Purpose:    Mitred joins revert to bevelled joins when are so tight that the mitre
01292                 exceeds the "mitre limit". This function determines and returns whether
01293                 a given join should be rendered mitred or bevelled. In the former case,
01294                 it also provides an output, which is based upon the distance from the
01295                 center of the join (point 2, below) to the mitre outline intersection
01296                 ("*" below). The distance is not an absolute length, but rather, the
01297                 ratio of that length to the line width.
01298 
01299                 i.e. if you take point 2, and add it's "normal" (the vector toward the mitre
01300                 point) and multiply it by Width*pIntersectionRatio, you'll arrive at the
01301                 mitre intersection point. This may sound like an odd number to return,
01302                 but in fact it is precisely the number I need.
01303 
01304                         +--------:-----*
01305                                       /         1,2,3 are the inputs p1, p2, p3
01306                         1--------2   /
01307                                 /   :           ':' are the points where stroke outline meets join
01308                         ---    /   /
01309                          /    /   /             * is the pIntersection output
01310                         /    3   +
01311 
01312 ******************************************************************************************/
01313 
01314 BOOL ProcessPathToTrapezoids::CalculateMitreIntersection(DocCoord *p1, DocCoord *p2, DocCoord *p3,
01315                                                             double *pMitreRatio)
01316 {
01317     // Camelot never seems to actually set the MitreLimit in any render regions, so I have
01318     // taken on a default value which is the same as GDraw uses (GDraw uses 10.0).
01319     // Anyway, Gavin's MitreLimit is sensibly based on the angle/width ratio, whereas
01320     // our attributes are a random value in millipoints, which seems daft to me.
01321     const double MitreLimit = 1.0 / 10.0;   // (10.0 plus a bit of precalculation)
01322 
01323     // Work out the normalised direction vectors for the incoming and outgoing lines
01324     NormCoord v1(p1->x - p2->x, p1->y - p2->y);
01325     v1.Normalise();
01326 
01327     NormCoord v2(p3->x - p2->x, p3->y - p2->y);
01328     v2.Normalise();
01329 
01330     // From these vectors, we can now calculate cos(theta) using a dot product 
01331     // (where theta is the angle between the vectors), and taking the arc-cosine
01332     // of that gives us a huge surprise as a theta drops out.
01333     const double Theta = acos(v1.DotProduct(v2));
01334     const double SinHalfTheta = sin(Theta / 2.0);
01335 
01336     // If the angle is too tight, then we must bevel this (this also stops a divide by zero)
01337     if (fabs(SinHalfTheta) < MitreLimit)
01338         return(FALSE);
01339 
01340     if (pMitreRatio != NULL)
01341         *pMitreRatio = 1.0 / SinHalfTheta;
01342 
01343     return(TRUE);
01344 }
01345 

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