00001 // $Id: ppstroke.cpp 1502 2006-07-24 11:51:13Z 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 // pathpcs - Definition of rendering PathProcessor class 00099 00100 #include "camtypes.h" 00101 00102 #include "ppstroke.h" 00103 00104 //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00105 //#include "fillval.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00106 //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00107 #include "pathstrk.h" 00108 #include "pathtrap.h" 00109 #include "qualattr.h" 00110 //#include "rndrgn.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00111 #include "strkattr.h" 00112 #include "valfunc.h" 00113 #include "nodetxtl.h" // for the format region 00114 //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00115 #include "attrmap.h" 00116 #include "lineattr.h" 00117 //#include "fillattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00118 #include "brshattr.h" 00119 #include "ai_epsrr.h" // for definition of AIEPSRenderRegion. Only used in ProcessStroke() 00120 00121 #include "ophist.h" 00122 #include "combshps.h" 00123 00124 CC_IMPLEMENT_DYNAMIC(PathProcessorStroke, PathProcessor); 00125 00126 // Declare smart memory handling in Debug builds 00127 #define new CAM_DEBUG_NEW 00128 00129 00130 00131 /******************************************************************************************** 00132 00133 > PathProcessorStroke::PathProcessorStroke() 00134 00135 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00136 Created: 31/12/96 00137 00138 Purpose: Constructor 00139 00140 ********************************************************************************************/ 00141 00142 PathProcessorStroke::PathProcessorStroke() 00143 { 00144 } 00145 00146 00147 00148 /******************************************************************************************** 00149 00150 > virtual BOOL PathProcessorStroke::WillChangeFillAndStrokeSeparately(void) 00151 00152 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00153 Created: 31/12/96 00154 00155 Returns: TRUE 00156 00157 Purpose: Called by the RenderRegion to determine if this PathProcessorStroke affects 00158 the "fill" and "stroke" portions of the path separately. (Generally 00159 speaking, only fill/stroke providers will cause these two different 00160 "bits" of a path to be rendered separately). This call is made BEFORE 00161 this Processor's ProcessPath function will be called. 00162 00163 If the caller gets a TRUE back, the stroke and fill paths will be 00164 rendered separately. 00165 00166 Notes: Base class implementation returns FALSE. Derived Stroke and Fill 00167 processors (such as this one) override this method to return TRUE. 00168 00169 ********************************************************************************************/ 00170 00171 BOOL PathProcessorStroke::WillChangeFillAndStrokeSeparately(void) 00172 { 00173 return(TRUE); 00174 } 00175 00176 00177 00178 /******************************************************************************************** 00179 00180 > virtual void PathProcessorStroke::ProcessPath(Path *pPath, 00181 RenderRegion *pRender, 00182 PathShape ShapePath = PATHSHAPE_PATH); 00183 00184 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00185 Created: 31/12/96 00186 00187 Purpose: Called by the RenderRegion to apply the path processing operation to 00188 the given path. 00189 00190 The PathProcessorStroke class changes the stroking (only) of paths 00191 passed into it. 00192 00193 Notes: * When rendering a path, always pass in your 'this' pointer to 00194 RenderRegion::DrawPath, so that you don't start an infinite 00195 recursion! 00196 00197 * To stop rendering of the path, simply return without calling the RR 00198 00199 * To render this path unchanged, simply call directly back to the RR: 00200 pRender->DrawPath(pPath, this); 00201 00202 * Only affect the fill of this path if pPath->IsFilled 00203 00204 * Only affect the stroke of this path if pPath->IsStroked 00205 00206 * If converting a path into a "filled path" for stroking, the output 00207 path should be rendered with IsStroked=FALSE or it'll get a line 00208 around the outside! 00209 00210 ********************************************************************************************/ 00211 00212 void PathProcessorStroke::ProcessPath(Path *pPath, 00213 RenderRegion *pRender, 00214 PathShape ShapePath) 00215 { 00216 TRACEUSER( "Diccon", _T("Rendering stroke\n")); 00217 ERROR3IF(pPath == NULL || pRender == NULL, "Illegal NULL Params"); 00218 00219 // --- If the provided path is not stroked, then we'll just pass it straight through 00220 // We also don't touch it if we're doing EOR rendering 00221 if (!pPath->IsStroked || pRender->DrawingMode != DM_COPYPEN) 00222 { 00223 pRender->DrawPath(pPath, this, ShapePath); 00224 return; 00225 } 00226 00227 // --- If the quality is set low, strokes are just rendered as centrelines 00228 // BLOCK 00229 { 00230 QualityAttribute *pQuality = (QualityAttribute *) pRender->GetCurrentAttribute(ATTR_QUALITY); 00231 if (pQuality != NULL && pQuality->QualityValue.GetLineQuality() != Quality::FullLine) 00232 { 00233 pRender->DrawPath(pPath, this, ShapePath); 00234 return; 00235 } 00236 } 00237 00238 // --- If the attribute which created us is not the current StrokeType attribute, then 00239 // we have been overridden by a different stroke type, so we do nothing. 00240 // BLOCK 00241 { 00242 StrokeTypeAttrValue *pTypeAttr = (StrokeTypeAttrValue *) pRender->GetCurrentAttribute(ATTR_STROKETYPE); 00243 if (pTypeAttr != NULL && pTypeAttr != GetParentAttr()) 00244 { 00245 pRender->DrawPath(pPath, this); 00246 return; 00247 } 00248 } 00249 00250 // --- We don't expect the input path to be stroked AND filled on entry 00251 ERROR3IF(pPath->IsFilled, "PathProcessor expected RenderRegion to handle IsFilled case"); 00252 00253 // --- Get the current line width, cap style, and join style from the render region 00254 // In case of failure, we initialise with suitable defaults 00255 INT32 LineWidth = 5000; 00256 00257 // BLOCK 00258 { 00259 LineWidthAttribute *pWidthAttr = (LineWidthAttribute *) pRender->GetCurrentAttribute(ATTR_LINEWIDTH); 00260 if (pWidthAttr != NULL) 00261 LineWidth = pWidthAttr->LineWidth; 00262 } 00263 00264 // --- Create a stroke outline by calling our helper function 00265 00266 // (ChrisG 4/11/00) - Use smoothed paths with AIExport. This is only done for the export, as the smooth 00267 // path creation function is about five times slower than the non-smoothed one. 00268 Path * pOutput = NULL; 00269 if (pRender->IsKindOf (CC_RUNTIME_CLASS (AIEPSRenderRegion))) 00270 { 00271 pOutput = CreateSmoothVarWidthStroke(pPath, pRender, LineWidth); 00272 } 00273 else 00274 { 00275 pOutput = CreateVarWidthStroke(pPath, pRender, LineWidth); 00276 } 00277 00278 if (pOutput == NULL) 00279 { 00280 pRender->DrawPath(pPath, this); 00281 return; 00282 } 00283 00284 // --- Finally, render the new stroke outline path in place of the one we were given 00285 pRender->SaveContext(); 00286 00287 // !!!!****ToDo - for now, strokes always render flat-filled with the stroke colour 00288 StrokeColourAttribute *pStrokeColour = (StrokeColourAttribute *) pRender->GetCurrentAttribute(ATTR_STROKECOLOUR); 00289 if (pStrokeColour != NULL) 00290 pRender->SetFillColour(pStrokeColour->Colour); 00291 00292 pRender->SetWindingRule(NonZeroWinding); 00293 pRender->DrawPath(pOutput, this); 00294 00295 pRender->RestoreContext(); 00296 00297 // --- And clean up 00298 delete pOutput; 00299 } 00300 00301 /******************************************************************************************** 00302 00303 > BOOL PathProcessorStroke::DoBecomeA(BecomeA* pBecomeA, Path* pPath, Node* pParent) 00304 00305 Author: Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 00306 Created: 17/3/2000 00307 Inputs: pBecomeA - the object that tells us what to become, and recieves the results 00308 pPath - the path that we are processing 00309 pParent- the node that this brush is applied to 00310 Outputs: 00311 Returns: TRUE if everything went ok, 00312 Purpose: To return the processed path to a BecomeA object. Does pretty much the same 00313 as process path but withot rendering anything. 00314 00315 Note: Because of the problems with bevelling and contouring this function will never 00316 be called as part of the normal DoBecomeA procedure. You have to specifically 00317 locate the AttrStrokeType and call its DoBecomeA directly if you wish to 00318 extract the paths from a stroke. This should be resolved somehow after 00319 the release. 00320 00321 25/10/2000 Extra note: We are now working out a mechanism which uses 00322 GetProcessedPath and GetOutlinePath, so this function may become obsolete 00323 ********************************************************************************************/ 00324 00325 BOOL PathProcessorStroke::DoBecomeA(BecomeA* pBecomeA, Path* pPath, Node* pParent) 00326 { 00327 ERROR2IF(pBecomeA == NULL, FALSE, "BecomeA pointer is NULL in PathProcessorStroke::DoBecomeA"); 00328 ERROR2IF(pParent == NULL, FALSE, "Parent is NULL in PathProcessorStroke::DoBecomeA"); 00329 ERROR2IF(pPath == NULL, FALSE, "Path is NULL in PathProcessorStroke::DoBecomeA"); 00330 00331 00332 // we need to make a new nodepath 00333 NodePath* pNewNode = GetSmoothProcessedPath(pPath, pParent); 00334 00335 if (pNewNode == FALSE) 00336 return FALSE; 00337 00338 BOOL Success = FALSE; 00339 // now what we do depends on what type of BecomeA we've got 00340 if (pBecomeA->BAPath()) 00341 { 00342 switch (pBecomeA->GetReason()) 00343 { 00344 case BECOMEA_REPLACE: 00345 { 00346 UndoableOperation* pUndoOp = pBecomeA->GetUndoOp(); 00347 ERROR2IF(pUndoOp == NULL, FALSE, "No op pointer in PathProcessorStroke::DoBecomeA"); 00348 // apply attributes to our new node 00349 // Get the attributes 00350 CCAttrMap AttribMap(30); 00351 if (!((NodeRenderableInk*)pParent)->FindAppliedAttributes(&AttribMap)) 00352 break; 00353 00354 // another bodge: this time if we have a stroked path that is not closed and 00355 // we have given it a stroke colour then we want this stroke colour to become 00356 // the fill colour of the new shape 00357 AttrStrokeColour * pColour = NULL; 00358 // if (!pParent->IsNodePath() && ((NodePath*)pParent)->InkPath.IsClosed()) 00359 { 00360 // look up the stroke colour attribute 00361 00362 //AttribMap.Lookup((void *)CC_RUNTIME_CLASS(AttrStrokeColour), 00363 // (void *&)pColour); 00364 ((NodeRenderableInk*)pParent)->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrStrokeColour), (NodeAttribute**)&pColour); 00365 // make a new flat fill attribute and apply this to the node 00366 if (pColour) 00367 { 00368 StrokeColourAttribute * pAttrVal = (StrokeColourAttribute *)pColour->GetAttributeValue(); 00369 00370 if (pAttrVal) 00371 { 00372 AttrFlatColourFill *pFill = NULL; 00373 // create and insert at first child 00374 ALLOC_WITH_FAIL(pFill, new AttrFlatColourFill(pNewNode, FIRSTCHILD), pUndoOp); 00375 00376 pFill->SetStartColour(&(pAttrVal->Colour)); 00377 // remove from the map so we don't duplicate 00378 AttribMap.RemoveKey(pFill->GetAttributeType()); 00379 } 00380 } 00381 00382 } 00383 00384 if (!pNewNode->ApplyAttributes(&AttribMap)) 00385 break; 00386 00387 // insert the new node and delete the original 00388 BOOL ok = TRUE; 00389 // If we don't own the parent node, we should insert after (on top of) it 00390 if (pBecomeA->IsSecondary()) 00391 ok = pUndoOp->DoInsertNewNode(pNewNode,pParent,NEXT,FALSE,FALSE,FALSE,FALSE); 00392 else 00393 { 00394 ok = pUndoOp->DoInsertNewNode(pNewNode,pParent,PREV,FALSE,FALSE,FALSE,FALSE); 00395 // If this BecomeA is a primary, then delete the source node 00396 if (ok) 00397 ok = pUndoOp->DoHideNode((NodeRenderableInk*)pParent, TRUE); 00398 } 00399 00400 if (!ok) 00401 break; 00402 00403 00404 if (!pBecomeA->IsCombineBecomeA()) 00405 { 00406 // pass it back then 00407 pBecomeA->PassBack(pNewNode,(NodeRenderableInk*)pParent); 00408 } 00409 else 00410 { 00411 CombineBecomeA* pPassback = (CombineBecomeA*) pBecomeA; 00412 00413 pPassback->SetStrokeCreatedPassbackPath(TRUE); 00414 pBecomeA->PassBack(pNewNode,(NodeRenderableInk*)pParent); 00415 pPassback->SetStrokeCreatedPassbackPath(FALSE); 00416 } 00417 Success = TRUE; 00418 } 00419 break; 00420 00421 case BECOMEA_PASSBACK : 00422 { 00423 if (!pBecomeA->IsCombineBecomeA ()) 00424 { 00425 // pass it back then 00426 Success = pBecomeA->PassBack(pNewNode,(NodeRenderableInk*)pParent); 00427 } 00428 else 00429 { 00430 CombineBecomeA* pPassback = (CombineBecomeA*) pBecomeA; 00431 00432 pPassback->SetStrokeCreatedPassbackPath(TRUE); 00433 Success = pBecomeA->PassBack(pNewNode,(NodeRenderableInk*)pParent); 00434 pPassback->SetStrokeCreatedPassbackPath(FALSE); 00435 } 00436 } 00437 break; 00438 00439 default: 00440 break; 00441 } 00442 } 00443 00444 00445 if (!Success) 00446 delete pNewNode; 00447 00448 return Success; 00449 } 00450 00451 00452 /******************************************************************************************** 00453 00454 > NodePath* PathProcessorStroke::GetProcessedPath(Path* pPath, Node* pParent, BOOL OutlineOnly = FALSE) 00455 00456 Author: Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 00457 Created: 17/3/2000 00458 Inputs: pPath - the path that we are processing 00459 pParent- the node that this brush is applied to 00460 Outputs: 00461 Returns: A NodePath, containing the path generated by our path stroker 00462 Purpose: To return the stroked path 00463 00464 Notes: 00465 ********************************************************************************************/ 00466 00467 NodePath* PathProcessorStroke::GetProcessedPath(Path* pPath, Node* pParent) 00468 { 00469 // we need to fake a renderregion as our helper functions need one, 00470 // luckily FormatRegion is on hand from the text stuff 00471 FormatRegion FakeRender; 00472 00473 if (!FakeRender.Init((NodeRenderableInk*)pParent)) // init renders all applied attributes into the region 00474 return NULL; 00475 00476 00477 // From here copied from ProcessPath: 00478 // --- Get the current line width, cap style, and join style from the render region 00479 // In case of failure, we initialise with suitable defaults 00480 INT32 LineWidth = 5000; 00481 00482 // BLOCK 00483 { 00484 LineWidthAttribute *pWidthAttr = (LineWidthAttribute *) FakeRender.GetCurrentAttribute(ATTR_LINEWIDTH); 00485 if (pWidthAttr != NULL) 00486 LineWidth = pWidthAttr->LineWidth; 00487 } 00488 00489 // --- Create a stroke outline by calling our helper function 00490 Path *pOutput = CreateVarWidthStroke(pPath, &FakeRender, LineWidth); 00491 if (pOutput == NULL) 00492 return FALSE; 00493 00494 if (!pOutput->IsClosed()) 00495 pOutput->TryToClose(); 00496 00497 // we need to make a new nodepath 00498 NodePath* pNewNode = new NodePath; 00499 00500 if (pNewNode == NULL) 00501 { 00502 delete pOutput; 00503 return FALSE; 00504 } 00505 00506 // initialise the path 00507 if (!pNewNode->InkPath.Initialise(pOutput->GetNumCoords(), 128)) 00508 { 00509 delete pOutput; 00510 delete pNewNode; 00511 return FALSE; 00512 } 00513 00514 // Copy our path data into it 00515 //CALL_WITH_FAIL(!pNewNode->InkPath.CopyPathDataFrom(pOutput), pUndoOp, Success) 00516 if (!pNewNode->InkPath.CopyPathDataFrom(pOutput)) 00517 { 00518 delete pOutput; 00519 delete pNewNode; 00520 return FALSE; 00521 } 00522 // don't need that path anymore 00523 delete pOutput; 00524 pOutput = NULL; 00525 00526 return pNewNode; 00527 } 00528 00529 00530 /******************************************************************************************** 00531 00532 > virtual BOOL PathProcessorStroke::IsDifferent(PathProcessorStroke *pOther); 00533 00534 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00535 Created: 8/1/97 00536 00537 Inputs: pOther - The other PathProcessorStroke 00538 00539 Returns: TRUE if they're considered different, FALSE if they are equivalent 00540 00541 Purpose: Equality operator 00542 00543 Notes: The base class implementation compares the runtime classes of the 00544 2 objects to see if they are different classes. If they are the same 00545 class, it assumes they're cnsidered equal - derived classes should 00546 override this behaviour if further comparisons are necessary. 00547 00548 ********************************************************************************************/ 00549 00550 BOOL PathProcessorStroke::IsDifferent(PathProcessorStroke *pOther) 00551 { 00552 ERROR3IF(pOther == NULL, "Illegal NULL param"); 00553 00554 if (GetRuntimeClass() != pOther->GetRuntimeClass()) 00555 return(TRUE); 00556 00557 // Assume that we're the same, as the base class is very simple 00558 return(FALSE); 00559 } 00560 00561 00562 00563 /******************************************************************************************** 00564 00565 > virtual PathProcessorStroke *PathProcessorStroke::Clone(void) 00566 00567 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00568 Created: 8/1/97 00569 00570 Returns: NULL if we're out of memory, else 00571 a pointer to a clone (exact copy) of this object 00572 00573 Purpose: To copy PathProcessorStroke or derived-class object 00574 00575 ********************************************************************************************/ 00576 00577 PathProcessorStroke *PathProcessorStroke::Clone(void) 00578 { 00579 // Clone this object - this can be done by just creating a new one 00580 PathProcessorStroke *pClone = new PathProcessorStroke; 00581 00582 // And copy the (base class) parent-attribute pointer so we know when our 00583 // parent attribute is "current" in the render region. 00584 if (pClone != NULL) 00585 pClone->SetParentAttr(GetParentAttr()); 00586 00587 return(pClone); 00588 } 00589 00590 00591 /******************************************************************************************** 00592 00593 > virtual BOOL PathProcessorStroke::DoesActuallyDoAnything(RenderRegion* pRender) 00594 00595 Author: Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 00596 Created: 29/9/2000 00597 Inputs: pRender - the render region we are about to render into. Note that it is 00598 important that the region has had all its attributes rendered into it 00599 Returns: TRUE if this PPS is going to change the path, FALSE if it will not. 00600 00601 Purpose: Allows the render region to know whether this PPS will do anything, if not then 00602 it can DrawPath normally allowing Dash patterns to be applied to nodes with 00603 constant VarWidth attributes applied to them 00604 00605 ********************************************************************************************/ 00606 00607 BOOL PathProcessorStroke::DoesActuallyDoAnything(RenderRegion* pRender) 00608 { 00609 ERROR2IF(pRender == NULL, FALSE, "RenderRegion is NULL in PathProcessorStroke::DoesActuallyDoAnything"); 00610 00611 if (IsDisabled()) 00612 return FALSE; 00613 00614 // Get the value function from the render region and have a look at it 00615 ValueFunction *pValFunc = NULL; 00616 00617 VariableWidthAttrValue *pVarWidthAttr = (VariableWidthAttrValue *) pRender->GetCurrentAttribute(ATTR_VARWIDTH); 00618 if (pVarWidthAttr != NULL) 00619 pValFunc = pVarWidthAttr->GetWidthFunction(); 00620 00621 // If it is a constant width stroke, then we don't really do anything 00622 if (pValFunc == NULL || IS_A(pValFunc, ValueFunctionConstant)) 00623 return FALSE; 00624 00625 // likewise if we have a brush don't do anything either, because we will use the stroke to 00626 // generate a pressure curve for the brush 00627 BrushAttrValue* pBrush = (BrushAttrValue*) pRender->GetCurrentAttribute(ATTR_BRUSHTYPE); 00628 if (pBrush != NULL && pBrush->GetBrushHandle() != BrushHandle_NoBrush) 00629 return FALSE; 00630 00631 return TRUE; 00632 } 00633 00634 /******************************************************************************************** 00635 00636 > TrapsList *PathProcessorStroke::PrepareTrapsForStroke(Path *pPath, RenderRegion *pRender, 00637 INT32 LineWidth) 00638 00639 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00640 Created: 31/12/96 (split into new function, 6/2/97) 00641 00642 Inputs: pPath - The source path to be stroked 00643 pRender - The render region it will be drawn to 00644 LineWidth - The maximum width of all strokes you intend to render with the 00645 resulting TrapsList. Normally this is the current line width from 00646 the output RenderRegion. 00647 00648 Returns: NULL if it failed, else 00649 a pointer to a new TrapsList object (which the caller must delete). 00650 00651 Purpose: Precalculation function to be used in tandem with CreateVarWidthStroke(). 00652 If you want to create several strokes along the same path, then precalculating 00653 the trapezoids will give a large performance improvement. 00654 00655 Call this with the width of the largest stroke you intend to render, and 00656 then render all strokes using the provided trapezoids. 00657 00658 SeeAlso: PathProcessorStroke::CreateVarWidthStroke 00659 00660 ********************************************************************************************/ 00661 00662 TrapsList *PathProcessorStroke::PrepareTrapsForStroke(Path *pPath, RenderRegion *pRender, INT32 LineWidth) 00663 { 00664 LineCapType LineCap = LineCapButt; 00665 JointType JoinStyle = RoundJoin; 00666 00667 // BLOCK 00668 { 00669 // --- Get the current line cap style from the render region 00670 StartCapAttribute *pCapAttr = (StartCapAttribute *) pRender->GetCurrentAttribute(ATTR_STARTCAP); 00671 if (pCapAttr != NULL) 00672 LineCap = pCapAttr->StartCap; 00673 00674 // --- Get the current line join style from the render region 00675 JoinTypeAttribute *pJoinAttr = (JoinTypeAttribute *) pRender->GetCurrentAttribute(ATTR_JOINTYPE); 00676 if (pJoinAttr != NULL) 00677 JoinStyle = pJoinAttr->JoinType; 00678 } 00679 00680 ProcessPathToTrapezoids GenerateTraps(64); 00681 TrapsList *pTrapezoids = new TrapsList; 00682 00683 if (pTrapezoids != NULL && GenerateTraps.Init(pPath, pTrapezoids)) 00684 { 00685 // Flags are: Flatten, QuantiseLines, QuantiseAll 00686 ProcessFlags PFlags(TRUE, FALSE, FALSE); 00687 if (!GenerateTraps.Process(PFlags, TrapTravel_Parametric, JoinStyle)) 00688 { 00689 delete pTrapezoids; 00690 pTrapezoids = NULL; 00691 } 00692 } 00693 00694 return pTrapezoids; 00695 } 00696 00697 00698 00699 /******************************************************************************************** 00700 00701 > Path *PathProcessorStroke::CreateVarWidthStroke(Path *pPath, RenderRegion *pRender, 00702 INT32 LineWidth, TrapsList *pTrapezoids = NULL) 00703 00704 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00705 Created: 31/12/96 (split into new function, 6/2/97) 00706 00707 Inputs: pPath - The source path to be stroked 00708 pRender - The render region it will be drawn to 00709 00710 LineWidth - The maximum width of the stroke. Normally this is the current 00711 line width from the RndRgn, but derived classes like airbrushes 00712 like to vary this value, hence it is a parameter. 00713 00714 pTrapezoids - NULL (in which case this function will generate the TrapList for itself), 00715 or a pointer to a precalculated trapezoid list (this should be generated 00716 by calling PathProcessorStroke::PrepareTrapsForStroke, and should be 00717 used if you are generating multiple strokes from one source (e.g. 00718 see the Airbrush stroker - ppairbsh.cpp)). 00719 (NOTE that using one TrapsList for multiple strokes will also give more 00720 consistent positioning of width/pattern elements along the path length) 00721 00722 Returns: NULL if it failed, else 00723 a pointer to a new Path object (which the caller must delete) to be 00724 drawn instead of the provided pPath parameter. 00725 00726 Purpose: Strokes the given path according to variable width parameters in the 00727 given render region, producing a new path representing the outline of 00728 a variable-width stroke. 00729 00730 Internal helper funciton for this class and derived classes. 00731 00732 SeeAlso: PathProcessorStroke::ProcessPath; 00733 PathProcessorStroke::PrepareTrapsForStroke 00734 00735 ********************************************************************************************/ 00736 00737 Path *PathProcessorStroke::CreateVarWidthStroke(Path *pPath, RenderRegion *pRender, INT32 LineWidth, 00738 TrapsList *pTrapezoids) 00739 { 00740 ERROR3IF(pPath == NULL || pRender == NULL, "Illegal NULL Params"); 00741 ERROR3IF(!pPath->IsStroked, "PathProcessor expects to be given a stroked path"); 00742 00743 // --- Ignore infinitely thin strokes 00744 if (LineWidth < 1) 00745 return(NULL); 00746 00747 // --- Create a new path to be rendered in place of the provided path 00748 // Note that I use a large allocation size so that reallocation need not be done 00749 // frequently, which also helps reduce memory fragmentation. 00750 Path *pOutput = new Path; 00751 if (pOutput == NULL) 00752 return(NULL); 00753 00754 pOutput->Initialise(128, 128); 00755 00756 // --- Get the current cap style from the render region 00757 // In case of failure, we initialise with suitable defaults 00758 LineCapType LineCap = LineCapButt; 00759 // BLOCK 00760 { 00761 StartCapAttribute *pCapAttr = (StartCapAttribute *) pRender->GetCurrentAttribute(ATTR_STARTCAP); 00762 if (pCapAttr != NULL) 00763 LineCap = pCapAttr->StartCap; 00764 } 00765 00766 // --- Get the variable line width descriptor from the render region 00767 ValueFunction *pValFunc = NULL; 00768 // BLOCK 00769 { 00770 VariableWidthAttrValue *pVarWidthAttr = (VariableWidthAttrValue *) pRender->GetCurrentAttribute(ATTR_VARWIDTH); 00771 if (pVarWidthAttr != NULL) 00772 pValFunc = pVarWidthAttr->GetWidthFunction(); 00773 00774 // If it is a constant width stroke, we'll just get GDraw to stroke it for us, 00775 // as that code is optimised assembler, and need not worry about variable width, 00776 // and as a result is about 4 times faster! 00777 if (pValFunc == NULL || IS_A(pValFunc, ValueFunctionConstant)) 00778 { 00779 // Get the current line join style from the render region 00780 JointType JoinStyle = RoundJoin; 00781 JoinTypeAttribute *pJoinAttr = (JoinTypeAttribute *) pRender->GetCurrentAttribute(ATTR_JOINTYPE); 00782 if (pJoinAttr != NULL) 00783 JoinStyle = pJoinAttr->JoinType; 00784 00785 pPath->StrokePathToPath(LineWidth, LineCap, JoinStyle, NULL, pOutput); 00786 00787 pOutput->IsFilled = TRUE; 00788 pOutput->IsStroked = FALSE; 00789 return(pOutput); 00790 } 00791 } 00792 00793 // --- If none were passed in, generate the set of trapezoids for the dest path 00794 BOOL MustDeleteTraps = FALSE; 00795 if (pTrapezoids == NULL) 00796 { 00797 MustDeleteTraps = TRUE; 00798 pTrapezoids = PrepareTrapsForStroke(pPath, pRender, LineWidth); 00799 if (pTrapezoids == NULL) 00800 return(NULL); 00801 } 00802 00803 // --- Stroke the trapezoids into a variable-width path 00804 PathStroker Stroker(pValFunc, LineWidth, LineCap); 00805 00806 // our new option allows us toonly get the forward mapped path 00807 00808 if (!Stroker.StrokePath(pTrapezoids, pOutput)) 00809 { 00810 if (MustDeleteTraps) 00811 delete pTrapezoids; 00812 return(NULL); 00813 } 00814 00815 if (MustDeleteTraps) 00816 delete pTrapezoids; 00817 00818 return(pOutput); 00819 } 00820 00821 00822 00823 /******************************************************************************************** 00824 00825 > Path* PathProcessorStroke::GetProcessedPath(Path* pPath, CCAttrMap* pAttrMap) 00826 00827 Author: Karim_MacDonald (Xara Group Ltd) <camelotdev@xara.com> 00828 Created: 17/11/2000 00829 Inputs: pPath the path to stroke 00830 pAttrMap the attr-map to get things like line-width and cap styles from. 00831 00832 Returns: ptr to a new path which is a stroked version of the supplied source path, or 00833 NULL if unsuccessful. 00834 00835 Purpose: This is a rehash of Diccon's GetProcessedPath method, except you needn't 00836 worry about any nodes but the four NodeAttributes below, which must live 00837 in the supplied attr-map. 00838 00839 We also don't use render-regions, as their not actually necessary 00840 (the other version of this method does). 00841 00842 Notes: The following valid NodeAttributes must live in the attr-map: 00843 AttrLineWidth 00844 AttrStartCap 00845 AttrVariableWidth 00846 AttrJoinType 00847 00848 Errors: ERROR2 with FALSE if anything goes wrong, or our inputs are NULL or invalid. 00849 00850 See also: Diccon's GetProcessedPath() higher up this file. 00851 00852 ********************************************************************************************/ 00853 Path* PathProcessorStroke::GetProcessedPath(Path* pPath, CCAttrMap* pAttrMap) 00854 { 00855 ERROR2IF(pPath == NULL || pAttrMap == NULL, NULL, 00856 "PathProcessorStroke::GetProcessedPath; NULL input parameters!"); 00857 00858 INT32 LineWidth; 00859 LineCapType LineCap; 00860 ValueFunction* pValFunc; 00861 JointType JoinStyle; 00862 00863 if (!GetRequiredAttributes(pAttrMap, &LineWidth, &LineCap, &pValFunc, &JoinStyle)) 00864 return NULL; 00865 00866 if (LineWidth < 1) 00867 return NULL; 00868 00869 // Create a path to hold our stroking exploits! 00870 // We'll prob'ly be generating loads of data, so be generous with our path's 00871 // initialisation, so it's not continually reallocating memory as it runs out. 00872 Path* pStroke = new Path; 00873 if (pStroke == NULL) 00874 return NULL; 00875 if (!pStroke->Initialise(128, 128)) 00876 { 00877 delete pStroke; 00878 return NULL; 00879 } 00880 00881 // if it's a constant-width stroke, ask GDraw to stroke it - it's 00882 // quicker and if it goes wrong, it's Gavin's fault (only joking ;o) 00883 if (pValFunc == NULL || IS_A(pValFunc, ValueFunctionConstant)) 00884 { 00885 pPath->StrokePathToPath(LineWidth, LineCap, JoinStyle, NULL, pStroke); 00886 pStroke->IsFilled = TRUE; 00887 pStroke->IsStroked = TRUE; 00888 return pStroke; 00889 } 00890 00891 // ok, GDraw didn't deal with it, so let's get down to business. 00892 00893 // Generate the set of trapezoids for the dest path. 00894 ProcessPathToTrapezoids GenerateTraps(64); 00895 TrapsList *pTrapezoids = new TrapsList; 00896 BOOL ok = (pTrapezoids != NULL); 00897 if (ok) ok = GenerateTraps.Init(pPath, pTrapezoids); 00898 if (ok) 00899 { 00900 // Flags are: Flatten, QuantiseLines, QuantiseAll 00901 ProcessFlags PFlags(TRUE, FALSE, FALSE); 00902 ok = GenerateTraps.Process(PFlags, TrapTravel_Parametric, JoinStyle); 00903 } 00904 00905 // Stroke the trapezoids into a variable-width path. 00906 if (ok) 00907 { 00908 PathStroker Stroker(pValFunc, LineWidth, LineCap); 00909 ok = Stroker.StrokeSmoothPath(pTrapezoids, pStroke); 00910 } 00911 00912 // tidy up temporary info. 00913 if (pTrapezoids != NULL) 00914 { 00915 delete pTrapezoids; 00916 pTrapezoids = NULL; 00917 } 00918 00919 // tidy up the path and pass it out. 00920 if (pStroke != NULL) 00921 { 00922 if (ok) 00923 { 00924 if (!pStroke->IsClosed()) 00925 pStroke->TryToClose(); 00926 } 00927 else 00928 { 00929 delete pStroke; 00930 pStroke = NULL; 00931 } 00932 } 00933 00934 return pStroke; 00935 } 00936 00937 00938 00939 /******************************************************************************************** 00940 00941 > BOOL PathProcessorStroke::GetRequiredAttributes(CCAttrMap* pAttrMap, 00942 INT32* pLineWidth, 00943 LineCapType* pLineCap, 00944 ValueFunction** ppValFunc, 00945 JointType* pJoinStyle) 00946 Author: Karim_MacDonald (Xara Group Ltd) <camelotdev@xara.com> 00947 Created: 17/11/2000 00948 00949 Inputs: pAttrMap ptr to the attr-map to search. 00950 pLineWidth line width ptr to fill. 00951 pLineCap end-cap type ptr to fill. 00952 ppValFunc variable line-width function ptr to fill. 00953 pJoinStyle join style ptr to fill. 00954 00955 Outputs: If successful, the supplied pointers are filled with the appropriate values 00956 from the attribute map, otherwise they are filled with default values. 00957 00958 Returns: TRUE if successful, 00959 FALSE otherwise. 00960 00961 Purpose: Extracts the following values, required for path stroking, 00962 from the supplied attribute map: 00963 Line width. 00964 Line start-cap - eg square or round end. 00965 Pointer to any applied width function - may be NULL. 00966 Join style - eg bevelled, mitred, round. 00967 00968 Notes: If this function fails, *DO NOT* use the output values! They are only there 00969 in case you screw up and *do* decide to use them, in which case they should 00970 hopefully not bring Camelot down around your ears! 00971 00972 Errors: An error message is output to TRACE if we were unsuccessful. 00973 I'm not putting in an ERROR2 or 3, as I think from a user view-point, 00974 a quiet failure is more graceful. 00975 00976 ********************************************************************************************/ 00977 BOOL PathProcessorStroke::GetRequiredAttributes(CCAttrMap* pAttrMap, 00978 INT32* pLineWidth, 00979 LineCapType* pLineCap, 00980 ValueFunction** ppValFunc, 00981 JointType* pJoinStyle) 00982 { 00983 AttrLineWidth* pAttrWidth = NULL; 00984 AttrStartCap* pAttrCap = NULL; 00985 AttrVariableWidth* pAttrVarWidth = NULL; 00986 AttrJoinType* pAttrJoinType = NULL; 00987 00988 pAttrMap->Lookup( CC_RUNTIME_CLASS(AttrLineWidth), (void*&)pAttrWidth); 00989 pAttrMap->Lookup( CC_RUNTIME_CLASS(AttrStartCap), (void*&)pAttrCap); 00990 pAttrMap->Lookup( CC_RUNTIME_CLASS(AttrVariableWidth),(void*&)pAttrVarWidth); 00991 pAttrMap->Lookup( CC_RUNTIME_CLASS(AttrJoinType), (void*&)pAttrJoinType); 00992 00993 if (pAttrWidth == NULL || pAttrCap == NULL || 00994 pAttrVarWidth == NULL || pAttrJoinType == NULL) 00995 { 00996 TRACEALL( _T("PathProcessorStroke::GetProcessedPath; AttrMap doesn't contain the required attrs!") ); 00997 *pLineWidth = 0; 00998 *pLineCap = LineCapButt; 00999 *ppValFunc = NULL; 01000 *pJoinStyle = RoundJoin; 01001 return FALSE; 01002 } 01003 01004 *pLineWidth = pAttrWidth->Value.LineWidth; 01005 *pLineCap = pAttrCap->Value.StartCap; 01006 *ppValFunc = pAttrVarWidth->Value.GetWidthFunction(); 01007 *pJoinStyle = pAttrJoinType->Value.JoinType; 01008 01009 return TRUE; 01010 } 01011 01012 01013 01014 NodePath* PathProcessorStroke::GetSmoothProcessedPath(Path* pPath, Node* pParent) 01015 { 01016 // we need to fake a renderregion as our helper functions need one, 01017 // luckily FormatRegion is on hand from the text stuff 01018 FormatRegion FakeRender; 01019 01020 if (!FakeRender.Init((NodeRenderableInk*)pParent)) // init renders all applied attributes into the region 01021 return FALSE; 01022 01023 // From here copied from ProcessPath: 01024 // --- Get the current line width, cap style, and join style from the render region 01025 // In case of failure, we initialise with suitable defaults 01026 INT32 LineWidth = 5000; 01027 01028 // BLOCK 01029 { 01030 LineWidthAttribute *pWidthAttr = (LineWidthAttribute *) FakeRender.GetCurrentAttribute(ATTR_LINEWIDTH); 01031 if (pWidthAttr != NULL) 01032 LineWidth = pWidthAttr->LineWidth; 01033 } 01034 01035 // --- Create a stroke outline by calling our helper function 01036 Path *pOutput = CreateSmoothVarWidthStroke(pPath, &FakeRender, LineWidth); 01037 if (pOutput == NULL) 01038 return FALSE; 01039 01040 if (!pOutput->IsClosed()) 01041 pOutput->TryToClose(); 01042 01043 // we need to make a new nodepath 01044 NodePath* pNewNode = new NodePath; 01045 01046 if (pNewNode == NULL) 01047 { 01048 delete pOutput; 01049 return FALSE; 01050 } 01051 01052 // initialise the path 01053 if (!pNewNode->InkPath.Initialise(pOutput->GetNumCoords(), 128)) 01054 { 01055 delete pOutput; 01056 delete pNewNode; 01057 return FALSE; 01058 } 01059 01060 // Copy our path data into it 01061 //CALL_WITH_FAIL(!pNewNode->InkPath.CopyPathDataFrom(pOutput), pUndoOp, Success) 01062 if (!pNewNode->InkPath.CopyPathDataFrom(pOutput)) 01063 { 01064 delete pOutput; 01065 delete pNewNode; 01066 return FALSE; 01067 } 01068 // don't need that path anymore 01069 delete pOutput; 01070 pOutput = NULL; 01071 01072 return pNewNode; 01073 } 01074 01075 01076 Path *PathProcessorStroke::CreateSmoothVarWidthStroke(Path *pPath, RenderRegion *pRender, INT32 LineWidth, 01077 TrapsList *pTrapezoids) 01078 { 01079 ERROR3IF(pPath == NULL || pRender == NULL, "Illegal NULL Params"); 01080 ERROR3IF(!pPath->IsStroked, "PathProcessor expects to be given a stroked path"); 01081 01082 // --- Ignore infinitely thin strokes 01083 if (LineWidth < 1) 01084 return(NULL); 01085 01086 // --- Create a new path to be rendered in place of the provided path 01087 // Note that I use a large allocation size so that reallocation need not be done 01088 // frequently, which also helps reduce memory fragmentation. 01089 Path *pOutput = new Path; 01090 if (pOutput == NULL) 01091 return(NULL); 01092 01093 pOutput->Initialise(128, 128); 01094 01095 // --- Get the current cap style from the render region 01096 // In case of failure, we initialise with suitable defaults 01097 LineCapType LineCap = LineCapButt; 01098 // BLOCK 01099 { 01100 StartCapAttribute *pCapAttr = (StartCapAttribute *) pRender->GetCurrentAttribute(ATTR_STARTCAP); 01101 if (pCapAttr != NULL) 01102 LineCap = pCapAttr->StartCap; 01103 } 01104 01105 // --- Get the variable line width descriptor from the render region 01106 ValueFunction *pValFunc = NULL; 01107 // BLOCK 01108 { 01109 VariableWidthAttrValue *pVarWidthAttr = (VariableWidthAttrValue *) pRender->GetCurrentAttribute(ATTR_VARWIDTH); 01110 if (pVarWidthAttr != NULL) 01111 pValFunc = pVarWidthAttr->GetWidthFunction(); 01112 01113 // If it is a constant width stroke, we'll just get GDraw to stroke it for us, 01114 // as that code is optimised assembler, and need not worry about variable width, 01115 // and as a result is about 4 times faster! 01116 if (pValFunc == NULL || IS_A(pValFunc, ValueFunctionConstant)) 01117 { 01118 // Get the current line join style from the render region 01119 JointType JoinStyle = RoundJoin; 01120 JoinTypeAttribute *pJoinAttr = (JoinTypeAttribute *) pRender->GetCurrentAttribute(ATTR_JOINTYPE); 01121 if (pJoinAttr != NULL) 01122 JoinStyle = pJoinAttr->JoinType; 01123 01124 pPath->StrokePathToPath(LineWidth, LineCap, JoinStyle, NULL, pOutput); 01125 01126 pOutput->IsFilled = TRUE; 01127 pOutput->IsStroked = FALSE; 01128 return(pOutput); 01129 } 01130 } 01131 01132 // --- If none were passed in, generate the set of trapezoids for the dest path 01133 BOOL MustDeleteTraps = FALSE; 01134 if (pTrapezoids == NULL) 01135 { 01136 MustDeleteTraps = TRUE; 01137 pTrapezoids = PrepareTrapsForStroke(pPath, pRender, LineWidth); 01138 if (pTrapezoids == NULL) 01139 return(NULL); 01140 } 01141 01142 // --- Stroke the trapezoids into a variable-width path 01143 PathStroker Stroker(pValFunc, LineWidth, LineCap); 01144 01145 // our new option allows us to only get the forward mapped path 01146 01147 if (!Stroker.StrokeSmoothPath(pTrapezoids, pOutput)) 01148 { 01149 if (MustDeleteTraps) 01150 delete pTrapezoids; 01151 return(NULL); 01152 } 01153 01154 01155 if (MustDeleteTraps) 01156 delete pTrapezoids; 01157 01158 return(pOutput); 01159 }