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