// $Id: extender.cpp 1282 2006-06-09 09:46:49Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ // #include "camtypes.h" #include "extender.h" #include "ncntrcnt.h" // for NodeContourController. #include "nodecont.h" // for NodeShadowController. #include "nbevcont.h" // for NodeBevelController. #include "layer.h" // for Layer. //#include "becomea.h" // for BecomeA. - in camtypes.h [AUTOMATICALLY REMOVED] //#include "range.h" // for SelRange. - in camtypes.h [AUTOMATICALLY REMOVED] //#include "undoop.h" // for UndoableOperation. - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodershp.h" // for NodeRegularShape. #include "objchge.h" // for ObjChange stuff. #include // for std::list. #include "nodeblnd.h" // for NodeBlend #include "nodebldr.h" // for NodeBlender #include "nodepath.h" #if 0 #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif #endif DECLARE_SOURCE("$Revision: 1282 $"); // Place any IMPLEMENT type statements here CC_IMPLEMENT_MEMDUMP(Extender, CC_CLASS_MEMDUMP) CC_IMPLEMENT_MEMDUMP(ExtendParams, CC_CLASS_MEMDUMP) // We want better memory tracking // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW // Functions follow /******************************************************************************************** > void Extender::Extend( NodeRenderableInk* pNode, DocRect* pStartRect, DocRect* pEndRect, BYTE fExtendFlags, DocRect* pOldStartRect = NULL) Author: Karim_MacDonald (Xara Group Ltd) Created: 10/09/1999 Inputs: Outputs: Purpose: ** DEPRECATED - DO NOT CALL ** Errors: ERROR2 always. See also: ********************************************************************************************/ void Extender::Extend( NodeRenderableInk* pNode, DocRect* pStartRect, DocRect* pEndRect, BYTE fExtendFlags, DocRect* pOldStartRect) { ERROR2RAW("This implementation of Extender::Extend is no longer used or valid"); } /******************************************************************************************** > BOOL Extender::CheckValidExtend(NodeRenderableInk *pNode, DocRect *pStartRect, DocRect *pEndRect, BYTE fExtendFlags, DocRect* pOldStartRect = NULL) Author: Karim_MacDonald (Xara Group Ltd) Created: 17/11/1999 Inputs: Outputs: Purpose: ** DEPRECATED - DO NOT CALL ** Returns: FALSE Errors: ERROR2 always. See also: ********************************************************************************************/ BOOL Extender::CheckValidExtend(NodeRenderableInk* pNode, DocRect* pStartRect, DocRect* pEndRect, BYTE fExtendFlags, DocRect* pOldStartRect) { ERROR2( FALSE, "This implementation of Extender::CheckValidExtend is no longer used or valid" ); } ///////////////////////////////////////////////////////////////////////////////////////////// // // Extender helper function for make-shapes capability. // /******************************************************************************************** > BOOL Extender::ConvertQuickShapesInSelRangeToPaths( UndoableOperation* pUndoOp, SelRange* pSel ) Author: Karim_MacDonald (Xara Group Ltd) Created: 21 March 2000 Inputs: pUndoOp we need an UndoOp to make this undoable. pSel the SelRange we'll iterate over. Outputs: Returns: TRUE if any quickshapes were successfully turned, FALSE if not. Purpose: Iterates over the given SelRange looking for any NodeRegularShapes, ie QuickShapes. If we find any then they get zapped, and all that is left is a smoking pair of shoes! ... erm, sorry - a NodePath. Notes: This is all done undoably. There is however a problem in that we do not necessarily want to completely unwind the UndoOp we were given if anything goes wrong. But if we don't do that then what _can_ we do??? Currently, if things go wrong while converting to shapes, we just try to convert as many shapes to paths as possible and return FALSE at the end. We don't mind if pSel is empty or has no QuickShapes. Our search of the SelRange is deep - we look at children of nodes in the range too. Errors: ERROR2 with FALSE if we get invalid parameters. See also: ********************************************************************************************/ BOOL Extender::ConvertQuickShapesInSelRangeToPaths( UndoableOperation* pUndoOp, Range* pRange ) { // validate params. ERROR2IF(pUndoOp == NULL || pRange == NULL, FALSE, "Invalid parameter(s)"); // TODO: May need to set the range's RangeControl to avoid promoting to parents. // iterate over the range and build a list of QuickShapes. // we do a depth-first search from every node in the range, // looking everywhere for those those pesky QuickShapes :) BOOL bQuickShapesConverted = FALSE; std::list lpShapes; Node* pCurrentRangeNode = pRange->FindFirst(); while (pCurrentRangeNode != NULL) { Node* pNextNode = NULL; Node* pCurrentNode = pCurrentRangeNode->FindFirstDepthFirst(); while (pCurrentNode != NULL) { pNextNode = pCurrentNode->FindNextDepthFirst(pCurrentRangeNode); if (pCurrentNode->IsARegularShape()) { if (!(pCurrentNode->FindParent (CC_RUNTIME_CLASS (NodeBlend)))) { // note thate we're *saying* we're going to replace with only one node. // this isn't necessarily true - DoBecomeA on a brushed path results in // many ungrouped nodes. it's just the best we can do, considering blends. ObjChangeFlags ocf; ocf.ReplaceNode = TRUE; ObjChangeParam ObjChange(OBJCHANGE_STARTING, ocf, pCurrentNode, pUndoOp); if (pCurrentNode->AllowOp(&ObjChange, FALSE, FALSE)) lpShapes.push_front((NodeRegularShape*)pCurrentNode); } /*else { // note thate we're *saying* we're going to replace with only one node. // this isn't necessarily true - DoBecomeA on a brushed path results in // many ungrouped nodes. it's just the best we can do, considering blends. ObjChangeFlags ocf; ocf.ReplaceNode = TRUE; ObjChangeParam ObjChange(OBJCHANGE_STARTING, ocf, pCurrentNode, pUndoOp); if (pCurrentNode->AllowOp(&ObjChange, TRUE, FALSE)) lpShapes.push_front((NodeRegularShape*)pCurrentNode); }*/ } pCurrentNode = pNextNode; } pCurrentRangeNode = pRange->FindNext(pCurrentRangeNode, TRUE); } // okay, if our list isn't empty then we have work to do. if (!lpShapes.empty()) { // invalidate the region of the selection. if (pUndoOp->DoInvalidateNodesRegions(*pRange, TRUE)) { // change all the QuickShapes into NodePaths. NodeRegularShape* pCurrentShape = NULL; while (!lpShapes.empty()) { pCurrentShape = lpShapes.front(); lpShapes.pop_front(); BecomeA BecomeAPath( BECOMEA_REPLACE, CC_RUNTIME_CLASS(NodePath), pUndoOp, pCurrentShape->IsSelected() ); if (pCurrentShape->CanBecomeA(&BecomeAPath)) { if (!(pCurrentShape->FindParent (CC_RUNTIME_CLASS (NodeBlend)))) { if (pCurrentShape->DoBecomeA(&BecomeAPath)) { pCurrentShape->DeSelect(FALSE); bQuickShapesConverted = TRUE; } } /*else { NodeBlend* ptrBlend = (NodeBlend*) pCurrentShape->FindParent (CC_RUNTIME_CLASS (NodeBlend)); BecomeA BecomeAPath( BECOMEA_REPLACE, CC_RUNTIME_CLASS(NodePath), pUndoOp, pCurrentShape->IsSelected() ); if (pCurrentShape->DoBecomeA(&BecomeAPath)) { pCurrentShape->DeSelect(FALSE); bQuickShapesConverted = TRUE; } // nastiness - to reinitialise the blend, I need a ptr to the converted path // BUT there is no easy way of doing this! This is a hack - which seems to // hold up well .... NOTE: assumes that the last action executed was a // HideNodeAction (i.e. hide pCurrentShape) ActionList* actList = pUndoOp->GetUndoActionList (); // get undo history ListItem* pItem = actList->GetTail (); // get last op ERROR3IF (!IS_A (pItem, HideNodeAction), "Assumed HideNodeAction is not a HideNodeAction!"); HideNodeAction* hnAct = (HideNodeAction*) pItem; NodeRenderableInk* newNode = (NodeRenderableInk*) hnAct->GetNode (); ERROR3IF (!IS_A (ptrBlend, NodeBlend), "NodeBlend is not a NodeBlend!"); BOOL done = FALSE; NodeBlender* ptrNode = ptrBlend->FindFirstBlender (); while (!done) { ptrNode->Deinit (); if (ptrNode->GetNodeStart () == pCurrentShape) { ptrNode->Reinit(newNode, NULL, FALSE); } if (ptrNode->GetNodeEnd () == pCurrentShape) { ptrNode->Reinit(NULL, newNode, FALSE); } ptrNode = ptrBlend->FindNextBlender (ptrNode); if (!ptrNode) { done = TRUE; } } }*/ } } } if (bQuickShapesConverted) pRange->Update(); } return bQuickShapesConverted; } ///////////////////////////////////////////////////////////////////////////////////////////// // // New Extender functions, for virtual-fn extend procedure. // /******************************************************************************************** > DocRect Extender::CheckValidExtend( NodeRenderableInk* pNode, BYTE fExtendFlags, const DocRect& drTriggerRelation, const DocRect& drTargetRelation, const DocRect& drNewTrigger, const DocRect& drLastTarget ) Author: Karim_MacDonald (Xara Group Ltd) Created: 06/12/1999 Inputs: pNode pointer to a NodeRenderableInk which will be extended. fExtendFlags bitwise flags, describing any combination of x- or y- stretching or extending, except that you may not extend and stretch along an axis at the same time. Values: X_EXTEND the node should be extended along the x-axis Y_EXTEND the node should be extended along the y-axis X_STRETCH the node should be stretched along the x-axis Y_STRETCH the node should be stretched along the y-axis drTriggerRelation relationship rectangle - the bounding box of the trigger sets when the relationship is defined. drTargetRelation relationship rectangle - the bounding box of the target sets when the relationship is defined. drNewTrigger bounding box of sets of triggers which started the call to this function. drLastTrigger the bounding box of the trigger set(s) before it changed. drLastTarget the bounding box of the target set before it is extended. Purpose: This function tests whether or not a call to the extend function will extend an object in a valid way. For example, a path cannot be shrunken so that its control points cross its centre, as this operation is irreversible - if the path was extended back again, these points would extend the wrong direction. Similarly, an invalid extend occurs when a trigger set is shrunk so that its centre happens to move over the centre of a target node which is not moving in this particular extension; undoing this operation would end up dragging the target node back with the trigger. This function should always be called on all objects sharing the same name, *before* Extender::Extend is called to actually perform the extension on those objects, to make sure that no irreversible operation is performed. Returns: A DocRect, whose values will all be set to INT32_MAX if the test was passed. For each side on which the test failed, the corresponding value will be set to how far in it is safe to extend the node, eg if the node could only be shrunk inwards by 10 millipoints on the right-hand side, then the hix value of the DocRect will contain 10, while all other values will be INT32_MAX. Errors: ERROR2 with FALSE if fExtendFlags holds contradictory flags or any parameters are NULL. See also: Extender::CheckValidExtend() and for more information, see: \\Earth\Develop\CAMELOT\DOCS\Specs\Camelot V3\Extending Objects - Mechanism.doc NOTE: A call to Extender::CheckValidExtend must *always* be made before performing a corresponding Extender::Extend process. ********************************************************************************************/ DocRect Extender::CheckValidExtend( NodeRenderableInk* pNode, BYTE fExtendFlags, const DocRect& drTriggerRelation, const DocRect& drTargetRelation, const DocRect& drNewTrigger, const DocRect& drLastTrigger, const DocRect& drLastTarget, BOOL * pOK, BOOL ExtendAroundTarget) { // validate parameters. ERROR2IF( pNode == NULL, DocRect(0, 0, 0, 0), "NULL parameters passed to Extender::CheckValidExtend" ); ERROR2IF( ((fExtendFlags & X_EXTEND) && (fExtendFlags & X_STRETCH)) || ((fExtendFlags & Y_EXTEND) && (fExtendFlags & Y_STRETCH)), DocRect(0, 0, 0, 0), "Extender::CheckValidExtend called with contradictory flags in fExtendFlags" ); if (pOK) *pOK = TRUE; // quit immediately if any of the bounding rectangles is invalid, // or is of zero width or height. if( !drTriggerRelation.IsValid() || !drTargetRelation.IsValid() || !drNewTrigger.IsValid() || !drLastTrigger.IsValid() || !drLastTarget.IsValid() || drTriggerRelation.Height() == 0 || drTriggerRelation.Width() == 0 || drTargetRelation.Height() == 0 || drTargetRelation.Width() == 0 || drNewTrigger.Height() == 0 || drNewTrigger.Width() == 0 || drLastTrigger.Height() == 0 || drLastTrigger.Width() == 0 || drLastTarget.Height() == 0 || drLastTarget.Width() == 0) { if (pOK) *pOK = FALSE; return DocRect(0, 0, 0, 0); } // determine extension working values. ExtendParams eps; CalculateExtendParams( &eps, fExtendFlags, drTriggerRelation, drTargetRelation, drNewTrigger, drLastTrigger, drLastTarget, NULL, ExtendAroundTarget ); // check whether pNode, or any child of pNode, is extendible. // also, check that extending all of these Nodes is a valid operation. // if this is the case, recursively extend pNode and its children. DocRect drMinExtend = pNode->ValidateExtend(eps); BOOL bValidExtend = ( drMinExtend.lo.x == INT32_MAX && drMinExtend.lo.y == INT32_MAX && drMinExtend.hi.x == INT32_MAX && drMinExtend.hi.y == INT32_MAX ); // DEBUG: #ifdef _DEBUG TRACEUSER( "Karim", _T("%5s ValidateExtend() on %s\n"), bValidExtend ? _T("TRUE") : _T("FALSE"), pNode->Name()); #endif BOOL bExtendible = TRUE; if (!bExtendible || !bValidExtend) { //#ifdef _DEBUG // TRACEUSER( "Karim", _T("Invalid Extend on %s; Ext: %d, Valid: %d\n"), // pNode->Name(), bExtendible, bValidExtend); //#endif return drMinExtend; } // return the outcome of the test, in the form of drMinExtend. return drMinExtend; } /******************************************************************************************** > void Extender::Extend( NodeRenderableInk* pNode, BYTE fExtendFlags, const DocRect& drTriggerRelation, const DocRect& drTargetRelation, const DocRect& drNewTrigger, const DocRect& drLastTarget, const DocRect* pdrDifference = NULL) Author: Karim_MacDonald (Xara Group Ltd) Created: 03/12/1999 Inputs: pNode pointer to a NodeRenderableInk which will be extended. fExtendFlags bitwise flags, describing any combination of x- or y- stretching or extending, except that you may not extend and stretch along an axis at the same time. Values: X_EXTEND the node should be extended along the x-axis Y_EXTEND the node should be extended along the y-axis X_STRETCH the node should be stretched along the x-axis Y_STRETCH the node should be stretched along the y-axis drTriggerRelation relationship rectangle - the bounding box of the trigger sets when the relationship is defined. drTargetRelation relationship rectangle - the bounding box of the target sets when the relationship is defined. drNewTrigger bounding box of sets of triggers which started the call to this function. drLastTrigger the bounding box of the trigger set(s) before it changed. drLastTarget the bounding box of the target set before it is extended. pdrDifference a rectangle containing four difference parameters which may be used to alter the extend. ok to omit or be NULL. Outputs: pNode may be extended or stretched horizontally or vertically in any combination. Purpose: Perform an extend operation on the given Node and its children, using the given rectangles and flags to determine how the Node should transform. Errors: ERROR2 if fExtendFlags holds contradictory flags or any parameters are NULL. See also: Extender::CheckValidExtend() and for more information, see: \\Earth\Develop\CAMELOT\DOCS\Specs\Camelot V3\Extending Objects - Mechanism.doc NOTE: A call to Extender::CheckValidExtend must always be made before performing a corresponding Extender::Extend process. ********************************************************************************************/ void Extender::Extend( NodeRenderableInk* pNode, BYTE fExtendFlags, const DocRect& drTriggerRelation, const DocRect& drTargetRelation, const DocRect& drNewTrigger, const DocRect& drLastTrigger, const DocRect& drLastTarget, const DocRect* pdrDifference, const BOOL ExtendAroundTarget, UndoableOperation * pOp) { // validate parameters. if (pNode == NULL) { ERROR2RAW("NULL parameters passed to Extender::Extend."); return; } if (((fExtendFlags & X_EXTEND) && (fExtendFlags & X_STRETCH)) || ((fExtendFlags & Y_EXTEND) && (fExtendFlags & Y_STRETCH))) { ERROR2RAW("Extender::Extend called with contradictory flags in fExtendFlags"); return; } // quit immediately if any of the bounding rectangles is invalid, // or is of zero width or height. if (!drTriggerRelation.IsValid() || !drTargetRelation.IsValid() || !drNewTrigger.IsValid() || !drLastTrigger.IsValid() || !drLastTarget.IsValid() || drTriggerRelation.Height() == 0 || drTriggerRelation.Width() == 0 || drTargetRelation.Height() == 0 || drTargetRelation.Width() == 0 || drNewTrigger.Height() == 0 || drNewTrigger.Width() == 0 || drLastTrigger.Height() == 0 || drLastTrigger.Width() == 0 || drLastTarget.Height() == 0 || drLastTarget.Width() == 0) { ERROR2RAW("Extender::Extend; Invalid or zero-width rectangle"); return; } // determine extension working values. ExtendParams eps; eps.pOp = pOp; CalculateExtendParams( &eps, fExtendFlags, drTriggerRelation, drTargetRelation, drNewTrigger, drLastTrigger, drLastTarget, pdrDifference, ExtendAroundTarget ); // this hasn't really changed size so do nothing if (!eps.xdec && !eps.xinc && !eps.ydec && !eps.yinc && eps.xscale == 1.0 && eps.yscale == 1.0 && eps.doccOffset.x == 0 && eps.doccOffset.y == 0) return; // invalidate the old image of the object, perform the extension // and flag that the object should be redrawn. // we must look above this node, to detect whether we are the child // of a node whose on-screen bounds are greater than ours. // if this is the case, we need to invalidate *that* node and // get it to redraw. // TODO: there may be a better way of doing this (is it necessary at all with the AllowOp mechanism?) BOOL bFoundBigParent = FALSE; Node* pInvalidateNode = pNode->FindParent(); while (pInvalidateNode != NULL && !IS_A(pInvalidateNode, Layer) && !bFoundBigParent) { if (IS_A(pInvalidateNode, NodeBevelController) || IS_A(pInvalidateNode, NodeShadowController) || IS_A(pInvalidateNode, NodeContourController)) bFoundBigParent = TRUE; else pInvalidateNode = pInvalidateNode->FindParent(); } if (!bFoundBigParent) pInvalidateNode = pNode; ((NodeRenderableInk*)pInvalidateNode)->RedrawObject(); ((NodeRenderableInk*)pInvalidateNode)->InvalidateBoundingRect(); pNode->Extend(eps); // DEBUG: //#ifdef _DEBUG // TRACEUSER( "Karim", _T("Extend() on %s\n"), pNode->Name()); //#endif ((NodeRenderableInk*)pInvalidateNode)->RedrawObject(); } /******************************************************************************************** > DocRect Extender::ValidateControlPoints(INT32 numPoints, const DocCoord* doccArray, const ExtendParams& ExtParams) Author: Karim_MacDonald (Xara Group Ltd) Created: 07/12/1999 Inputs: numPoints number of coordinates to validate. doccArray the array of coordinates. ExtParams extend parameters. Returns: A DocRect containing maximum safe shrink distances for the points: lox maximum shrinkage in from the left. hix maximum shrinkage in from the right. loy maximum shrinkage in from the bottom. hiy maximum shrinkage in from the top. If shrinking in a particular direction is ok, the corresponding DocRect value will be set to INT32_MAX. Purpose: Validate the given points, using the provided extend parameters. If extending the points is invalid, then return a rectangle containing the largest distances which the points can be shrunk in by. Errors: ERROR3 if doccArray is NULL. See also: ********************************************************************************************/ DocRect Extender::ValidateControlPoints(INT32 numPoints, const DocCoord* doccArray, const ExtendParams& ExtParams) { // validate data ERROR3IF(doccArray == NULL, "Extender::ValidateControlPoints- NULL doccArray passed!"); DocRect drMaxSafeShrink(INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX), drCheckExtend; // this check is only necessary if extension will be occurring along an // axis; if the extension type is stretch, or no action, no validation // is required. // for each of the four directions of displacement, and only if we shrank, check that // none of the points lie in a position to which they would not be returned under the // inverse extend operation. if they do, we record the largest safe amount we could // shrink them in by. // providing the extend operation involves displacing all points by the difference // between start- and end- centres, followed by the extension, these invalid points // will be only those which would be dragged inwards, to cross over the end-centre. if (ExtParams.fExtendFlags & X_EXTEND) { if (ExtParams.xinc < 0) { INT32 minXshrink = CheckInvalidShrinkingPoints( ExtParams.doccEndCentre.x, ExtParams.xinc, ExtParams.doccOffset.x, numPoints, doccArray, TRUE ); if (minXshrink >= 0) // invalid, so record the return value. drMaxSafeShrink.hi.x = minXshrink; } // we do a different test if we're expanding. else if (ExtParams.xinc > 0) { INT32 minXshrink = CheckInvalidExpandingPoints( ExtParams.doccEndCentre.x, ExtParams.xinc, ExtParams.doccOffset.x, numPoints, doccArray, TRUE ); if (minXshrink >= 0) // invalid, so record the return value. drMaxSafeShrink.hi.x = minXshrink; } if (ExtParams.xdec < 0) { INT32 minXshrink = CheckInvalidShrinkingPoints( ExtParams.doccEndCentre.x, -ExtParams.xdec, ExtParams.doccOffset.x, numPoints, doccArray, TRUE ); if (minXshrink >= 0) // invalid, so record the return value. drMaxSafeShrink.lo.x = minXshrink; } // we do a different test if we're expanding. else if (ExtParams.xdec > 0) { INT32 minXshrink = CheckInvalidExpandingPoints( ExtParams.doccEndCentre.x, -ExtParams.xdec, ExtParams.doccOffset.x, numPoints, doccArray, TRUE ); if (minXshrink >= 0) // invalid, so record the return value. drMaxSafeShrink.lo.x = minXshrink; } } if (ExtParams.fExtendFlags & Y_EXTEND) { if (ExtParams.yinc < 0) { INT32 minYshrink = CheckInvalidShrinkingPoints( ExtParams.doccEndCentre.y, ExtParams.yinc, ExtParams.doccOffset.y, numPoints, doccArray, FALSE ); if (minYshrink >= 0) // invalid, so record the return value. drMaxSafeShrink.hi.y = minYshrink; } // we do a different test if we're expanding. else if (ExtParams.yinc > 0) { INT32 minYshrink = CheckInvalidExpandingPoints( ExtParams.doccEndCentre.y, ExtParams.yinc, ExtParams.doccOffset.y, numPoints, doccArray, FALSE ); if (minYshrink >= 0) // invalid, so record the return value. drMaxSafeShrink.hi.y = minYshrink; } if (ExtParams.ydec < 0) { INT32 minYshrink = CheckInvalidShrinkingPoints( ExtParams.doccEndCentre.y, -ExtParams.ydec, ExtParams.doccOffset.y, numPoints, doccArray, FALSE ); if (minYshrink >= 0) // invalid, so record the return value. drMaxSafeShrink.lo.y = minYshrink; } // we do a different test if we're expanding. else if (ExtParams.ydec > 0) { INT32 minYshrink = CheckInvalidExpandingPoints( ExtParams.doccEndCentre.y, -ExtParams.ydec, ExtParams.doccOffset.y, numPoints, doccArray, FALSE ); if (minYshrink >= 0) // invalid, so record the return value. drMaxSafeShrink.lo.y = minYshrink; } } return drMaxSafeShrink; } /******************************************************************************************** > INT32 Extender::CheckInvalidShrinkingPoints( INT32 nCentre, INT32 nDelta, INT32 nOffset, INT32 nNumPoints, const DocCoord* doccArray, BOOL bExamineX ) Author: Karim_MacDonald (Xara Group Ltd) Created: 07/12/1999 Inputs: nCentre the limiting x or y centre-value, to which points are shrinking. nDelta the distance by which the points are being shrunk. nOffset an offset which should be applied to the points before testing. nNumPoints the number of points to test. doccArray the array of points to test. bExamineX whether to examine x-coords (TRUE) or y-coords (FALSE). Outputs: Returns: The maximum safe distance that points can be extended towards nCentre by. This value will be non-negative if any invalid points were found, or negative if all points passed the test. Purpose: Check whether any of the points in the given array will be shrunk past the given limiting value. If all the points check out, we return an all-clear value, which is negative. Otherwise, the maximum safe shrinkage is given. Karim 11/05/2000 With the addition of invariant points, this method only checks those points which would move under extension. If a point is invariant, ie it is so close to nCentre that it would not be moved, it is disregarded. It is up to the various extend methods to ensure that these invariant points actually *stay* invariant! Errors: See also: ********************************************************************************************/ INT32 Extender::CheckInvalidShrinkingPoints( INT32 nCentre, INT32 nDelta, INT32 nOffset, INT32 nNumPoints, const DocCoord* doccArray, BOOL bExamineX ) { // return with an all-clear if delta is zero. if (nDelta == 0) return -1; // test the points we were given, and determine the furthest we can move them without // dragging any into the dead zone, defined as the limit value +/- our buffer size. // this value will be the least of all the maximum distances that each point can // shrink by. INT32 maxShrink, smallest_maxShrink = INT32_MAX; // each point should be offset by nOffset so for testing, we 'un-offset' the centre. INT32 myCentre = nCentre - nOffset; // nDelta < 0 means we are being dragged down or left. // we're checking that each point can stretch by nDelta down towards nCentre. // if it can't, we see how far it _can_ go. if (nDelta < 0) { // validate the x-component of the coordinates. if (bExamineX) { for (INT32 i = 0; i < nNumPoints; i ++) { maxShrink = CheckLimits(myCentre, nDelta, doccArray[i].x); if (maxShrink >= 0 && smallest_maxShrink > maxShrink) smallest_maxShrink = maxShrink; } } // validate the y-component of the coordinates. else { for (INT32 i = 0; i < nNumPoints; i ++) { maxShrink = CheckLimits(myCentre, nDelta, doccArray[i].y); if (maxShrink >= 0 && smallest_maxShrink > maxShrink) smallest_maxShrink = maxShrink; } } } // nDelta > 0 means we are being dragged up or right. // we're checking that each point can stretch by nDelta up towards nCentre. // if it can't, we see how far it _can_ go. else { // validate the x-component of the coordinates. if (bExamineX) { for (INT32 i = 0; i < nNumPoints; i ++) { maxShrink = CheckLimits(myCentre, -nDelta, myCentre - doccArray[i].x + myCentre); if (maxShrink >= 0 && smallest_maxShrink > maxShrink) smallest_maxShrink = maxShrink; } } // validate the y-component of the coordinates. else { for (INT32 i = 0; i < nNumPoints; i ++) { maxShrink = CheckLimits(myCentre, -nDelta, myCentre - doccArray[i].y + myCentre); if (maxShrink >= 0 && smallest_maxShrink > maxShrink) smallest_maxShrink = maxShrink; } } } return smallest_maxShrink == INT32_MAX ? -1 : smallest_maxShrink; } /******************************************************************************************** > inline INT32 Extender::CheckLimits(const INT32 limit, const INT32 delta, const INT32 value) Author: Karim_MacDonald (Xara Group Ltd) Created: 07/12/1999 Inputs: limit the limiting value to use. delta the amount to attempt to shrink by - should always be negative. value the value to validate. Outputs: Returns: -1 if the validation was successful, non-negative integer value otherwise. Purpose: Checks that the given value may be reduced by the amount delta without going below the given limit value. If it cannot, we return the most which the given value can be safely reduced by. A preset buffer value is applied, defining a 'dead zone' around the limit, into which the value may not be reduced. All calculations performed within this method take account of this dead zone. Also note that if a value begins within the dead zone, then an all-clear value of -1 will be returned, as we don't want this invariant point to interfere with shrinking of other values. The actual extension code should always check for and not alter these points. Errors: ERROR3 if delta is non-negative (ie >= 0). See also: ********************************************************************************************/ inline INT32 Extender::CheckLimits(const INT32 limit, const INT32 delta, const INT32 value) { // data validation. ERROR3IF(delta >= 0, "Extender::CheckLimits called with delta non-negative!"); // we return an all-clear if the test value starts in the buffer zone. if (value >= limit - EXT_SHRINK_BUFFER && value <= limit + EXT_SHRINK_BUFFER) return -1; // give the all-clear if the test value lies below the limit value already. // also give the all-clear if the test value will not be drawn down into the dead-zone. if (value < limit - EXT_SHRINK_BUFFER || value + delta > limit + EXT_SHRINK_BUFFER) return -1; // ok, we're invalid - return the safe shrinkage distance for the test value. return value - (limit + EXT_SHRINK_BUFFER + 1); } /******************************************************************************************** > void Extender::CalculateExtendParams(ExtendParams* pEPS, BYTE fExtendFlags, const DocRect& drTriggerRelation, const DocRect& drTargetRelation, const DocRect& drNewTrigger, const DocRect& drLastTarget ) Author: Karim_MacDonald (Xara Group Ltd) Created: 06/12/1999 Inputs: pEPS pointer to an ExtendParams object to fill. fExtendFlags bitwise flags, describing any combination of x- or y- stretching or extending, except that you may not extend and stretch along an axis at the same time. Values: X_EXTEND the node should be extended along the x-axis Y_EXTEND the node should be extended along the y-axis X_STRETCH the node should be stretched along the x-axis Y_STRETCH the node should be stretched along the y-axis drTriggerRelation relationship rectangle - the bounding box of the trigger sets when the relationship is defined. drTargetRelation relationship rectangle - the bounding box of the target sets when the relationship is defined. drNewTrigger bounding box of sets of triggers which started the call to this function. drLastTrigger the bounding box of the trigger set(s) before it changed. drLastTarget the bounding box of the target set before it is extended. pdrDifference a rectangle containing four difference parameters which may be used to alter the extend. ok to omit or be NULL. Outputs: *pEPS will be set up to contain extend information derived from the inputs. Purpose: Given the rectangles and flags which define an extension, this function derives the bare information which a Node requires to perform the extension on itself. Errors: ERROR3 in debug builds if pEPS is NULL. See also: Extender::Extend(). ********************************************************************************************/ void Extender::CalculateExtendParams(ExtendParams* pEPS, BYTE fExtendFlags, const DocRect& drTriggerRelation, const DocRect& drTargetRelation, const DocRect& drNewTrigger, const DocRect& drLastTrigger, const DocRect& drLastTarget, const DocRect* pdrDifference, const BOOL ExtendAroundTarget) { // validate input. ERROR3IF(pEPS == NULL, "Extender::CalculateExtendParams- pEPS is NULL!"); // use the relationship rects and the new trigger rect to calculate the new target rect. DocRect drNewTarget(drNewTrigger); // x-extend - the new target rect must be x-offset from new-trigger on each // side, by the same amount as target-relation from trigger-relation. if (fExtendFlags & X_EXTEND) { // if the trigger and target-relation rects do not overlap in the x-direction, // then the new target rect must be x-offset from new-trigger on its near-side, // by the same amount as target-relation from trigger-relation. if (drTargetRelation.lo.x > drTriggerRelation.hi.x) { drNewTarget.lo.x = drNewTrigger.hi.x + (drTargetRelation.lo.x - drTriggerRelation.hi.x); drNewTarget.hi.x = drNewTrigger.hi.x + (drTargetRelation.hi.x - drTriggerRelation.hi.x); } else if (drTargetRelation.hi.x < drTriggerRelation.lo.x) { drNewTarget.lo.x = drNewTrigger.lo.x - (drTriggerRelation.lo.x - drTargetRelation.lo.x); drNewTarget.hi.x = drNewTrigger.lo.x - (drTriggerRelation.lo.x - drTargetRelation.hi.x); } // normal extend behaviour - distances between corresponding sides are kept constant. else { drNewTarget.lo.x += drTargetRelation.lo.x - drTriggerRelation.lo.x; drNewTarget.hi.x += drTargetRelation.hi.x - drTriggerRelation.hi.x; // if the target's new sides are coincident or invalid, then // they need to be moved back into valid positions. if (drNewTarget.lo.x >= drNewTarget.hi.x) { drNewTarget.hi.x = drNewTarget.lo.x + 2; } } } else if (fExtendFlags & X_STRETCH) { double a = drNewTrigger.Width() / (double)drTriggerRelation.Width(); INT32 b = drNewTrigger.hi.x - (INT32)(a * drTriggerRelation.hi.x); drNewTarget.lo.x = b + (INT32)(a * drTargetRelation.lo.x); drNewTarget.hi.x = b + (INT32)(a * drTargetRelation.hi.x); } // y-extend - the new target rect must be y-offset from new-trigger on top and // bottom, by the same amount as target-relation from trigger-relation. if (fExtendFlags & Y_EXTEND) { // if the trigger and target-relation rects do not overlap in the y-direction, // then the new target rect must be y-offset from new-trigger on its near-side, // by the same amount as target-relation from trigger-relation. if (drTargetRelation.lo.y > drTriggerRelation.hi.y) { drNewTarget.lo.y = drNewTrigger.hi.y + (drTargetRelation.lo.y - drTriggerRelation.hi.y); drNewTarget.hi.y = drNewTrigger.hi.y + (drTargetRelation.hi.y - drTriggerRelation.hi.y); } else if (drTargetRelation.hi.y < drTriggerRelation.lo.y) { drNewTarget.lo.y = drNewTrigger.lo.y - (drTriggerRelation.lo.y - drTargetRelation.lo.y); drNewTarget.hi.y = drNewTrigger.lo.y - (drTriggerRelation.lo.y - drTargetRelation.hi.y); } // normal extend behaviour - distances between corresponding sides are kept constant. else { drNewTarget.lo.y += drTargetRelation.lo.y - drTriggerRelation.lo.y; drNewTarget.hi.y += drTargetRelation.hi.y - drTriggerRelation.hi.y; // if the target's new top and bottom are coincident or invalid, then // they need to be moved back into valid positions. if (drNewTarget.lo.y >= drNewTarget.hi.y) { drNewTarget.hi.y = drNewTarget.lo.y + 2; } } } else if (fExtendFlags & Y_STRETCH) { double a = drNewTrigger.Height() / (double)drTriggerRelation.Height(); INT32 b = drNewTrigger.hi.y - (INT32)(a * drTriggerRelation.hi.y); drNewTarget.lo.y = b + (INT32)(a * drTargetRelation.lo.y); drNewTarget.hi.y = b + (INT32)(a * drTargetRelation.hi.y); } // use the old and new target rectangles to calculate our extension working values. // extend flags. pEPS->fExtendFlags = fExtendFlags; // the start- and end- centres of extension + the offset between them. // Karim 14/03/2000 - changed to use the trigger set bounds if their old centre // lies within the bounds of the old target set. if (!ExtendAroundTarget && drLastTarget.ContainsCoord(drLastTrigger.Centre())) { pEPS->doccStartCentre = drLastTrigger.Centre(); pEPS->doccEndCentre = drNewTrigger.Centre(); pEPS->doccOffset = pEPS->doccEndCentre - pEPS->doccStartCentre; } else { pEPS->doccStartCentre = drLastTarget.Centre(); pEPS->doccEndCentre = drNewTarget.Centre(); pEPS->doccOffset = pEPS->doccEndCentre - pEPS->doccStartCentre; } // magnitudes of extension. note that under extension, objects are first offset // from start- to end- centre, then extended. this makes extension mirror-symmetric // so that xinc == xdec (+/- 1). pEPS->xinc = drNewTarget.hi.x - (drLastTarget.hi.x + pEPS->doccOffset.x); pEPS->xdec = (drLastTarget.lo.x + pEPS->doccOffset.x) - drNewTarget.lo.x; pEPS->yinc = drNewTarget.hi.y - (drLastTarget.hi.y + pEPS->doccOffset.y); pEPS->ydec = (drLastTarget.lo.y + pEPS->doccOffset.y) - drNewTarget.lo.y; // scale factors, if automatic _stretching_ is required. pEPS->xscale = drNewTarget.Width() / (double)drLastTarget.Width(); pEPS->yscale = drNewTarget.Height() / (double)drLastTarget.Height(); // if our difference rect is non-NULL, then we may need to substitute // xinc, xdec, yinc, ydec with negative copies of some of its values. if (pdrDifference != NULL) { if (pdrDifference->lo.x != INT32_MAX) pEPS->xdec = -pdrDifference->lo.x; if (pdrDifference->lo.y != INT32_MAX) pEPS->ydec = -pdrDifference->lo.y; if (pdrDifference->hi.x != INT32_MAX) pEPS->xinc = -pdrDifference->hi.x; if (pdrDifference->hi.y != INT32_MAX) pEPS->yinc = -pdrDifference->hi.y; } // TODO: should probably not use these, but the equivalent of b (used above) instead. pEPS->doccScaleStart = drLastTarget.lo; pEPS->doccScaleEnd = drNewTarget.lo; // set the various dead-zones; these depend on the signs of the expansions in each dirn. pEPS->xincExtendBuffer = (pEPS->xinc >= 0) ? EXT_EXPAND_BUFFER : EXT_SHRINK_BUFFER; pEPS->xdecExtendBuffer = (pEPS->xdec >= 0) ? EXT_EXPAND_BUFFER : EXT_SHRINK_BUFFER; pEPS->yincExtendBuffer = (pEPS->yinc >= 0) ? EXT_EXPAND_BUFFER : EXT_SHRINK_BUFFER; pEPS->ydecExtendBuffer = (pEPS->ydec >= 0) ? EXT_EXPAND_BUFFER : EXT_SHRINK_BUFFER; } /******************************************************************************************** > INT32 Extender::CheckInvalidExpandingPoints( INT32 nCentre, INT32 nDelta, INT32 nOffset, INT32 nNumPoints, const DocCoord* doccArray, BOOL bExamineX ) Author: Karim_MacDonald (Xara Group Ltd) Created: 25/01/2000 Inputs: nCentre the centre x- or y- value from which point are expanding. nDelta (currently unused) the distance by which the points are moving. nOffset an offset which should be applied to the points before testing. nNumPoints the number of points to test. doccArray the array of points to test. bExamineX whether to examine x-coords (TRUE) or y-coords (FALSE). Outputs: Returns: Negative value if all points can expand safely away from nCentre, or Zero if any of the points cannot be safely moved away. Purpose: Two checks: No points may lie inside the dead-zone. Due to rounding errors, the centre-point of an object depends on whether its width is even or odd. There is a potential for error here, if a point lying one millipoint outside the dead zone is moved out from the centre, and the operation then reversed. If the centre-point changes position due to a new width then the point may be moving back *inside* the dead zone, which will be disallowed -> irreversible operation. This method checks for these points. (boy, what a mouthful!) Assumptions: That the extension being checked is symmetric - ie all points are moving away from the centre point, regardless of their position relative to it. Errors: See also: ********************************************************************************************/ INT32 Extender::CheckInvalidExpandingPoints( INT32 nCentre, INT32 nDelta, INT32 nOffset, INT32 nNumPoints, const DocCoord* doccArray, BOOL bExamineX ) { // return with an all-clear if delta is zero. if (nDelta == 0) return -1; // each point should be offset by nOffset, so instead we 'un-offset' the centre. // INT32 myCentre = nCentre - nOffset; /* * Karim 11/05/2000 * Commented out, as this test is being moved into the actual extension code - rather than * have invariant points limit the extension, they will have no effect, and will instead * just not themselves extend. * // The test we do is simple - ensure that no points lie within one millipoint of the // dead zone. We only need one invalid point for the test to fail. BOOL bPassedTest = TRUE; if (bExamineX) { for (INT32 i = 0; i < nNumPoints && bPassedTest; i ++) { bPassedTest = (doccArray[i].x > (myCentre + EXT_EXPAND_BUFFER)) || (doccArray[i].x < (myCentre - EXT_EXPAND_BUFFER)); } } else { for (INT32 i = 0; i < nNumPoints && bPassedTest; i ++) { bPassedTest = (doccArray[i].y > (myCentre + EXT_EXPAND_BUFFER)) || (doccArray[i].y < (myCentre - EXT_EXPAND_BUFFER)); } } return bPassedTest ? -1 : 0; */ return -1; }