// $Id: nodetxts.cpp 1445 2006-07-14 20:15:02Z phil $ /* @@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 "nodetxts.h" // Code headers //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "blobs.h" #include "cameleps.h" #include "contmenu.h" //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fillattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "lineattr.h" #include "nodetext.h" #include "nodetxtl.h" #include "objchge.h" //#include "opbreak.h" //#include "ops.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "pathproc.h" //#include "rndrgn.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "trans2d.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "textops.h" //#include "tool.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "txtattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "textfuns.h" #include "nativeps.h" // The old style EPS native filter, used in v1.1 #include "ai_epsrr.h" #include "progress.h" //#include "camfiltr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "cxftext.h" //#include "cxfrec.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "impstr.h" #include "unicdman.h" #include "extender.h" // for ExtendParams #include "ngcore.h" // NameGallery, for stretching functionality #include "nodepath.h" // Resource headers //#include "mario.h" //#include "peter.h" //#include "simon.h" #include "textinfo.h" #ifdef RALPH #include "ralphcri.h" // For RalphCriticalSection #endif #include "opbevel.h" // For determining if a gradient fill is used in a TextStory. DECLARE_SOURCE("$Revision: 1445 $") CC_IMPLEMENT_DYNAMIC(BaseTextClass, NodeRenderableInk) CC_IMPLEMENT_MEMDUMP(TextStoryInfo, CC_CLASS_MEMDUMP ); CC_IMPLEMENT_DYNAMIC(TextStory, BaseTextClass) // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW // statics ... TextStory* TextStory::pFocusStory = NULL; ///////////////////////////////////////////////////////////////////////////////////////////// // BaseTextClass methods /******************************************************************************************** > void BaseTextClass::Init() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: init function called by various constructors ********************************************************************************************/ void BaseTextClass::Init() { mModifiedByOpFlag = TRUE; // ensures node will have metrics recached mDescendantModifiedByOpFlag = TRUE; // ensures any children will have metrics cached mAffectedFlag = TRUE; // ensures node will be includes in redraw rect mDescendantAffectedFlag = TRUE; // ensures any children will be included in redraw mAlreadyWritten = FALSE; // ensures the char will be written to v2 files } /******************************************************************************************** > BaseTextClass::BaseTextClass() Author: Simon_Maneggio (Xara Group Ltd) Created: 21/12/94 Purpose: Simple BaseTextClass constructor ********************************************************************************************/ BaseTextClass::BaseTextClass(): NodeRenderableInk() // Call the base class { Init(); } /******************************************************************************************** > BaseTextClass::~BaseTextClass() Author: Simon_Maneggio (Xara Group Ltd) Created: 20/2/95 Purpose: BaseTextClass destructor ********************************************************************************************/ BaseTextClass::~BaseTextClass() { } /******************************************************************************************** > BaseTextClass::BaseTextClass(Node* ContextNode, AttachNodeDirection Direction) Author: Simon_Maneggio (Xara Group Ltd) Created: 21/12/94 Inputs: ContextNode: Pointer to a node which this node is to be attached to. Direction: Specifies the direction in which the node is to be attached to the ContextNode. The values this variable can take are as follows: PREV : Attach node as a previous sibling of the context node NEXT : Attach node as a next sibling of the context node FIRSTCHILD: Attach node as the first child of the context node LASTCHILD : Attach node as a last child of the context node Purpose: The main BaseTextClass constructor ********************************************************************************************/ BaseTextClass::BaseTextClass(Node* ContextNode, AttachNodeDirection Direction):NodeRenderableInk(ContextNode, Direction) { Init(); } /*********************************************************************************************** > void BaseTextClass::CopyNodeContents(BaseTextClass* NodeCopy) Author: Ed_Cornes (Xara Group Ltd) Created: 3/5/95 Purpose: This method copies the node's contents to the node pointed to by NodeCopy. Assumption: Only called when creating new copies and the constructor sets these flags correctly ***********************************************************************************************/ void BaseTextClass::CopyNodeContents(BaseTextClass* NodeCopy) { // Ask the base class to do its bit NodeRenderableInk::CopyNodeContents(NodeCopy); } /*********************************************************************************************** > void BaseTextClass::PolyCopyNodeContents(NodeRenderable* pNodeCopy) Author: Phil_Martin (Xara Group Ltd) Created: 18/12/2003 Outputs: - Purpose: Polymorphically copies the contents of this node to another Errors: An assertion failure will occur if NodeCopy is NULL Scope: protected ***********************************************************************************************/ void BaseTextClass::PolyCopyNodeContents(NodeRenderable* pNodeCopy) { ENSURE(pNodeCopy, "Trying to copy a node's contents into a NULL node"); ENSURE(pNodeCopy->IsKindOf(CC_RUNTIME_CLASS(BaseTextClass)), "PolyCopyNodeContents given wrong dest node type"); if (pNodeCopy->IsKindOf(CC_RUNTIME_CLASS(BaseTextClass))) CopyNodeContents((BaseTextClass*)pNodeCopy); } /******************************************************************************************** > BOOL BaseTextClass::ReCacheNodeAndDescendantsMetrics() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Returns: FALSE if fails Purpose: Recache metrics in (this node and any text descendants) which have been ModifiedByOp ********************************************************************************************/ BOOL BaseTextClass::ReCacheNodeAndDescendantsMetrics(FormatRegion* pFormatRegion) { PORTNOTE("text","BaseTextClass::ReCacheNodeAndDescendantsMetrics - do nothing"); #ifndef DISABLE_TEXT_RENDERING BOOL ok=TRUE; if (NodeOrDescendantModifiedByOp()) { pFormatRegion->SaveContext(); // scan immediate children rendering any text attrs, and recaching metrics on any text node Node* pNode = FindFirstChild(); while (pNode!=NULL) { if (pNode->IsABaseTextClass()) ok = ok && ((BaseTextClass*)pNode)->ReCacheNodeAndDescendantsMetrics(pFormatRegion); else if (pNode->IsKindOfTextAttribute()) pNode->Render(pFormatRegion); pNode = pNode->FindNext(); } // now all child text attrs rendered, recache this node's metrics if (this->ModifiedByOp()) ok = ok && ReCacheMetrics(pFormatRegion); pFormatRegion->RestoreContext(); } return ok; #else return FALSE; #endif } /******************************************************************************************** > virtual BOOL BaseTextClass::ReCacheMetrics(FormatRegion* pFormatRegion) Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Inputs: pFormatRegion - Returns: FALSE if fails Purpose: Recache metrics in text node - does nothing by default ********************************************************************************************/ BOOL BaseTextClass::ReCacheMetrics(FormatRegion* pFormatRegion) { return TRUE; } /******************************************************************************************** > void BaseTextClass::ClearFlags() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: clear all flags in the node ********************************************************************************************/ void BaseTextClass::ClearFlags() { mModifiedByOpFlag = FALSE; mDescendantModifiedByOpFlag = FALSE; mAffectedFlag = FALSE; mDescendantAffectedFlag = FALSE; mAlreadyWritten = FALSE; } /******************************************************************************************** > void BaseTextClass::ClearNodeAndDescendantsFlags() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: Clear flags in node and all text descendants Assumption: if any flag is set the AffectedFlag is set, and if AffectedFlag is set, its parents have DescendantAffectedFlag set ********************************************************************************************/ void BaseTextClass::ClearNodeAndDescendantsFlags() { if (IS_A(this, CaretNode)) SetOpPermission(PERMISSION_UNDEFINED); if (NodeOrDescendantAffected()) { Node* pNode = FindFirstChild(); while (pNode!=NULL) { if (pNode->IsABaseTextClass()) ((BaseTextClass*)pNode)->ClearNodeAndDescendantsFlags(); pNode = pNode->FindNext(); } ClearFlags(); } } /******************************************************************************************** > void BaseTextClass::FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: Flag node and all text descendants that they have been directly modified by an op and flag all text parents that they have a text descendant directly modified by an op ********************************************************************************************/ void BaseTextClass::FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp() { FlagModifiedByOp(); FlagDescendantsModifiedByOp(); FlagParentsHaveDescendantModifiedByOp(); } /******************************************************************************************** > void BaseTextClass::FlagParentsHaveDescendantModifiedByOp() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: Flag all text parents that they have a text descendant directly modified by an op ********************************************************************************************/ void BaseTextClass::FlagParentsHaveDescendantModifiedByOp() { // get parent Node* pNode = this->FindParent(); if (pNode==NULL) { // Can be called when text block is being inserted and does not yet have a parent??? // ERROR3("BaseTextClass::FlagParentsHaveDescendantModifiedByOp() - node has no parent") return; } // if parent is a text node, set it's flag and flag it's parents if (pNode->IsABaseTextClass()) { // if 'DescendantModifiedByOp' assume all it's parents already have this flag set BaseTextClass* pBTC = (BaseTextClass*)pNode; if (pBTC->DescendantModifiedByOp()==FALSE) { pBTC->FlagDescendantModifiedByOp(); pBTC->FlagParentsHaveDescendantModifiedByOp(); } } } /******************************************************************************************** > void BaseTextClass::FlagDescendantsModifiedByOp() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: Flag all text descendants as being directly modified by an op ********************************************************************************************/ void BaseTextClass::FlagDescendantsModifiedByOp() { Node* pNode = FindFirstChild(); while (pNode!=NULL) { if (pNode->IsABaseTextClass()) { BaseTextClass* pBTC = (BaseTextClass*)pNode; pBTC->FlagModifiedByOp(); pBTC->FlagDescendantsModifiedByOp(); } pNode = pNode->FindNext(); } } /******************************************************************************************** > void BaseTextClass::FlagNodeAndDescendantsAffectedAndParentsHaveDescendantAffected() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: Flag node and all text descendants that they have been 'affected' and flag all text parents that they have a text descendant 'affected' ********************************************************************************************/ void BaseTextClass::FlagNodeAndDescendantsAffectedAndParentsHaveDescendantAffected() { FlagAffected(); FlagDescendantsAffected(); FlagParentsHaveDescendantAffected(); } /******************************************************************************************** > void BaseTextClass::FlagParentsHaveDescendantAffected() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: Flag all text parents that they have a text descendant 'affected' ********************************************************************************************/ void BaseTextClass::FlagParentsHaveDescendantAffected() { // get parent Node* pNode = this->FindParent(); if (pNode==NULL) { ERROR3("BaseTextClass::FlagParentsHaveDescendantAffected() - node has no parent"); return; } // if parent is a text node, set it's flag and flag it's parents if (pNode->IsABaseTextClass()) { // if 'DescendantAffected' assume all it's parents already have this flag set BaseTextClass* pBTC = (BaseTextClass*)pNode; if (pBTC->DescendantAffected()==FALSE) { pBTC->FlagDescendantAffected(); pBTC->FlagParentsHaveDescendantAffected(); } } } /******************************************************************************************** > void BaseTextClass::FlagDescendantsAffected() Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Purpose: Flag all text descendants as being 'affected' ********************************************************************************************/ void BaseTextClass::FlagDescendantsAffected() { Node* pNode = FindFirstChild(); while (pNode!=NULL) { if (pNode->IsABaseTextClass()) { BaseTextClass* pBTC = (BaseTextClass*)pNode; pBTC->FlagAffected(); pBTC->FlagDescendantsAffected(); } pNode = pNode->FindNext(); } } /******************************************************************************************** > void BaseTextClass:: FlagPrevTextCharAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp() Author: Jonathan_Payne (Xara Group Ltd) Created: 17/10/2000 Purpose: Flags the previous text node (on the current line only) as modified by op (this is useful for kerning where the previous char to the one that is inserted needs to be updated so it can find out if it is the left half of a kern pair) ********************************************************************************************/ void BaseTextClass::FlagPrevTextCharAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp() { if (!this->IsAVisibleTextNode()) return; VisibleTextNode* pVTN = static_cast(this); TextChar* pTC = pVTN->FindPrevTextCharInStory(); BaseTextClass* pBTC = pTC; if (pBTC) { pBTC->FlagModifiedByOp(); pBTC->FlagDescendantsModifiedByOp(); pBTC->FlagParentsHaveDescendantModifiedByOp(); } } /******************************************************************************************** > void BaseTextClass::UnionNodeAndDescendantsBounds(DocRect* pBounds, BOOL ParentModifiedByOp=FALSE) Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Inputs: ParentModified - indicates if parent is ModifiedByOp Output: pBounds - updated to include bounds of all affected nodes Purpose: Union bounds of the node and any text descendants 'affected' with the given bounds Any text nodes which are also 'ModifiedByOp' also have their BlobBounds included as they were selected at the start of the op. NOTES: All 'affected' text descendants must have their bounds invalidated before the parent can have its bounds invalidated and recached. But (w.r.t. blob bounds) since a selected node can have no selected descendants AND BlobBounds are not dependent on descendants, if a node's parent is ModifiedByOp this node's blob bounds do not need to be included Should not be including BlobBounds in undo actions as they don't scale except if they're text blobs! - what about line & story blobs? ********************************************************************************************/ void BaseTextClass::UnionNodeAndDescendantsOldAndNewBounds(DocRect* pBounds, BOOL ParentModifiedByOp) { if (NodeOrDescendantAffected()) { Node* pNode = FindFirstChild(); while (pNode!=NULL) { if (pNode->IsABaseTextClass()) ((BaseTextClass*)pNode)->UnionNodeAndDescendantsOldAndNewBounds(pBounds,ModifiedByOp()); pNode = pNode->FindNext(); } } if (Affected()) { *pBounds = pBounds->Union(GetBoundingRect()); if (ModifiedByOp() && !ParentModifiedByOp) *pBounds = pBounds->Union(GetBlobBoundingRect()); InvalidateBoundingRect(); *pBounds = pBounds->Union(GetBoundingRect()); if (ModifiedByOp() && !ParentModifiedByOp) *pBounds = pBounds->Union(GetBlobBoundingRect()); } } /******************************************************************************************** > virtual BOOL BaseTextClass::AllowOp(ObjChangeParam* pParam, BOOL SetOpPermissionState = TRUE, BOOL DoPreTriggerEdit = TRUE) Author: Ed_Cornes (Xara Group Ltd) ; Karim 19/01/2000 Created: 8/5/95 Inputs: pParam - pointer to the change parameter object SetOpPermissionState - TRUE to set the Nodes permission flags DoPreTriggerEdit = if TRUE then calls NameGallery::PreTriggerEdit. *Must* be TRUE if the calling Op may make any nodes change their bounds, eg move, line width, cut. Use TRUE if unsure. Returns: TRUE if the operation can proceede, FALSE to stop it Purpose: Generic AllowOp() for all text nodes except TextStories ********************************************************************************************/ BOOL BaseTextClass::AllowOp(ObjChangeParam* pParam, BOOL SetOpPermissionState, BOOL DoPreTriggerEdit) { ERROR2IF(pParam==NULL,FALSE,"BaseTextClass::AllowOp() - pParam==NULL"); ERROR2IF(Parent==NULL,FALSE,"BaseTextClass::AllowOp() - Parent==NULL"); // must have at least a TextStory // if a parent AllowOp() calls the TextStory AllowOp() this is as far down the tree as we // need to go to ensure the story is maintained correctly, hence the TextStory AllowOp() // does not call any child AllowOp()s // also, for similar reasons, any node in the story need only call it's parents AllowOp()s // and never its children // so we should never be called by a parent AllowOp() ... ERROR3IF(pParam->GetDirection()==OBJCHANGE_CALLEDBYPARENT,"BaseTextClass::AllowOp() - called by parent AllowOp()!"); // decide if we allow it ... BOOL allowed=TRUE; ObjChangeFlags Flags=pParam->GetChangeFlags(); if (pParam->GetDirection()==OBJCHANGE_CALLEDBYCHILD) { // clean out the calling-child ptr, so it doesn't get passed around unintentionally. pParam->SetCallingChild(NULL); if (Flags.ReplaceNode || Flags.MultiReplaceNode || Flags.MoveNode) allowed=FALSE; } // and special case for caret (don't allow it to be deleted or copied) if (IS_A(this,CaretNode) && pParam->GetDirection()==OBJCHANGE_CALLEDBYOP) if (Flags.DeleteNode || Flags.CopyNode) allowed=FALSE; // if we didn't allow it set a reason if (allowed==FALSE) pParam->SetReasonForDenial(_R(IDE_TEXT_USEDBYSTORY)); // if we allowed it, see if our parent allows it if (allowed) { // indicate we're calling from a child AllowOp() ObjChangeDirection OldDirection=pParam->GetDirection(); pParam->SetCallingChild(this); pParam->SetDirection(OBJCHANGE_CALLEDBYCHILD); allowed=Parent->AllowOp(pParam,SetOpPermissionState,DoPreTriggerEdit); pParam->SetDirection(OldDirection); } // if setting permisions ... if (SetOpPermissionState) { // (assume if parent allowed it, it set permission correctly on itself) // (and if called by a child, it did all the pre-op processing required) // (note we should never be called by our parent) if (allowed) allowed=PreOpProcessing(pParam); else SetOpPermission(PERMISSION_DENIED,TRUE); } // if we're ok so far and were asked to do a PreTriggerEdit, then // determine whether the Op may change the bounds of some nodes. // If it may, then call NameGallery::PreTriggerEdit. if (allowed && DoPreTriggerEdit) { // if the Op is non-NULL then query its MayChangeNodeBounds() method. UndoableOperation* pChangeOp = pParam->GetOpPointer(); if (pChangeOp != NULL && pChangeOp->MayChangeNodeBounds() && NameGallery::Instance()) { allowed = NameGallery::Instance()->PreTriggerEdit(pChangeOp, pParam, this); } } // return result (directly, or indirectly via a child AllowOp()) to op return allowed; } /******************************************************************************************** > BOOL BaseTextClass::PreOpProcessing(ObjChangeParam* pParam) Author: Ed_Cornes (Xara Group Ltd) Created: 8/5/95 Inputs: pParam - Returns: FALSE if fails Purpose: Perform all preprocessing required before an op is performed on a text node ********************************************************************************************/ BOOL BaseTextClass::PreOpProcessing(ObjChangeParam* pParam) { // if called by an AllowOp() which itself was NOT called by a child's AllowOp(), do our stuff // (assume if it WAS called by an AllowOp() which itself was called by a child AllowOp() // (this routine would have already been called by a child's AllowOp() which itself was // (called by an AlowOp() called directly by an Op! if (pParam->GetDirection()!=OBJCHANGE_CALLEDBYCHILD || (pParam->GetCallingChild() != NULL && pParam->GetCallingChild()->IsNodePath()) ) { // if the TextStory has already been met by the op, // just union the affected node's bounds with the current redraw rect // else (we've been called for the first time in this op for this story) so, // flag the story has been met, reset the redraw rect, insert a single reformat action for undo TextStory* pTextStory = FindParentStory(); if (pTextStory->NodeOrDescendantModifiedByOp()) pTextStory->UpdateRedrawRect(GetBoundingRect()); else { pTextStory->Validate(); pTextStory->SetRedrawRect(GetBoundingRect()); UndoableOperation* pOp=pParam->GetOpPointer(); if (pOp!=NULL) if (PrePostTextAction::Init(pOp,pOp->GetUndoActionList(),pTextStory,TRUE)==AC_FAIL) return FALSE; } // also need to include blob bounds as obj must be selected to get here! pTextStory->UpdateRedrawRect(GetBlobBoundingRect()); // flag the node is directly modified by the op FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp(); // flag prev char to allow for kerning FlagPrevTextCharAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp(); } return TRUE; } /******************************************************************************************** > virtual CCRuntimeClass* BaseTextClass::GetCurrentAttribGroup() Author: Simon_Maneggio (Xara Group Ltd) Created: 6/4/95 Returns: The current attribute group for all text nodes Purpose: Every Ink object has an associated current attribute group, If an attribute is applied to the object, and it needs to become current then the attribute group specified will get updated. ********************************************************************************************/ CCRuntimeClass* BaseTextClass::GetCurrentAttribGroup() { return (CC_RUNTIME_CLASS(BaseTextClass)); } /******************************************************************************************** > TextStory* BaseTextClass::FindParentStory() Author: Ed_Cornes (Xara Group Ltd) Created: 15/5/95 Returns: pointer to the (parent) text story (else NULL if error) Purpose: get a pointer to the text story associated with this text node NB if 'this' is a text story, return 'this' ********************************************************************************************/ TextStory* BaseTextClass::FindParentStory() { Node* pNode=this; while (IS_A(pNode,TextStory)==FALSE) { pNode = pNode->FindParent(); if (pNode==NULL || pNode->IsABaseTextClass()==FALSE) ERROR2(NULL,"BaseTextClass::FindParentStory() - could not find associated TextStory"); } return (TextStory*)pNode; } /******************************************************************************************** > BOOL BaseTextClass::DoHideNode(UndoableOperation* pUndoOp) Author: Ed_Cornes (Xara Group Ltd) Created: 14/6/96 Inputs: pUndoOp - undoable operation (may be NULL if not undoable) Returns: FALSE if fails Purpose: As UndoableOperation::DoHideNode() BUT can be called non-undoably!!! ********************************************************************************************/ BOOL BaseTextClass::DoHideNode(UndoableOperation* pUndoOp) { // deselect any node to hide to ensure 'parent of selected' flag correct if (IsSelected()) SetSelected(FALSE); if (pUndoOp!=NULL) return pUndoOp->DoHideNode(this,FALSE,NULL,TRUE); // delete the node // (no need to update selection as non-undoable code only called for clipboard/import) // (no need to invalidate bounding box as text has it's own sytem to handle this) CascadeDelete(); delete this; return TRUE; } /******************************************************************************************** > BOOL BaseTextClass::DoInsertNewNode(UndoableOperation* pUndoOp, Node* pContextNode, AttachNodeDirection Direction) Author: Ed_Cornes (Xara Group Ltd) Created: 14/6/96 Inputs: pUndoOp - undoable operation (may be NULL if not undoable) pContextNode - see UndoableOperation::DoInsertNewNode() Direction - see UndoableOperation::DoInsertNewNode() Returns: FALSE if fails Purpose: As UndoableOperation::DoInsertNewNode() BUT can be called non-undoably!!! ********************************************************************************************/ BOOL BaseTextClass::DoInsertNewNode(UndoableOperation* pUndoOp, Node* pContextNode, AttachNodeDirection Direction) { BOOL ok = TRUE; if (pUndoOp!=NULL) ok = pUndoOp->DoInsertNewNode(this, pContextNode, Direction, FALSE,FALSE,FALSE,FALSE); else { // Insert the NewNode into the tree // (no need to invalidate bounding box as text has it's own sytem to handle this) // (assume layer is not locked and is visible) // (do not select the object, normalise it's attrs as this is done later) AttachNode(pContextNode, Direction); } if (ok) this->FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp(); // flag prev char to allow for kerning FlagPrevTextCharAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp(); return ok; } /******************************************************************************************** > BOOL BaseTextClass::DoLocaliseCommonAttributes( UndoableOperation* pUndoOp, BOOL CheckForDuplicates, BOOL Global, AttrTypeSet* pAffectedAttrTypes) Author: Ed_Cornes (Xara Group Ltd) Created: 14/6/96 Inputs: pUndoOp - undoable operation (may be NULL if not undoable) CheckForDuplicated - see UndoableOperation::DoLocaliseCommonAttributes() Global - see UndoableOperation::DoLocaliseCommonAttributes() pAffectedAttrTypes - see UndoableOperation::DoLocaliseCommonAttributes() Returns: FALSE if fails Purpose: As UndoableOperation::DoLocaliseCommonAttributes() BUT can be called non-undoably!!! ********************************************************************************************/ BOOL BaseTextClass::DoLocaliseCommonAttributes( UndoableOperation* pUndoOp, BOOL CheckForDuplicates, BOOL Global, AttrTypeSet* pAffectedAttrTypes) { if (pUndoOp!=NULL) return pUndoOp->DoLocaliseCommonAttributes(this,CheckForDuplicates,Global,pAffectedAttrTypes); return LocaliseCommonAttributes(CheckForDuplicates,Global,pAffectedAttrTypes); } /******************************************************************************************** > BOOL BaseTextClass::DoFactorOutCommonChildAttributes(UndoableOperation* pUndoOp, BOOL Global, AttrTypeSet* pAffectedAttrTypes) Author: Ed_Cornes (Xara Group Ltd) Created: 14/6/96 Inputs: pUndoOp - undoable operation (may be NULL if not undoable) Global - see UndoableOperation::DoFactorOutCommonChildAttributes() pAffectedAttrTypes - see UndoableOperation::DoFactorOutCommonChildAttributes() Returns: FALSE if fails Purpose: As UndoableOperation::DoFactorOutCommonChildAttributes() BUT can be called non-undoably!!! ********************************************************************************************/ BOOL BaseTextClass::DoFactorOutCommonChildAttributes(UndoableOperation* pUndoOp, BOOL Global, AttrTypeSet* pAffectedAttrTypes) { if (pUndoOp!=NULL) return pUndoOp->DoFactorOutCommonChildAttributes(this,Global,pAffectedAttrTypes); return FactorOutCommonChildAttributes(Global,pAffectedAttrTypes); } /******************************************************************************************** > void BaseTextClass::GetAttachNodeAndDirectionToAttachFirstChildObject(Node** ppNode, AttachNodeDirection* pDir) Author: Ed_Cornes (Xara Group Ltd) Created: 14/6/96 Outputs: ppNode - context node pDir - attach direction ********************************************************************************************/ void BaseTextClass::GetAttachNodeAndDirectionToAttachFirstChildObject(Node** ppNode, AttachNodeDirection* pDir) { *ppNode = this->FindFirstChild(CC_RUNTIME_CLASS(NodeRenderableInk)); *pDir = PREV; // if no object to attach before, try next to last non-object node if (*ppNode==NULL) { *ppNode = this->FindLastChild(); *pDir = NEXT; } // if no child nodes, set first child of this if (*ppNode==NULL) { *ppNode = this; *pDir = FIRSTCHILD; } } /******************************************************************************************** > BOOL BaseTextClass::AddNonLineLevelDescendantAttrsToSet(AttrTypeSet* pAttrTypeSet) Author: Ed_Cornes (Xara Group Ltd) Created: 14/6/96 Inputs: pAttrTypeSet - Outputs: pAttrTypeSet - Returns: FALSE if fails ********************************************************************************************/ BOOL BaseTextClass::AddNonLineLevelDescendantAttrsToSet(AttrTypeSet* pAttrTypeSet) { ERROR2IF(pAttrTypeSet==NULL,FALSE,"BaseTextClass::AddNonLineLevelDescendantAttrsToSet() - pAttrTypeSet==NULL"); Node* pNode = FindFirstChild(); while (pNode!=NULL) { if ( pNode->IsAnAttribute() && !((NodeAttribute*)pNode)->IsALineLevelAttrib() ) { NodeAttribute* pAttr = (NodeAttribute*)pNode; if (!pAttrTypeSet->AddToSet(pAttr->GetAttributeType())) return FALSE; } else if (pNode->IsABaseTextClass()) if (!((BaseTextClass*)pNode)->AddNonLineLevelDescendantAttrsToSet(pAttrTypeSet)) return FALSE; pNode = pNode->FindNext(); } return TRUE; } ///////////////////////////////////////////////////////////////////////////////////////////// // TextStoryInfo methods /******************************************************************************************** > TextStoryInfo::TextStoryInfo() Author: Ed_Cornes (Xara Group Ltd) Created: 4/2/96 Purpose: default constructor! ********************************************************************************************/ TextStoryInfo::TextStoryInfo() { pUndoOp = NULL; WordWrap = TRUE; StoryWidth = 0; // assume text at a point WordWrapping = FALSE; pPath = NULL; PathLength = 0; PathClosed = FALSE; UnitDirectionVectorX = 0; UnitDirectionVectorY = 1; LeftPathIndent = 0; RightPathIndent = 0; DescentLine = 0; DescentLineValid = FALSE; } ///////////////////////////////////////////////////////////////////////////////////////////// // TextStory methods /******************************************************************************************** > TextStory::TextStory() Author: Simon_Maneggio (Xara Group Ltd) Created: 21/12/94 Purpose: Simple TextStory constructor, it is required so that SimpleCopy will work. ********************************************************************************************/ TextStory::TextStory(): BaseTextClass() // Call the base class { Init(); } /******************************************************************************************** > TextStory::~TextStory() Author: Peter_Arnold (Xara Group Ltd) Created: 20/2/95 Purpose: The destructor currently just checks to see if the TextStory being deleted still has the input focus, and clears it if it has. One situation where this occurs is when deleting a document. This dosen't clear the selection ********************************************************************************************/ TextStory::~TextStory() { if (GetFocusStory() == this) SetFocusStory(NULL); if (pImportedStringList != NULL) { pImportedStringList->DeleteAll(); delete pImportedStringList; pImportedStringList = NULL; } } /******************************************************************************************** > TextStory::TextStory(Node* ContextNode, AttachNodeDirection Direction) Author: Simon_Maneggio (Xara Group Ltd) Created: 21/12/94 Inputs: ContextNode: Pointer to a node which this node is to be attached to. Direction: Specifies the direction in which the node is to be attached to the ContextNode. The values this variable can take are as follows: PREV : Attach node as a previous sibling of the context node NEXT : Attach node as a next sibling of the context node FIRSTCHILD: Attach node as the first child of the context node LASTCHILD : Attach node as a last child of the context node Purpose: The main TextStory constructor ********************************************************************************************/ TextStory::TextStory(Node* ContextNode, AttachNodeDirection Direction):BaseTextClass(ContextNode, Direction) { Init(); } /******************************************************************************************** > void TextStory::Init() Author: Peter_Arnold (Xara Group Ltd) Created: 16/02/95 Outputs: Set member variables to default values. Purpose: Initialies the member variables of the TextStory ********************************************************************************************/ void TextStory::Init() { StoryMatrix = Matrix(); RedrawRect = DocRect(0,0,0,0); StoryWidth = 0; mLeftIndent = 0; mRightIndent = 0; CachedCaret = NULL; TextOnPathReversed = FALSE; TextOnPathTangential = TRUE; PrintAsShapes = FALSE; WordWrapping = FALSE; BeingCopied = FALSE; ImportFormatWidth = 0; ImportBaseShift = AlignBaseline; CharsScale = 1; CharsAspect = 1; CharsRotation = 0; CharsShear = 0; pImportedStringList = NULL; // Used when importing strings in the v2 file format AutoKern = true; } /******************************************************************************************** > Node* TextStory::SimpleCopy() Author: Simon_Maneggio (Xara Group Ltd) Created: 21/12/94 Returns: A copy of the node, or NULL if we are out of memory Purpose: This method returns a shallow copy of the node with all Node pointers NULL. The function is virtual, and must be defined for all derived classes of Node Errors: If memory runs out when trying to copy, then ERROR is called with an out of memory error and the function returns NULL. ********************************************************************************************/ Node* TextStory::SimpleCopy() { // Make a new TextStory and then copy things into it TextStory* NodeCopy = new TextStory(); ERROR1IF(NodeCopy==NULL, NULL, _R(IDE_NOMORE_MEMORY)); if (NodeCopy) CopyNodeContents(NodeCopy); return NodeCopy; } /*********************************************************************************************** > void TextStory::PolyCopyNodeContents(NodeRenderable* pNodeCopy) Author: Phil_Martin (Xara Group Ltd) Created: 18/12/2003 Outputs: - Purpose: Polymorphically copies the contents of this node to another Errors: An assertion failure will occur if NodeCopy is NULL Scope: protected ***********************************************************************************************/ void TextStory::PolyCopyNodeContents(NodeRenderable* pNodeCopy) { ENSURE(pNodeCopy, "Trying to copy a node's contents into a NULL node"); ENSURE(IS_A(pNodeCopy, TextStory), "PolyCopyNodeContents given wrong dest node type"); if (IS_A(pNodeCopy, TextStory)) CopyNodeContents((TextStory*)pNodeCopy); } /*********************************************************************************************** > void TextStory::CopyNodeContents(TextStory* NodeCopy) Author: Simon_Maneggio (Xara Group Ltd) Created: 28/4/93 Outputs: A copy of this node Purpose: This method copies the node's contents to the node pointed to by NodeCopy. Errors: An assertion failure will occur if NodeCopy is NULL ***********************************************************************************************/ void TextStory::CopyNodeContents(TextStory* NodeCopy) { // Ask the base class to do its bit BaseTextClass::CopyNodeContents(NodeCopy); // Copy the rest of the data (which should not be that set by the default constructor?) NodeCopy->StoryMatrix = StoryMatrix; NodeCopy->StoryWidth = StoryWidth; NodeCopy->TextOnPathReversed = TextOnPathReversed; NodeCopy->TextOnPathTangential = TextOnPathTangential; NodeCopy->PrintAsShapes = PrintAsShapes; NodeCopy->WordWrapping = WordWrapping; NodeCopy->ImportFormatWidth = ImportFormatWidth; NodeCopy->ImportBaseShift = ImportBaseShift; NodeCopy->CharsScale = CharsScale; NodeCopy->CharsAspect = CharsAspect; NodeCopy->CharsRotation = CharsRotation; NodeCopy->CharsShear = CharsShear; NodeCopy->mLeftIndent = mLeftIndent; NodeCopy->mRightIndent = mRightIndent; NodeCopy->AutoKern = AutoKern; } /******************************************************************************************** > String TextStory::Describe(BOOL Plural, BOOL Verbose = TRUE) Author: Simon_Maneggio (Xara Group Ltd) Created: 21/12/94 Inputs: Plural: Singular or plural description Returns: A string describing the node Purpose: Gives a description of the TextStory node for the status line etc ********************************************************************************************/ String TextStory::Describe(BOOL Plural, BOOL Verbose) { if (Plural) return(String(_R(IDS_DESCRIBE_TEXTSTORYP))); else return(String(_R(IDS_DESCRIBE_TEXTSTORYS))); } /******************************************************************************************** > UINT32 TextStory::GetNodeSize() const Author: Simon_Maneggio (Xara Group Ltd) Created: 21/12/94 Returns: The size of the node in bytes Purpose: For finding the size of the node ********************************************************************************************/ UINT32 TextStory::GetNodeSize() const { return (sizeof(TextStory)); } /******************************************************************************************** > static TextStory* TextStory::CreateTextObject(DocCoord Anchor) Author: Simon_Maneggio (Xara Group Ltd) Created: 25/03/95 Inputs: Anchor - The position the text story should be created at. Returns: A pointer to the root of the text object subtree if successful, else NULL Purpose: Creates a Text object. Errors: An ERROR will be set if we run out of memory ********************************************************************************************/ TextStory* TextStory::CreateTextObject(DocCoord Anchor) { Matrix StoryMat(Anchor.x, Anchor.y); return CreateTextObject(StoryMat); } /******************************************************************************************** > static TextStory* TextStory::CreateTextObject(Matrix TheMatrix) Author: Simon_Maneggio (Xara Group Ltd) Created: 25/03/95 Inputs: TheMatrix - the matrix to initialise the story with (includes inital position) Returns: A pointer to the root of the text object subtree if successful, else NULL Purpose: Creates a Text object Errors: An ERROR will be set if we run out of memory ********************************************************************************************/ TextStory* TextStory::CreateTextObject(Matrix TheMatrix) { // Pointers to objects we will create to construct a TextStory TextStory* pTextStory = NULL; TextLine* pTextLine = NULL; // CaretNode* pCaret = NULL; // EOLNode* pEOLN = NULL; BOOL ok = TRUE; // Try to create the root TextStory node pTextStory = new TextStory(); ok = (pTextStory != NULL); // Set the matrix so that the origin is translated to the start point if (ok) pTextStory->SetStoryMatrix(TheMatrix); // Create a line for the story if (ok) { pTextLine = new TextLine(pTextStory, LASTCHILD); ok = (pTextLine != NULL); } // Add an EOLNode to the line if (ok) ok = (new EOLNode(pTextLine, FIRSTCHILD) != NULL); // Create the TextStory's Caret node if (ok) ok = (new CaretNode(pTextLine, FIRSTCHILD) != NULL); // Clean up if failure if (!ok && (pTextStory != NULL)) { pTextStory->CascadeDelete(); delete pTextStory; pTextStory = NULL; } return pTextStory; } /******************************************************************************************** > void TextStory::GetDebugDetails(StringBase* Str) Author: Simon_Maneggio (Xara Group Ltd) Created: 1/2/95 Outputs: Str: String giving debug info about the node Purpose: For obtaining debug information about the Node. This fn can be deleted before we ship ********************************************************************************************/ void TextStory::GetDebugDetails(StringBase* Str) { #if DEBUG_TREE BaseTextClass::GetDebugDetails(Str); String_256 TempStr; String_256 TempStr2; TCHAR floatStr[20]; (*Str) += TEXT( "\r\nText story Data Dump\r\n" ); fixed16 abcd[4]; INT32 ef[2]; StoryMatrix.GetComponents(abcd, ef); TempStr._MakeMsg( TEXT("\r\nMatrix\r\n")); (*Str) += TempStr; camSnprintf( floatStr, 20, _T("%f,%f"), abcd[0].MakeDouble(), abcd[1].MakeDouble() ); TempStr._MakeMsg( TEXT("a, b :\t#1%s\r\n"), floatStr); (*Str) += TempStr; camSnprintf( floatStr, 20, _T("%f,%f"), abcd[2].MakeDouble(), abcd[3].MakeDouble() ); TempStr._MakeMsg( TEXT("c, d :\t#1%s\r\n"), floatStr); (*Str) += TempStr; TempStr._MakeMsg( TEXT("e, f :\t#1%ld,\t#2%ld\r\n"), ef[0], ef[1]); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\r\nIndents")); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\tLeft\t#1%ld\r\n"), GetLeftIndent()); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\tRight\t#1%ld\r\n"), GetRightIndent()); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\tStory Width\t#1%ld\r\n"), GetStoryWidth()); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\r\nFlags")); (*Str) += TempStr; if (IsTextOnPathReversed()) TempStr._MakeMsg( TEXT("\tText on path is reversed\r\n")); else TempStr._MakeMsg( TEXT("\tText on path is NOT reversed\r\n")); (*Str) += TempStr; if (IsTextOnPathTangential()) TempStr._MakeMsg( TEXT("\tText on path is tangential\r\n")); else TempStr._MakeMsg( TEXT("\tText on path is NOT tangential\r\n")); (*Str) += TempStr; if (IsPrintingAsShapes()) TempStr._MakeMsg( TEXT("\tText is printing as shapes\r\n")); else TempStr._MakeMsg( TEXT("\tText is NOT printing as shapes\r\n")); (*Str) += TempStr; if (IsWordWrapping()) TempStr._MakeMsg( TEXT("\tText is word wrapping\r\n")); else TempStr._MakeMsg( TEXT("\tText is NOT word wrapping\r\n")); (*Str) += TempStr; if (IsAutoKerning()) TempStr._MakeMsg( TEXT("\tText is auto kerned\r\n")); else TempStr._MakeMsg( TEXT("\tText is NOT auto kerned\r\n")); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\tCharsScale = #1%ld\r\n"), CharsScale.GetRawLong() ); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\tCharsAspect = #1%ld\r\n"), CharsAspect.GetRawLong() ); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\tCharsRotation = #1%ld\r\n"), CharsRotation.GetRawLong() ); (*Str) += TempStr; TempStr._MakeMsg( TEXT("\tCharsShear = #1%ld\r\n"), CharsShear.GetRawLong() ); (*Str) += TempStr; #endif } /******************************************************************************************** > CaretNode* TextStory::GetCaret() Author: Simon_Maneggio (Xara Group Ltd) Created: 10/2/95 Returns: A pointer to the TextStory's caret node, or NULL Purpose: Returns a pointer to the caret node of this TextStory, NULL will be returned if there is no Caret. ********************************************************************************************/ CaretNode* TextStory::GetCaret() { // if there's a caret pointer in the cache then use it if (CachedCaret) return CachedCaret; // otherwise find the thing inside the story Node* pNode=FindFirstDepthFirst(); while (pNode != NULL) { if (IS_A(pNode,CaretNode)) break; pNode=pNode->FindNextDepthFirst(this); } // if we can't find the thing its trouble! ERROR2IF(pNode==NULL,NULL,"TextStory::GetCaret() - could not find caret!"); CachedCaret = (CaretNode*)pNode; return CachedCaret; } /******************************************************************************************** > virtual BOOL TextStory::AllowOp(ObjChangeParam* pParam, BOOL SetOpPermissionState = TRUE, BOOL DoPreTriggerEdit = TRUE) Author: Ed_Cornes (Xara Group Ltd) ; Karim 19/01/2000 Created: 6/5/95 Inputs: pParam - pointer to the change parameter object SetOpPermissionState - TRUE to set the Nodes permission flags DoPreTriggerEdit - if TRUE then calls NameGallery::PreTriggerEdit. *Must* be TRUE if the calling Op may make any nodes change their bounds, eg move, line width, cut. Use TRUE if unsure. Returns: TRUE if the operation can proceede, FALSE to stop it Purpose: Allows the TextStory to abort the following operations under the following circumstances ********************************************************************************************/ BOOL TextStory::AllowOp(ObjChangeParam* pParam, BOOL SetOpPermissionState, BOOL DoPreTriggerEdit) { ERROR2IF(pParam==NULL,FALSE,"TextStory::AllowOp() - pParam==NULL"); // decide if we allow it ... // Karim 14/11/2000 // Added the !=OBJCHANGE_CALLEDBYCHILD clause. // TextStories don't usually pass AllowOp down to their children. // This is an exception - see TextStory::AllowOp_AccountForCompound for more info. BOOL allowed=TRUE; ObjChangeFlags Flags=pParam->GetChangeFlags(); if (pParam->GetDirection() != OBJCHANGE_CALLEDBYCHILD) AllowOp_AccountForCompound(pParam, SetOpPermissionState, DoPreTriggerEdit); else { // we disallow changes to our text chars, but any child NodePath is fair game. if (pParam->GetCallingChild() == NULL || !pParam->GetCallingChild()->IsNodePath()) { if (Flags.ReplaceNode || Flags.MultiReplaceNode) { pParam->SetReasonForDenial(_R(IDE_TEXT_USEDBYSTORY)); allowed=FALSE; } } } // if we allow it, (and our parents weren't calling us) see if our parents do ... if (allowed && pParam->GetDirection()!=OBJCHANGE_CALLEDBYPARENT && Parent!=NULL) { // pass a temporary copy of the ObjChangeParam to our parent indicating call direction // and if all our children are deleted we know we don't delete ourselves so clear delete flag ObjChangeFlags NewFlags=pParam->GetChangeFlags(); if (pParam->GetDirection()==OBJCHANGE_CALLEDBYCHILD) NewFlags.DeleteNode=FALSE; ObjChangeParam ObjParam(pParam->GetChangeType(), NewFlags, pParam->GetChangeObj(), pParam->GetOpPointer(), OBJCHANGE_CALLEDBYCHILD); ObjParam.SetCallingChild(this); allowed=Parent->AllowOp(&ObjParam,SetOpPermissionState,DoPreTriggerEdit); } // if setting permisions ... if (SetOpPermissionState) { if (allowed) { if (Parent!=NULL) Parent->SetOpPermission(PERMISSION_ALLOWED); // if post process required, // ensure our OnChildChange is called for post processing (by setting permission on ourself), // and do all pre-op processing we need to do for text if (pParam->GetDirection()!=OBJCHANGE_CALLEDBYPARENT || Flags.Attribute || Flags.TransformNode) { SetOpPermission(PERMISSION_ALLOWED); allowed=PreOpProcessing(pParam); } } else SetOpPermission(PERMISSION_DENIED,TRUE); } // if we're ok so far and were asked to do a PreTriggerEdit, then // determine whether the Op may change the bounds of some nodes. // If it may, then call NameGallery::PreTriggerEdit. if (allowed && DoPreTriggerEdit) { // if the Op is non-NULL then query its MayChangeNodeBounds() method. UndoableOperation* pChangeOp = pParam->GetOpPointer(); if (pChangeOp != NULL && pChangeOp->MayChangeNodeBounds() && NameGallery::Instance()) { allowed = NameGallery::Instance()->PreTriggerEdit(pChangeOp, pParam, this); } } // Ilan 8/5/00 // Inform geom linked attrs of the change. if(allowed) { UndoableOperation* pChangeOp = pParam->GetOpPointer(); BOOL InformGeomLinkedAttrs = SetOpPermissionState && pChangeOp && pChangeOp->MayChangeNodeBounds(); if(InformGeomLinkedAttrs) { NodeAttribute* pNA = FindFirstGeometryLinkedAttr(); while(pNA) { pNA->LinkedNodeGeometryHasChanged(pChangeOp); pNA = pNA->FindNextGeometryLinkedAttr(); } } } // return result (directly, or indirectly via a child AllowOp()) to op return allowed; } /******************************************************************************************** > virtual BOOL TextStory::AllowOp_AccountForCompound( ObjChangeParam* pParam, BOOL SetOpPermissionState, BOOL DoPreTriggerEdit ) Author: Karim_MacDonald (Xara Group Ltd) Created: 14/11/2000 Purpose: Req'd so that the curve bit of text-on-a-curve gets AllowOp messages, thus allowing feathered curve bits to update correctly. This is a very much cut-down version of Node::AllowOp_AccountForCompound, tailored to the way TextStories behave - DON'T COPY IT! See also: Node::AllowOp_AccountForCompound(). ********************************************************************************************/ BOOL TextStory::AllowOp_AccountForCompound(ObjChangeParam* pParam, BOOL SetOpPermissionState, BOOL DoPreTriggerEdit) { // we only do the biz if an attr changes, or if we're transformin' or regeneratin'. ObjChangeFlags Flags = pParam->GetChangeFlags(); if (Flags.Attribute || Flags.TransformNode || Flags.RegenerateNode) { ObjChangeDirection OldDirection = pParam->GetDirection(); pParam->SetDirection(OBJCHANGE_CALLEDBYPARENT); for (Node* pNode = FindFirstChild(); pNode != NULL; pNode = pNode->FindNext()) { if (pNode->IsNodePath()) pNode->AllowOp(pParam, SetOpPermissionState, DoPreTriggerEdit); } pParam->SetDirection(OldDirection); } return TRUE; } /******************************************************************************************** > virtual ChangeCode TextStory::OnChildChange(ObjChangeParam* pParam) Author: Peter_Arnold (Xara Group Ltd) Created: 14/03/95 Inputs: pParam = pointer to a object change parameter class Returns: CC_OK if we have successfully processed the change. CC_FAIL if we cannot handle this particular change and must prevent the child from continuing Purpose: This function is called at the end of any operation which has affected a TextStory in order to (undoably) reformat the story and redraw affected bits ********************************************************************************************/ ChangeCode TextStory::OnChildChange(ObjChangeParam* pParam) { ERROR2IF(pParam==NULL,CC_FAIL,"TextStory::OnChildChange() - pParam==NULL"); // ignore all but OBJCHANGE_FINISHED codes if (pParam->GetChangeType()!=OBJCHANGE_FINISHED) return CC_OK; // The AllowOp system will have called the CaretNode and it will have set // PERMISSION_DENIED on itself. We must ensure that that is cleared at the // end of the operation. if (IS_A(this, CaretNode)) SetOpPermission(PERMISSION_UNDEFINED); // if we have an op pointer format and redraw the story undoably else just do it // (ie consecutive attribute applies only do the first one undoably) BOOL ok = TRUE; UndoableOperation* pOp=pParam->GetOpPointer(); if (pOp!=NULL) { if (ok) ok = PrePostTextAction::DoFormatStory(pOp,this); if (ok) { DocRect temprect = RedrawRect; // Don't call ReleaseCached here without very good reason // Some Ops which call this function have carefully controlled // their cacheing so that unwanted re-renders are avoided. // Calling ReleaseCached here blows all that control away if (!pParam->GetRetainCachedData()) { ReleaseCached(TRUE, FALSE); BOOL bFoundEffects = FALSE; DocRect effectrect = GetEffectStackBounds(&bFoundEffects); if (bFoundEffects) temprect = temprect.Union(effectrect); } ok = pOp->DoInvalidateRegion(FindParentSpread(), temprect); } } else { if (ok) ok = FormatAndChildren(pOp,TRUE); BaseDocument* pOwnerDoc = FindOwnerDoc(); ERROR2IF(pOwnerDoc==NULL,CC_FAIL,"TextStory::OnChildChange() - pDoc==NULL"); if (ok && IS_A(pOwnerDoc, Document)) { DocRect temprect = RedrawRect; if (!pParam->GetRetainCachedData()) { ReleaseCached(TRUE, FALSE); BOOL bFoundEffects = FALSE; DocRect effectrect = GetEffectStackBounds(&bFoundEffects); if (bFoundEffects) temprect = temprect.Union(effectrect); } ((Document*)pOwnerDoc)->ForceRedraw(FindParentSpread(), temprect, TRUE, this, FALSE); } } if (ok) return CC_OK; else return CC_FAIL; } /******************************************************************************************** > virtual BOOL TextStory::CanBecomeA(BecomeA* pBecomeA) Author: Peter_Arnold (Xara Group Ltd) Created: 10/02/94 Inputs: InkClass: The class of object to turn into pNumObjects = ptr to place number of objects of type pClass that will be created (Note: can be NULL). *pNumObects in undefined on entry Returns: TRUE if the node, or any of its children can transmogrify themselves to become an InkClass object Purpose: This function is used by the convert to shapes operation. It determines if the node or any of its children can convert themselves into an InkClass object. The number you put into pNumObjects (if it's not NULL) should exactly equal the total number of pClass objects you create. It should NOT contain any additional objects you may produce such as group objects for containing the pClass object, or attributes. Also, the entry value of *pNumObjects cannot be assumed to be 0. ********************************************************************************************/ BOOL TextStory::CanBecomeA(BecomeA* pBecomeA) { // A TextStory can become a NodePath if (pBecomeA->BAPath()) { if (pBecomeA->IsCounting()) { // Sum the number of paths our immediate children will create Node* pNode = FindFirstDepthFirst(); while (pNode!=NULL && pNode!=this) { // Call child to update BecomeA count pNode->CanBecomeA(pBecomeA); // Text chars don't have their own CanBecomeA, so count them here. if (IS_A(pNode, TextChar) && !((TextChar*)pNode)->IsAVisibleSpace()) pBecomeA->AddCount(1); pNode = pNode->FindNextDepthFirst(this); } } return TRUE; } return FALSE; } /******************************************************************************************** > virtual BOOL TextStory::DoBecomeA(BecomeA* pBecomeA) Author: Ed_Cornes (Xara Group Ltd) Created: 30/3/95 Inputs: pBecomeA - ptr to class containing info needed to become a new type of node. Returns: FALSE if fails Purpose: Transforms the object into another type of object. ********************************************************************************************/ BOOL TextStory::DoBecomeA(BecomeA* pBecomeA) { // BODGE TEXT - pass back should delete nodes if it fails - though MarkN claimed not when I wrote this! ERROR2IF(pBecomeA==NULL,FALSE,"TextStory::DoBecomeA() - pBecomeA==NULL"); // here to overcome scope problem Node* pNode=NULL; // create a format region to keep an attribute stack FormatRegion FRegion; FormatRegion* pFormatRegion=&FRegion; if (pFormatRegion->Init(this)==FALSE) return FALSE; pFormatRegion->SaveContext(); // if not passing back, create a NodeGroup to encompass the story // BODGE - should use ALLOC_WITH_FAIL NodeGroup* pStoryNodeGroup=NULL; if (pBecomeA->GetReason()!=BECOMEA_PASSBACK) { pStoryNodeGroup=new NodeGroup; if (pStoryNodeGroup==NULL) goto Fail; // render story level attrs and copy non-text attrs pNode=FindFirstChild(); while (pNode) { if (pNode->IsAnAttribute()) { pNode->Render(pFormatRegion); // render attributes if (!pNode->IsKindOfTextAttribute()) if (pNode->CopyNode(pStoryNodeGroup, LASTCHILD)==FALSE) goto Fail; } pNode=pNode->FindNext(); } } // convert all the story's childs either attaching to the StoryNodeGroup or PassingBack pNode=FindFirstChild(); while (pNode) { // convert all TextLines to groups (inc attrs and chars) if (IS_A(pNode,TextLine)) { TextLine* pTextLine=(TextLine*)pNode; NodeGroup* pLineNodeGroup=NULL; if (pTextLine->CreateNodeGroup(&pLineNodeGroup,pFormatRegion,pBecomeA)==FALSE) goto Fail; if (pBecomeA->GetReason()!=BECOMEA_PASSBACK) pLineNodeGroup->AttachNode(pStoryNodeGroup,LASTCHILD); } // copy any NodePath with it's children attrs if (pNode->IsNodePath()) { NodePath* pNodePath=(NodePath*)pNode; NodePath* pNodePathCopy=(NodePath*)pNodePath->SimpleCopy(); if (pNodePathCopy==NULL) goto Fail; if (pNodePath->CopyChildrenTo(pNodePathCopy)==FALSE) goto Fail; if (pBecomeA->GetReason()!=BECOMEA_PASSBACK) { pNodePathCopy->AttachNode(pStoryNodeGroup,LASTCHILD); pBecomeA->PassBack(pNodePathCopy, pNodePath); } else if (pBecomeA->PassBack(pNodePathCopy,pNodePath)==FALSE) goto Fail; } pNode=pNode->FindNext(); } // Must do this here or else the current attributes in the region // will have been deleted pFormatRegion->RestoreContext(); // now either insert the replace the story or pass it back switch (pBecomeA->GetReason()) { case BECOMEA_REPLACE: { UndoableOperation* pUndoOp = pBecomeA->GetUndoOp(); // Deselect the node SetSelected(TRUE); // ensures all children are deselected SetSelected(FALSE); // are we undoable or not ... if (pUndoOp!=NULL) { // hide the text story NodeHidden* pNodeHidden; if (pUndoOp->DoHideNode(this,FALSE,&pNodeHidden)==FALSE) goto FailNoRestore; // Insert the NodeGroup next to the old hidden TextStory if (!pUndoOp->DoInsertNewNode(pStoryNodeGroup,pNodeHidden,NEXT,FALSE,FALSE,FALSE,FALSE)) goto FailNoRestore; } else { // attach the NodeGroup next to the text story, then delete 'this' TextStory - eek! pStoryNodeGroup->AttachNode(this,NEXT); CascadeDelete(); delete this; } // Select the group, but only if the caller wants us too (moulds don't, for example) if (pBecomeA->Reselect() && pUndoOp!=NULL) if (!pUndoOp->DoSelectNode(pStoryNodeGroup,FALSE)) return FALSE; break; } case BECOMEA_PASSBACK: // all pass back done by children! break; default: ERROR2_PF(FALSE,("Unknown BecomeA reason %d",pBecomeA->GetReason())); break; } return TRUE; Fail: pFormatRegion->RestoreContext(); FailNoRestore: if (pStoryNodeGroup) { pStoryNodeGroup->CascadeDelete(); delete pStoryNodeGroup; } return TRUE; } /******************************************************************************************** > static TextStory* TextStory::GetFocusStory() Author: Peter_Arnold (Xara Group Ltd) Created: 16/02/95 Returns: Pointer to the TextStory with the active caret (can be NULL). Purpose: This function should be used to get a pointer to the TextStory with the input focus. There can be only one such story with an active caret at any time, although there does not have to aways be one (in this case NULL is returned) ********************************************************************************************/ TextStory* TextStory::GetFocusStory() { return pFocusStory; } /******************************************************************************************** > static void TextStory::SetFocusStory(TextStory* pNewStory) Author: Peter_Arnold (Xara Group Ltd) Created: 16/2/95 Inputs: pNewStory - pointer to a TextStory to giv the input focus to NULL to give no TextStory the input focus Returns: FALSE if an error occured Purpose: This function should be used to set the pointer with the input focus. Setting the pointer to NULL clears the into focus. Errors: Attempts to set the intput focus to a non TextStory object. (ERROR3) ********************************************************************************************/ void TextStory::SetFocusStory(TextStory* pNewStory) { if ((pNewStory != NULL) && (!IS_A(pNewStory,TextStory)) ) { ERROR3("Attempted to set FocusTextStory to a non-TextStory object"); pNewStory = NULL; } pFocusStory = pNewStory; } /******************************************************************************************** > NodePath TextStory::GetTextPath() Author: Peter_Arnold (Xara Group Ltd) Created: 10/02/94 Returns: A pointer to the path or NULL if none Purpose: This function should be used to get a pointer to the path the text is on. NULL is returned if there is no text path. ********************************************************************************************/ NodePath* TextStory::GetTextPath() const { return (NodePath*)FindFirstChild(CC_RUNTIME_CLASS(NodePath)); } /******************************************************************************************** > TextLine* TextStory::FindFirstLine() Author: Simon_Maneggio (Xara Group Ltd) Created: 27/03/95 Returns: A pointer to the TextStory's first line Purpose: This function should be used to obtain a pointer to the TextStory's first TextLine node. ********************************************************************************************/ TextLine* TextStory::FindFirstLine() const { return (TextLine*)FindFirstChild(CC_RUNTIME_CLASS(TextLine)); } /******************************************************************************************** > TextLine* TextStory::FindLastLine() Author: Ed_Cornes (Xara Group Ltd) Created: 24/7/96 Returns: ptr to last TextLine in story ********************************************************************************************/ TextLine* TextStory::FindLastLine() const { return (TextLine*)FindLastChild(CC_RUNTIME_CLASS(TextLine)); } /******************************************************************************************** > void TextStory::RenderObjectBlobs(RenderRegion* pRenderRegion) Author: Ed_Cornes (Xara Group Ltd) Created: 26/4/95 Inputs: pRenderRegion - Purpose: Render the text story's object blobs ********************************************************************************************/ void TextStory::RenderObjectBlobs(RenderRegion* pRenderRegion) { #if !defined(EXCLUDE_FROM_RALPH) if (pRenderRegion != NULL) { pRenderRegion->SetLineColour(COLOUR_NONE); pRenderRegion->SetFillColour(COLOUR_UNSELECTEDBLOB); pRenderRegion->DrawBlob(GetBlobPosAndSize(), BT_UNSELECTED); } else ERROR3("TextStory::RenderTinyBlobs() - pRenderRegion==NULL"); #endif } /******************************************************************************************** > void TextStory::RenderTinyBlobs(RenderRegion* pRenderRegion) Author: Ed_Cornes (Xara Group Ltd) Created: 26/4/95 Inputs: pRenderRegion - Purpose: Render the text story's tiny blobs ********************************************************************************************/ void TextStory::RenderTinyBlobs(RenderRegion* pRenderRegion) { #if !defined(EXCLUDE_FROM_RALPH) if (pRenderRegion != NULL) { pRenderRegion->SetLineColour(COLOUR_NONE); pRenderRegion->SetFillColour(COLOUR_UNSELECTEDBLOB); pRenderRegion->DrawBlob(GetBlobPosAndSize(), BT_UNSELECTED); } else ERROR3("TextStory::RenderTinyBlobs() - pRenderRegion==NULL"); #endif } /******************************************************************************************** > DocRect TextStory::GetBlobBoundingRect(); Author: Ed_Cornes (Xara Group Ltd) Created: 26/4/95 Returns: The rectangle of the document covered by the blobs of this text story ********************************************************************************************/ DocRect TextStory::GetBlobBoundingRect() { // get the bounds of the test story's blob INT32 BlobSize=0; DocCoord BlobPos=GetBlobPosAndSize(&BlobSize); DocRect BlobBounds(BlobPos,BlobPos); BlobBounds.Inflate(BlobSize/2); IncludeChildrensBoundingRects(&BlobBounds); return BlobBounds; } /******************************************************************************************** > DocCoord TextStory::GetBlobPosAndSize(INT32* pSize=NULL) Author: Ed_Cornes (Xara Group Ltd) Created: 26/4/95 Outputs: pSize - size of a blob if required Returns: coord to position blob ********************************************************************************************/ DocCoord TextStory::GetBlobPosAndSize(INT32* pSize) { #if !defined(EXCLUDE_FROM_RALPH) // Get the bounds of the text story DocRect StoryBounds=GetBoundingRect(); // get the size of a blob INT32 BlobSize=0; BlobManager* pBlobMgr=GetApplication()->GetBlobManager(); if (pBlobMgr) BlobSize=pBlobMgr->GetBlobSize(); else ERROR3("TextStory::GetObjectBlobPos() - GetBlobManager() returned NULL"); if (pSize) *pSize=BlobSize; return DocCoord( StoryBounds.lo.x-BlobSize/2, StoryBounds.hi.y+BlobSize/2 ); #else return DocCoord(0,0); #endif } /******************************************************************************************** > virtual BOOL TextStory::HidingNode() Author: Peter_Arnold (Xara Group Ltd) Created: 20/2/95 Outputs: Can update pFocusStory if it points to this TextStory Returns: The base class result Purpose: Called when this TextStory is hidden - Clears the input focus from this TextStory if it currently has it, then calls the parent class. ********************************************************************************************/ BOOL TextStory::HidingNode() { // Clear the focus if (GetFocusStory() == this) { SetFocusStory(NULL); } // Call the parent for it's processing return BaseTextClass::HidingNode(); } /******************************************************************************************** > BOOL TextStory::MoveCaretToCharacter(VisibleTextNode* pChar, AttachNodeDirection Dir) Author: Peter_Arnold (Xara Group Ltd) Created: 22/3/95 Inputs: pChar - points to the character to position the caret by Dir - direction relative to character to position Returns: TRUE if the caret was successfuly moved FALSE if an error occured Purpose: This routine moves the caret to the left or right of a specified char ********************************************************************************************/ BOOL TextStory::MoveCaretToCharacter(VisibleTextNode* pChar, AttachNodeDirection Dir) { ERROR2IF(Dir!=PREV && Dir!=NEXT,FALSE,"Dir must be PREV or NEXT"); ERROR2IF(pChar==NULL,FALSE,"Attempted to move the caret to NULL"); CaretNode* pCaret=GetCaret(); ERROR2IF(pCaret==NULL,FALSE,"TextStory::MoveCaretToCharacter() - TextStory has no caret"); // ensure not inserted after EOL AttachNodeDirection AttachDir = Dir; if (pChar->IsAnEOLNode() && AttachDir==NEXT) AttachDir = PREV; pCaret->MoveNode(pChar, AttachDir); pCaret->HasMoved(); return AttachCaretAttributes(); } /******************************************************************************************** > VisibleTextNode* TextStory::GetPrevWordChar(VisibleTextNode* pStartChar) Author: Ed_Cornes (Xara Group Ltd) Created: 8/7/96 Inputs: pStartChar - char to move from (usually the caret) Returns: ptr to first char in previous word, or char after EOL or EOL ********************************************************************************************/ VisibleTextNode* TextStory::GetPrevWordChar(VisibleTextNode* pStartChar) { ERROR2IF(pStartChar==NULL,NULL,"TextStory::GetPrevWordChar() - pStartChar==NULL"); // skips chars until non-space found, then return ptr to last non-space before space // also return ptr to char immediately after EOL and EOL BOOL NonSpaceFound = FALSE; VisibleTextNode* pVTN = pStartChar; while (1) { VisibleTextNode* pPrevVTN = pVTN->FindPrevVTNInStory(); // if at start of story, return ptr to first VTN if (pPrevVTN==NULL) return pVTN; // if EOL found, if we've moved, return ptr to char after EOL, else return EOL if (pPrevVTN->IsAnEOLNode()) { if (pVTN!=pStartChar) return pVTN; else return pPrevVTN; } // if prev char not a space, flag this, // else prev char a space and if non-space found, return ptr to this char if (!pPrevVTN->IsAVisibleSpace()) NonSpaceFound = TRUE; else if (NonSpaceFound) return pVTN; pVTN = pPrevVTN; } } /******************************************************************************************** > VisibleTextNode* TextStory::GetNextWordChar(VisibleTextNode* pStartChar) Author: Ed_Cornes (Xara Group Ltd) Created: 8/7/96 Inputs: pStartChar - char to move from (usually the caret) Returns: ptr to first char in next word, or EOL or char after EOL ********************************************************************************************/ VisibleTextNode* TextStory::GetNextWordChar(VisibleTextNode* pStartChar) { ERROR2IF(pStartChar==NULL,NULL,"TextStory::GetNextWordChar() - pStartChar==NULL"); VisibleTextNode* pVTN = pStartChar; // skip the caret so we always start on a char if (pStartChar->IsACaret()) { pVTN = pVTN->FindNextVTNInStory(); ERROR2IF(pVTN==NULL,NULL,"TextStory::GetNextWordChar() - no VTN after caret"); } // skips chars until space found, then return pointer to first non-space // also return ptr to EOL and char immediately after EOL BOOL SpaceFound = FALSE; while (1) { VisibleTextNode* pNextVTN = pVTN->FindNextVTNInStory(); // if this is an EOL return pointer to next char if not at end of story if (pVTN->IsAnEOLNode()) { if (pNextVTN!=NULL) return pNextVTN; else return pVTN; } // if next char is an EOL return pointer to it ERROR2IF(pNextVTN==NULL,NULL,"TextStory::GetNextWordChar() - story deos not end in EOL"); if (pNextVTN->IsAnEOLNode()) return pNextVTN; // if char a space, flag it, // else non-space and if space found, return ptr to non-space if (pVTN->IsAVisibleSpace()) SpaceFound = TRUE; else if (SpaceFound) return pVTN; pVTN = pNextVTN; } } /******************************************************************************************** > BOOL TextStory::MoveCaretLeftAChar() Author: Ed_Cornes (Xara Group Ltd) Created: 7/8/96 Returns: FALSE if fails Purpose: This routine moves the caret to the left by one position ********************************************************************************************/ BOOL TextStory::MoveCaretLeftAChar() { CaretNode* pCaret = GetCaret(); ERROR2IF(pCaret==NULL,FALSE,"TextStory::MoveCaretLeftAChar() - Story has no charet"); VisibleTextNode* pNewCaretPos = pCaret->FindPrevVTNInStory(); if (pNewCaretPos==NULL) return TRUE; // do nothing if at start of story return MoveCaretToCharacter(pNewCaretPos, PREV); } /******************************************************************************************** > BOOL TextStory::MoveCaretRightAChar() Author: Ed_Cornes (Xara Group Ltd) Created: 7/8/96 Returns: FALSE if fails Purpose: This routine moves the caret to the right by one position ********************************************************************************************/ BOOL TextStory::MoveCaretRightAChar() { CaretNode* pCaret = GetCaret(); ERROR2IF(pCaret==NULL,FALSE,"TextStory::MoveCaretRightAChar() - Story has no caret"); VisibleTextNode* pNewCaretPos = pCaret->FindNextVTNInStory(); ERROR2IF(pNewCaretPos==NULL,FALSE,"TextStory::MoveCaretRightAChar() - no VTN after caret in story!"); pNewCaretPos = pNewCaretPos->FindNextVTNInStory(); if (pNewCaretPos==NULL) return TRUE; // if at end of story, do nothing return MoveCaretToCharacter(pNewCaretPos, PREV); } /******************************************************************************************** > BOOL TextStory::MoveCaretLeftAWord() Author: Peter_Arnold (Xara Group Ltd) Created: 29/8/95 Returns: TRUE/FALSE for success/failure Purpose: This routine moves the caret to the left by a word. If the caret is in a word it is moved to the start of that word, else the caret is positioned at the start of the previous word ********************************************************************************************/ BOOL TextStory::MoveCaretLeftAWord() { CaretNode* pCaret = GetCaret(); // Obtain the caret ERROR2IF(pCaret == NULL, FALSE, "Story had no caret"); // Get the new caret position VisibleTextNode* pStartOfPrev = GetPrevWordChar(pCaret); ERROR2IF(pStartOfPrev == NULL, FALSE, "Didn't find prev word start"); // Move the caret if (pStartOfPrev != pCaret) return MoveCaretToCharacter(pStartOfPrev, PREV); else return TRUE; } /******************************************************************************************** > BOOL TextStory::MoveCaretRightAWord() Author: Peter_Arnold (Xara Group Ltd) Created: 29/9/95 Returns: TRUE if the caret was moved a word to the right. FALSE if the caret is already positioned at the end of the TextLine Purpose: This routine moves the caret to the right by a word. It caret is positioned to the left of the first character encountered after a space. ********************************************************************************************/ BOOL TextStory::MoveCaretRightAWord() { CaretNode* pCaret = GetCaret(); // Obtain the caret ERROR2IF(pCaret == NULL, FALSE, "Story had no caret"); // Get the new caret position VisibleTextNode* pStartOfNext = GetNextWordChar(pCaret); ERROR2IF(pStartOfNext == NULL, FALSE, "Didn't find next word start"); // Move the caret if (pStartOfNext != pCaret) return MoveCaretToCharacter(pStartOfNext, PREV); else return TRUE; } /******************************************************************************************** > BOOL TextStory::MoveCaretToStartOfLine() Author: Peter_Arnold (Xara Group Ltd) Created: 29/8/95 Returns: TRUE for success, FALSE if there was an error Purpose: This routine moves the caret to the start of the TextLine that it is on ********************************************************************************************/ BOOL TextStory::MoveCaretToStartOfLine() { CaretNode* pCaret = GetCaret(); // Obtain the caret ERROR2IF(pCaret == NULL, FALSE, "Story had no caret"); // Get its parent (which should be a TextLine) TextLine* pParent = (TextLine*)pCaret->FindParent(); ERROR2IF(!IS_A(pParent,TextLine), FALSE, "Parent of Caret was not a TextLine!"); // Get the first child of the TextLine VisibleTextNode* pFirstChar = pParent->FindFirstVTN(); // Only move the caret if we need to if (pFirstChar != pCaret) return MoveCaretToCharacter(pFirstChar, PREV); else return TRUE; } /******************************************************************************************** > BOOL TextStory::MoveCaretToEndOfLine() Author: Ed_Cornes (Xara Group Ltd) Created: 9/7/96 Returns: FALSE if fails Purpose: Move caret to end of TextLine which it is on ********************************************************************************************/ BOOL TextStory::MoveCaretToEndOfLine() { CaretNode* pCaret = GetCaret(); ERROR2IF(pCaret==NULL,FALSE,"TextStory::MoveCaretToEndOfLine() - story has no caret"); TextLine* pCaretLine = (TextLine*)pCaret->FindParent(); ERROR2IF(pCaretLine==NULL || !IS_A(pCaretLine,TextLine), FALSE, "Parent of Caret was not a TextLine!"); VisibleTextNode* pLastChar = pCaretLine->FindLastVTN(); if (pLastChar->IsAnEOLNode()) { pLastChar = pLastChar->FindPrevVTNInLine(); ERROR2IF(pLastChar==NULL,FALSE,"TextStory::MoveCaretToEndOfLine() - should be at least 1 other VTN on line - the caret!"); } if (pLastChar!=pCaret) return MoveCaretToCharacter(pLastChar, NEXT); return TRUE; } /******************************************************************************************** > BOOL TextStory::AttachCaretAttributes() Author: Peter_Arnold (Xara Group Ltd) Created: 15/03/95 Returns: TRUE if the attributes were successfully applied to the caret FALSE if an error occured Purpose: This function should be called after the caret has been moved to its new location. It deletes all its previous children, and copies the children of the character to the left of it. If there isn't anything to the left then the attributes to the right are used; if there isn't anything to the right then no attributes are attached so the defaults are used. This function broacasts an attribute changed message Errors: Returns TRUE if it worked, FALSE if the copy failed ********************************************************************************************/ BOOL TextStory::AttachCaretAttributes() { CaretNode* pCaret = GetCaret(); BOOL Success = TRUE; BOOL TellPeople = FALSE; ERROR2IF(pCaret==NULL,FALSE,"Text story didn't have caret"); // Identify where were are going to get the new attributes from VisibleTextNode* pAttributeSource = pCaret->FindPrevVTNInLine(); if (pAttributeSource == NULL) pAttributeSource = pCaret->FindNextVTNInLine(); // See if the attributes are the same as the current ones /* if (pAttributeSource != NULL) { Node* pCaretChild = pCaret->FindFirstChild(); Node* pSourceChild = pAttributeSource->FindFirstChild(); if ( ( (pCaretChild == NULL) && (pSourceChild != NULL) ) || ( (pCaretChild != NULL) && (pSourceChild == NULL) ) ) { TellPeople = TRUE; } if ( (pCaretChild != NULL) && (pSourceChild != NULL) ) { while ((pCaretChild != NULL) && (pSourceChild != NULL)) { if ( (pCaretChild->IsAnAttribute()) && (pSourceChild->IsAnAttribute()) ) { if (! ( *((NodeAttribute*)pCaretChild) == *((NodeAttribute*)pSourceChild) )) TellPeople = TRUE; } else TellPeople = TRUE; pCaretChild = pCaretChild->FindNext(); pSourceChild = pSourceChild->FindNext(); } } } else */ TellPeople = TRUE; if (pAttributeSource != NULL) { // Delete the children of the caret pCaret->DeleteChildren(pCaret->FindFirstChild()); // Attempt to copy the attributes of the previous character across Node* pToCopy = pAttributeSource->FindFirstChild(CC_RUNTIME_CLASS(NodeAttribute)); while (Success && (pToCopy != NULL)) { Success = pToCopy->CopyNode(pCaret, LASTCHILD); pToCopy = pToCopy->FindNext(CC_RUNTIME_CLASS(NodeAttribute)); } } // Update other people if (TellPeople) GetApplication()->FindSelection()->Update(); return Success; } /******************************************************************************************** > virtual void TextStory::Transform(TransformBase& transform) Author: Ed_Cornes (Xara Group Ltd) Created: 9/3/95 Inputs: transform - some form of transform Purpose: transform a text story and any children capable of being transformed ********************************************************************************************/ void TextStory::Transform(TransformBase& transform) { BOOL ok=TRUE; if (IS_A(&transform, Trans2DMatrix)) { // apply transform to story matrix StoryMatrix *= ((Trans2DMatrix&)transform).GetMatrix(); // decompose new story matrix FIXED16 Scale=1; FIXED16 Aspect=1; ANGLE Rotation=0; ANGLE Shear=0; DocCoord Translation(0,0); ok=StoryMatrix.Decompose(&Scale,&Aspect,&Rotation,&Shear,&Translation); //TRACEUSER( "Ed", _T("\nNew Story Matrix - Scale=%8.5f, Aspect=%8.5f, Rotation=%9.4f, Shear=%8.5f, Translation=%d,%d\n"),Scale.MakeDouble(),Aspect.MakeDouble(),Rotation.MakeDouble()*180/PI,Shear.MakeDouble()*180/PI,Translation.x,Translation.y); if (Scale<0) { Scale=-Scale; Shear=-Shear; } // if the scale or aspect in the matrix deviate from unit by more than a specified amount normalise them const FIXED16 NormaliseLimit=0.001; FIXED16 AbsScaleError = Scale-1; FIXED16 AbsAspectError = Aspect-1; AbsScaleError = AbsScaleError<0 ? -AbsScaleError : AbsScaleError; AbsAspectError = AbsAspectError<0 ? -AbsAspectError : AbsAspectError; if (ok && (AbsScaleError>NormaliseLimit || AbsAspectError>NormaliseLimit) ) { // determine if we can handle aspect ratio transforms in the attributes BOOL TransformAspect=(GetTextPath()==NULL); if (TransformAspect==FALSE) Aspect=1; // any change in width applied to attributes must also be applied to the story width FIXED16 AbsAspect = Aspect<0 ? -Aspect : Aspect; if (ok) SetStoryWidth( XLONG(GetStoryWidth()) * Scale * AbsAspect ); if (ok) SetLeftIndent( XLONG(GetLeftIndent()) * Scale * AbsAspect ); if (ok) SetRightIndent( XLONG(GetRightIndent()) * Scale * AbsAspect ); // create a set containing attributes which will be transformed AttrTypeSet AffectedAttrs; if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtFontSize)); if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtLineSpace)); if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtBaseLine)); if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtLeftMargin)); if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtRightMargin)); if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtFirstIndent)); if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtRuler)); if (ok && TransformAspect) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtAspectRatio)); // We add all attributes, including the defaults to the TextStory if (ok) ok=MakeAttributeComplete(NULL, TRUE, &AffectedAttrs, TRUE); // Now localise the stories attributes, globally, checking for duplicates if (ok) ok=LocaliseCommonAttributes(TRUE,TRUE,&AffectedAttrs); // Before we transform the TextStory we must add all non-common defaults to the // children of the TextLines. if (ok) { // Scan all lines, localising and factoring out, to add defaults TextLine* pLine = this->FindFirstLine(); while (pLine!=NULL) { ok = pLine->LocaliseCommonAttributes(TRUE,FALSE,&AffectedAttrs); if (ok) ok = pLine->FactorOutCommonChildAttributes(FALSE,&AffectedAttrs); pLine = pLine->FindNextLine(); } } // At this stage there should be no invalidly placed defaults in the tree // apply scale/aspect to attribtes if (ok) { // for the entire TextStory sub-tree, transform and of the text attributes Node* pNode=FindFirstDepthFirst(); while (pNode!=NULL) { if (pNode->IsAnAttribute() && pNode->IsKindOfTextAttribute()) ((AttrTxtBase*)pNode)->BaseLineRelativeTransform(Scale,Aspect); pNode=pNode->FindNextDepthFirst(this); } // normalise the attributes which may have been affected if (ok) ok=FactorOutCommonChildAttributes(FALSE,&AffectedAttrs); // even if factor out failed, remove effect of attribute scale/(aspect) from story matrix Matrix AttrChanges, AttrSpace, StoryChanges; AttrChanges.Compose(Scale,Aspect); // effect of scale/aspect on attrs in attr space AttrSpace.Compose(1,1,Rotation,Shear,Translation); // story to attr space transfom StoryChanges=AttrSpace.Inverse(); // transform into attr space StoryChanges*=AttrChanges.Inverse(); // remove effect of attr scale/aspect StoryChanges*=AttrSpace; // transform back into doc space StoryMatrix*=StoryChanges; // effect of scale/aspect on attrs in story space } if (ok) { // Finally factor out the attributes globally ok = FactorOutCommonChildAttributes(TRUE,&AffectedAttrs); } AffectedAttrs.DeleteAll(); } if (!ok) { InformError(); return; } } else ERROR3("TextStory::Transform() - can't handle non-Trans2DMatrix transforms!"); #if 0 { FIXED16 Scale=1; FIXED16 Aspect=1; ANGLE Rotation=0; ANGLE Shear=0; DocCoord Translation(0,0); StoryMatrix.Decompose(&Scale,&Aspect,&Rotation,&Shear,&Translation); TRACEUSER( "Ed", _T("Normalised Story Matrix - Scale=%8.5f, Aspect=%8.5f, Rotation=%9.4f, Shear=%8.5f, Translation=%d,%d\n"),Scale.MakeDouble(),Aspect.MakeDouble(),Rotation.MakeDouble()*180/PI,Shear.MakeDouble()*180/PI,Translation.x,Translation.y); } #endif InvalidateBoundingRect(); TransformChildren(transform); if (transform.bSolidDrag) FormatAndChildren(); } /******************************************************************************************** > VisibleTextNode* TextStory::GetSelectionEnd(BOOL* pDirection = NULL) Author: Peter_Arnold (Xara Group Ltd) Created: 20/03/95 Inputs: pDirection - Pointer to a BOOL that is filled in with the direction of the selection Outputs: FALSE if the the selection end if to the left of the Anchor. TRUE if the the selection end if to the right of the Anchor. Returns: Pointer to the last selected node in the text selection (at the other end from the caret) Purpose: This function should be called to find what character nodes are selected. The return value is a pointer to the last selected node, at the other end of the selected characters from the anchor (which is the caret). NULL will be returned if there is no selection. ********************************************************************************************/ VisibleTextNode* TextStory::GetSelectionEnd(BOOL* pDirection) { CaretNode* pCaret = GetCaret(); ERROR2IF(pCaret==NULL, NULL, "Story has no caret"); VisibleTextNode* pNextChar = NULL; // Get the direction of the selection BOOL Forward = TRUE; pNextChar = pCaret->FindPrevVTNInStory(); if ((pNextChar != NULL) && pNextChar->IsSelected()) Forward = FALSE; if (pDirection != NULL) *pDirection = Forward; // Loop to the end of the selection VisibleTextNode* pResult = NULL; if (Forward) pNextChar = pCaret->FindNextVTNInStory(); else pNextChar = pCaret->FindPrevVTNInStory(); while ((pNextChar != NULL) && pNextChar->IsSelected()) { pResult = pNextChar; if (Forward) pNextChar = pNextChar->FindNextVTNInStory(); else pNextChar = pNextChar->FindPrevVTNInStory(); } if (pResult == pCaret) pResult = NULL; return pResult; } /******************************************************************************************** > virtual BOOL TextStory::FormatAndChildren(UndoableOperation* pUndoOp=NULL, BOOL UseNodeFlags=FALSE, BOOL WordWrap=TRUE) Author: Ed_Cornes (Xara Group Ltd) Created: 17/03/95 Inputs: pUndoOp - pointer to undoable op (NULL if not undoable) UseNodeFlags - TRUE => use each node's ModifiedByOpflags to determine what has changed FALSE => assume the whole story has changed WordWrap - indicates if wordwrapping should be undertaken (FALSE for undo/redo as history will restore previous wrap state or stories on clipboard, where they should be unwrapped or when non-undoably affecting the tree in the doc) Returns: FALSE if fails Purpose: Reformat a text story ********************************************************************************************/ BOOL TextStory::FormatAndChildren(UndoableOperation* pUndoOp, BOOL UseNodeFlags, BOOL WordWrap) { #ifndef DISABLE_TEXT_RENDERING // if whole story is affected, just flag all children as 'ModifiedByOp' if (UseNodeFlags==FALSE) FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp(); // if DOing the format (as opposed to undo/redo) // ensure all lines in paragraphs have same line level attrs as first line // flagging any changed lines as modified, before metrics are recached if (WordWrap) { Validate(FALSE); TextLine* pLine = this->FindFirstLine(); while (pLine!=NULL) { if (pLine->FindEOLNode()==NULL) if (!pLine->EnsureNextLineOfParagraphHasSameLineLevelAttrs(pUndoOp)) return FALSE; pLine = pLine->FindNextLine(); } } // recache metrics in any nodes in story 'ModifiedByOp' // Karim 02/03/2000 - braces added because this FormatRegion needs to go out of scope // *before* people start localising attributes in one of the fn's called later on. { FormatRegion FormattingRegion; if (FormattingRegion.Init(this)==FALSE) return FALSE; ReCacheNodeAndDescendantsMetrics(&FormattingRegion); } // if text on path, get untransfomed (reversed) copy and associated information TextStoryInfo StoryInfo; StoryInfo.pUndoOp = pUndoOp; StoryInfo.WordWrap = WordWrap; if (GetTextPath()!=NULL) { if (CreateUntransformedPath(&StoryInfo)==FALSE) return FALSE; } // determine the story's width, and if it is word wrapping or not if (StoryInfo.pPath==NULL) StoryInfo.StoryWidth = GetStoryWidth(); // 0 if text at point else { StoryInfo.StoryWidth = StoryInfo.PathLength; if (IsWordWrapping()) // we might just as well store the logical width in the story as well // (for the benefit of displaying it on the ruler) StoryWidth = StoryInfo.PathLength - StoryInfo.LeftPathIndent - StoryInfo.RightPathIndent; } StoryInfo.WordWrapping = IsWordWrapping(); // format each line in story TextLine* pLine = this->FindFirstLine(); while (pLine!=NULL) { if (pLine->Format(&StoryInfo)==FALSE) return FALSE; pLine = pLine->FindNextLine(); } // delete the untransformed copy of the path if there was one if (StoryInfo.pPath!=NULL) delete StoryInfo.pPath; // ensure the caret is in the correct place CaretNode* pCaret=GetCaret(); ERROR2IF(pCaret==NULL,FALSE,"TextStory::Format() - pCaret==NULL"); if (pCaret->HasMoved()==FALSE) return FALSE; // update the redraw rect to encompass all the affected nodes UnionNodeAndDescendantsOldAndNewBounds(&RedrawRect); // clear all the flags in the story ClearNodeAndDescendantsFlags(); Validate(); #endif return TRUE; } /******************************************************************************************* void TextStory::PreExportRender(RenderRegion* pRegion) Author: Chris_Snook (Xara Group Ltd) Created: 2/2/95 Inputs: pRegion - points to the export render region Purpose: This function is called when the render function passes through this node It outputs the Text Object start and end tokens ********************************************************************************************/ void TextStory::PreExportRender(RenderRegion* pRegion) { #if EXPORT_TEXT BOOL exportingAsShapes = FALSE; // Determine whether or not to export AI text as shapes (i.e. if there are any // gradient fills in the text. if (pRegion->IsKindOf(CC_RUNTIME_CLASS(AIEPSRenderRegion))) { exportingAsShapes = IsGradientFilled (); } // if there are gradient fills, then don't export text info, just set the RenderRegion up // so that it's ready for converting text to shapes. if (exportingAsShapes) { ((AIEPSRenderRegion*) pRegion)->SetTextAsShapes (exportingAsShapes); } // if there are no gradient fills, or we're not doing AI export, then carry on as normal. else { if (pRegion->IsKindOf(CC_RUNTIME_CLASS(EPSRenderRegion))) { // Output "start text object" token EPSExportDC *pDC = (EPSExportDC *) CCDC::ConvertFromNativeDC(pRegion->GetRenderDC()); EPSRenderRegion* pEPSRegion = (EPSRenderRegion*)pRegion; NodePath* pPath = GetTextPath(); // Text Story type if (pPath) pDC->OutputValue((INT32)2); else pDC->OutputValue((INT32)0); // text object token pDC->OutputToken(_T("To")); pDC->OutputNewLine(); // output the story matrix. We need to make this relative to the spread the // story is on. Matrix TempMatrix; TempMatrix = StoryMatrix; // if we're exporting to illustrator, make the text position relative to // the page. if (pRegion->IsKindOf(CC_RUNTIME_CLASS(AIEPSRenderRegion))) { DocCoord pos; TempMatrix.GetTranslation(pos); Spread* pCurSpread = FindParentSpread(); if (pCurSpread) { DocCoord result; pCurSpread->SpreadCoordToPagesCoord(&result, pos); TempMatrix.SetTranslation(result); } } pDC->OutputMatrix(&TempMatrix); // null start point pDC->OutputValue((INT32)0); // start path pDC->OutputToken(_T("Tp")); pDC->OutputNewLine(); // export the path if (pPath) { // if it's an AI EPS region, we need to save the text attributes after the TP token, or // Photoshop screws up. if (pRegion->IsKindOf (CC_RUNTIME_CLASS (AIEPSRenderRegion)) == FALSE) { // Need to call this for any text attributes that may have // changed pEPSRegion->GetValidTextAttributes(); } // Save the attribute context pRegion->SaveContext(); // Save the attributes associated with this path. Node* pNode = pPath->FindPrevious(); while (pNode) { if (pNode->IsAnAttribute()) { ((NodeAttribute*)pNode)->Render(pEPSRegion); } pNode=pNode->FindPrevious(); } pNode = pPath->FindFirstChild(); while (pNode) { if (pNode->IsAnAttribute()) { ((NodeAttribute*)pNode)->Render(pEPSRegion); } pNode=pNode->FindNext(); } /* CMapPtrToPtr* pAttribMap = new CMapPtrToPtr(30); if (pAttribMap == NULL) return; // Now find any attributes that are applied to this node. BOOL FoundAttrs = pPath->FindAppliedAttributes(pAttribMap); if (FoundAttrs) { // iterating all (key, value) pairs for (POSITION Pos = pAttribMap->GetStartPosition(); Pos != NULL;) { void *pType,*pVal; pAttribMap->GetNextAssoc(Pos,pType,pVal); NodeAttribute* pAttr = (NodeAttribute*)pVal; pAttr->Render(pEPSRegion); } } */ if (IsTextOnPathReversed()) (pPath->InkPath).Reverse(); pEPSRegion->ExportPath(&(pPath->InkPath),FALSE); if (IsTextOnPathReversed()) (pPath->InkPath).Reverse(); // restore the old context. pRegion->RestoreContext(); } // end path pDC->OutputToken(_T("TP")); pDC->OutputNewLine(); if (pRegion->IsKindOf(CC_RUNTIME_CLASS(AIEPSRenderRegion))) { // Need to call this for any text attributes that may have // changed. // // (ChrisG - 8/11/00). Removed, as this should be done by the // individual characters themselves, and can screw up the export // if it isn't, (AI doesn't like lots of font and render changes // without actually drawing any text). // pEPSRegion->GetValidTextAttributes(); // (ChrisG - 8/11/00) Added support for automatic kerning. if (AutoKern) pDC->OutputValue ((INT32)1); else pDC->OutputValue ((INT32)0); pDC->OutputToken (_T("TA")); // automatic kerning TA pDC->OutputNewLine(); pDC->OutputToken(_T("0 0 0 TC")); // Character spacing TC pDC->OutputNewLine(); pDC->OutputToken(_T("100 100 100 TW")); // Word spacing TW pDC->OutputNewLine(); pDC->OutputToken(_T("0 0 0 Ti")); // Line indentation Ti pDC->OutputNewLine(); // (ChrisG - 3/11/00) // Export the overflow text if the text is on a path if (pPath) ((AIEPSRenderRegion *) pRegion)->OverflowTextStart (); } } if (pRegion->IsKindOf(CC_RUNTIME_CLASS(NativeRenderRegion))) { // Output "extras" token for text EPSExportDC *pDC = (EPSExportDC *) pRegion->GetRenderDC(); // EPSRenderRegion* pEPSRegion = (EPSRenderRegion*)pRegion; INT32 WordWrapping = (IsWordWrapping() ? 1 : 0); pDC->OutputValue(WordWrapping); pDC->OutputValue(GetStoryWidth()); double CScale = CharsScale.MakeDouble(); pDC->OutputReal(CScale); double CAspect = CharsAspect.MakeDouble(); pDC->OutputReal(CAspect); double CRotation = CharsRotation.MakeDouble(); pDC->OutputReal(CRotation); double CShear = CharsShear.MakeDouble(); pDC->OutputReal(CShear); INT32 AsShapes = ((PrintAsShapes) ? (1) : (0)); pDC->OutputValue(AsShapes); // Output version zero and token pDC->OutputValue((INT32)0); pDC->OutputToken(_T("ctex")); pDC->OutputNewLine(); // Version one stuff /* pDC->OutputValue(GetLeftMargin()); pDC->OutputValue(GetRightMargin()); // Output version and token pDC->OutputValue((INT32)1); pDC->OutputToken(_T("ctex")); pDC->OutputNewLine();*/ } } #endif } /******************************************************************************************* > BOOL TextStory::ExportRender(RenderRegion* pRegion) Author: Ed_Cornes (Xara Group Ltd) Created: 14/4/95 Inputs: pRegion - points to the export render region Returns: TRUE if rendered OK (FALSE=>use normal rendering) Purpose: This function is called when the render function passes through this node It outputs the Text Object start and end tokens ********************************************************************************************/ BOOL TextStory::ExportRender(RenderRegion* pRegion) { #if EXPORT_TEXT // (ChrisG 8/11/00) - Reset the 'exporting text as shapes' flag, so that it's valid for // the next text story - also don't export the 'end of text object' data if we aren't // exporting a text object. BOOL endTextObject = TRUE; if (pRegion->IsKindOf(CC_RUNTIME_CLASS(AIEPSRenderRegion))) { if (((AIEPSRenderRegion*) pRegion)->GetTextAsShapes () == TRUE) { endTextObject = FALSE; } ((AIEPSRenderRegion*) pRegion)->SetTextAsShapes (FALSE); } // don't export the 'end text object' if we shouldn't if (pRegion->IsKindOf(CC_RUNTIME_CLASS(EPSRenderRegion)) && (endTextObject)) { // Output "End text object" token EPSExportDC *pDC = (EPSExportDC *) pRegion->GetRenderDC(); pDC->OutputToken(_T("TO")); pDC->OutputNewLine(); // output token if text is wrapped if (pRegion->IsKindOf(CC_RUNTIME_CLASS(NativeRenderRegion))) { if (WillStoryWrapOnPath()) { pDC->OutputValue((INT32)EOTAG_TEXTWRAPPED); pDC->OutputToken(_T("cso")); pDC->OutputNewLine(); pDC->OutputToken(_T("ceo")); pDC->OutputNewLine(); } } return TRUE; } #endif return FALSE; } /******************************************************************************************** > BOOL TextStory::EnsureStoryHasCaret() Author: Mark_Neves (Xara Group Ltd) Created: 28/8/96 Returns: FALSE if fails Purpose: Makes sure the story has a caret node ********************************************************************************************/ BOOL TextStory::EnsureStoryHasCaret() { TextLine* pFirstLine = FindFirstLine(); TextLine* pLine = pFirstLine; if (pFirstLine != NULL) { // Scan all the lines looking for a caret node while (pLine != NULL) { // Does the line have a caret? if (pLine->FindCaret() != NULL) return TRUE; // Get next line pLine = pLine->FindNextLine(); } // No caret in the text story, so lets stick one in the first line // just before the first visible text node VisibleTextNode* pVTN = pFirstLine->FindFirstVTN(); if (pVTN != NULL) { CaretNode* pCaret = new CaretNode(pVTN,PREV); if (pCaret != NULL) return TRUE; } } return FALSE; } /******************************************************************************************** > BOOL TextStory::PostImport() Author: Peter_Arnold (Xara Group Ltd) Created: 05/04/95 Returns: FALSE if fails Purpose: This functions is called following the complete import of a document. It allows nodes to complete their post import initialisation once they are in complete tree. The TextStory and its children are formatted following loading. ********************************************************************************************/ BOOL TextStory::PostImport() { #ifdef RALPH // We don't want to be rendering at the same time as doing this, because we get // blobby text problems - fun, but bad ! RalphCriticalSection RalphCS; #endif // Expand any strings that might have been imported from the v2 file format if (!ExpandImportedStrings()) return FALSE; // Make sure it has a caret if (!EnsureStoryHasCaret()) return FALSE; // if not newly inserted, ignore! if (!ModifiedByOp()) return TRUE; // format to a width if one is specified if (GetImportFormatWidth()!=0) { SetWordWrapping(TRUE); SetStoryWidth(GetImportFormatWidth()); ImportFormatWidth=0; // prevent it happening on subsequent imports (as all stories are called!) } // account for any corel baseline shift style BOOL ok = TRUE; if (ok && ImportBaseShift!=AlignBaseline) { ok=CorelStyleBaselineShift(ImportBaseShift); ImportBaseShift=AlignBaseline; // prevent it happening on subsequent imports (as all stories are called!) } // format and redraw if (ok) ok=FormatAndChildren(); if (ok) { BaseDocument* pDoc = FindOwnerDoc(); if (pDoc!=NULL && IS_A(pDoc, Document)) { DocRect temprect = RedrawRect; ReleaseCached(TRUE, FALSE); BOOL bFoundEffects = FALSE; DocRect effectrect = GetEffectStackBounds(&bFoundEffects); if (bFoundEffects) temprect = temprect.Union(effectrect); ((Document*)pDoc)->ForceRedraw(FindParentSpread(), RedrawRect, TRUE, this); } } return ok; } /******************************************************************************************** > BOOL TextStory::PostDuplicate(UndoableOperation* pOp) Author: Peter_Arnold (Xara Group Ltd) Created: 7/10/96 Inputs: pOp - pointer to operation Outputs: - Returns: TRUE/FALSE for success/failure Purpose: This function is called following the (simple)copy of a text story. The TextStory and its children are re-formatted. ********************************************************************************************/ BOOL TextStory::PostDuplicate(UndoableOperation* pOp) { ERROR2IF(pOp==NULL, FALSE, "Operation pointer was NULL"); return PrePostTextAction::DoFormatStory(pOp, this); } /******************************************************************************************** > BOOL TextStory::CorelStyleBaselineShift(BaseShiftEnum BaseShift); Author: Ed_Cornes (Xara Group Ltd) Created: 5/7/95 Inputs: BaseShift - Returns: FALSE if fails Purpose: ********************************************************************************************/ BOOL TextStory::CorelStyleBaselineShift(BaseShiftEnum BaseShift) { TextLine* pTextLine = this->FindFirstLine(); ERROR2IF(pTextLine==NULL,FALSE,"TextStory::CorelStyleBaselineShift() - couldn't find text line in story"); MILLIPOINT BaselineShift=0; MILLIPOINT LineAscent=(pTextLine->GetLineAscent()*4)/5; switch (BaseShift) { case AlignAscenders: BaselineShift=-LineAscent; break; case AlignDescenders: BaselineShift=-pTextLine->GetLineDescent(); break; case AlignCentres: BaselineShift=(-LineAscent-pTextLine->GetLineDescent())/2; break; default: break; } TxtBaseLineAttribute BaselineAttr(BaselineShift); BOOL ok = ((AttributeValue*)&BaselineAttr)->MakeNode(this,FIRSTCHILD) != NULL; return ok; } /******************************************************************************************** > BOOL TextStory::ImportFix_MoveScaleToAttrs(); Author: Ed_Cornes (Xara Group Ltd) Created: 4/6/95 Returns: FALSE if fails Purpose: Move the scale from the story's matrix down to its attributes (ie fix up docs which were created with partly developed by code!) ********************************************************************************************/ BOOL TextStory::ImportFix_MoveScaleToAttrs() { BOOL ok=TRUE; // remove scale from story matrix // Localise // for attrs on story/line/chars if txtattr and fontsize/linespace/baseline, scale // FactorOut return ok; } /******************************************************************************************* > BOOL TextStory::CreateUntransformedPath(TextStoryInfo* pStoryInfo) Author: Ed_Cornes (Xara Group Ltd) Created: 17/4/95 Outputs: pStoryInfo - info required about the path Returns: FALSE if fails Purpose: Create a copy of the story's path removing the Story matrix transform Also, reverse path if text is reversed on the path Note: This should really be broken up into member functions of Path ********************************************************************************************/ BOOL TextStory::CreateUntransformedPath(TextStoryInfo* pStoryInfo) { ERROR2IF(pStoryInfo==NULL,FALSE,"TextStory::CreateUntransformedPath() - pStoryInfo==NULL"); // get pointers to info in text story's NodePath NodePath* pNodePath = GetTextPath(); ERROR2IF(pNodePath==NULL,FALSE,"TextStory::CreateUntransformedPath() - the TextStory has no path!!"); INT32 NumCoords = pNodePath->InkPath.GetNumCoords(); DocCoord* pNodePathCoords = pNodePath->InkPath.GetCoordArray(); PathVerb* pNodePathVerbs = pNodePath->InkPath.GetVerbArray(); BOOL ok = (NumCoords>=2 && pNodePathCoords!=NULL && pNodePathVerbs!=NULL); ERROR2IF(!ok,FALSE,"TextStory::CreateUntransformedPath() - problem with NodePath in story"); // create a copy of the transformed path Path* pPath = new Path; ERROR2IF(pPath==NULL,FALSE,"TextStory::CreateUntransformedPath() - failed to crete path"); if (ok) ok=pPath->Initialise(NumCoords, 12); if (ok) ok=pPath->CopyPathDataFrom(pNodePathCoords, pNodePathVerbs, NumCoords, TRUE); DocCoord* pPathCoords = NULL; PathVerb* pPathVerbs = NULL; if (ok) { pPathCoords = pPath->GetCoordArray(); pPathVerbs = pPath->GetVerbArray(); ok = (pPathCoords!=NULL && pPathVerbs!=NULL); if (!ok) ERROR2RAW("TextStory::CreateUntransformedPath() - Problem copying path"); } // if text reversed on path, reverse path if (ok && IsTextOnPathReversed()) pPath->Reverse(); // remove the story transform from the copy of the path if (ok) { Matrix matrix = GetStoryMatrix(); matrix = matrix.Inverse(); matrix.transform((Coord*)pPathCoords, NumCoords); } // find the untransformed path length if (ok) { double fPathLength = 0; ProcessLength PathLengthProcess(64); ok = PathLengthProcess.PathLength(pPath, &fPathLength); pStoryInfo->PathLength = (MILLIPOINT)(fPathLength+0.5); } // determine if the path is closed if (ok) pStoryInfo->PathClosed = ( (pPathVerbs[NumCoords-1] & PT_CLOSEFIGURE) != 0); // determine direction of perpendicular to first point (downwards) if (ok) { // get the x,y offsets ratios for the line position at a perpendicular to the first point on the path ... double dx = pPathCoords[1].x-pPathCoords[0].x; double dy = pPathCoords[1].y-pPathCoords[0].y; double LineLength = sqrt(dx*dx+dy*dy); pStoryInfo->UnitDirectionVectorX = -dy/LineLength; pStoryInfo->UnitDirectionVectorY = dx/LineLength; } pStoryInfo->LeftPathIndent = GetLeftIndent(); pStoryInfo->RightPathIndent = GetRightIndent(); // if not ok and we created a path, delete it if (!ok && pPath!=NULL) { delete pPath; pPath=NULL; } // set output pStoryInfo->pPath = pPath; return ok; } /******************************************************************************************* > void TextStory::MatrixFitToPath() Author: Ed_Cornes (Xara Group Ltd) Created: 17/4/95 Purpose: modify TextStory matrix appropriately when fitting text to a path ********************************************************************************************/ void TextStory::MatrixFitToPath() { DocCoord Translation(0,0); BOOL ok=GetpStoryMatrix()->Decompose(&CharsScale,&CharsAspect,&CharsRotation,&CharsShear,&Translation); ERROR2IF(!ok,void(0),"TextStory::MatrixFitToPath() - Matrix::Decompose() failed"); // if text rotated to 2nd/3rd quadrant, reverse text on curve if (CharsRotation<(ANGLE)(-PI/2) || CharsRotation>(ANGLE)(PI/2)) { ReverseTextOnPath(); CharsRotation = (CharsRotation>0) ? CharsRotation-PI : CharsRotation+PI; } StoryMatrix=Matrix(Translation.x,Translation.y); // keep a record of translation } /******************************************************************************************* > void TextStory::MatrixRemoveFromPath() Author: Ed_Cornes (Xara Group Ltd) Created: 17/4/95 Purpose: modify TextStory matrix appropriately when removing text from a path ********************************************************************************************/ void TextStory::MatrixRemoveFromPath() { if (IsTextOnPathReversed()) { CharsRotation = (CharsRotation>0) ? CharsRotation-PI : CharsRotation+PI; ReverseTextOnPath(); } Matrix CharsMatrix; CharsMatrix.Compose(CharsScale,CharsAspect,CharsRotation,CharsShear); CharsMatrix*=StoryMatrix; SetStoryMatrix(CharsMatrix); } /******************************************************************************************** > VisibleTextNode* TextStory::FindFirstVTN() Author: Peter_Arnold (Xara Group Ltd) Created: 27/04/95 Returns: A pointer to the first VisibleTextNode in the story. NULL if error ********************************************************************************************/ VisibleTextNode* TextStory::FindFirstVTN() const { TextLine* pFirstLine = this->FindFirstLine(); ERROR2IF(pFirstLine==NULL,NULL,"TextStory::FindFirstVisibleTextNode() - No TextLine in the TextStory"); return pFirstLine->FindFirstVTN(); } /******************************************************************************************** > VisibleTextNode* TextStory::FindLastVTN() Author: Ed_Cornes (Xara Group Ltd) Created: 8/7/96 Returns: A pointer to the last VisibleTextNode in the story. NULL if error ********************************************************************************************/ VisibleTextNode* TextStory::FindLastVTN() const { TextLine* pLastLine = this->FindLastLine(); ERROR2IF(pLastLine==NULL,NULL,"TextStory::FindLastVTN() - story has no lines!"); return pLastLine->FindLastVTN(); } /******************************************************************************************** > virtual BOOL TextStory::OnNodePopUp(Spread* pSpread, DocCoord PointerPos, ContextMenu* pMenu) Author: Peter_Arnold (Xara Group Ltd) Created: 27/04/95 Inputs: pSpread The spread in which things are happening PointerPos The Location of the mouse pointer at the time of the click pMenu The menu to which items should be added Returns: BOOL - TRUE if the node claims the click as its own and FALSE if it is not interested in the click Purpose: Allows the TextStory to respond to pop up menu clicks on itself. ********************************************************************************************/ BOOL TextStory::OnNodePopUp(Spread* pSpread, DocCoord PointerPos, ContextMenu* pMenu) { #if !defined(EXCLUDE_FROM_RALPH) BOOL ok = TRUE; ok = ok && pMenu->BuildCommand(TOOL_OPTOKEN_TEXT, TRUE); // ok = ok && pMenu->BuildCommand(OPTOKEN_APPLYLEFTJUSTIFY); // ok = ok && pMenu->BuildCommand(OPTOKEN_APPLYCENTREJUSTIFY); // ok = ok && pMenu->BuildCommand(OPTOKEN_APPLYRIGHTJUSTIFY); // ok = ok && pMenu->BuildCommand(OPTOKEN_APPLYFULLJUSTIFY, TRUE); // WEBSTER-ranbirr-13/11/96 ok = ok && pMenu->BuildCommand(OPTOKEN_REVERSESTORYPATH); #ifndef WEBSTER ok = ok && pMenu->BuildCommand(OPTOKEN_TOGGLEPRINTASSHAPES, TRUE); #endif //webster return ok; #else return FALSE; #endif } /******************************************************************************************** > BOOL TextStory::DeleteSelectedText(UndoableOperation* pUndoOp) Author: Ed_Cornes (Xara Group Ltd) Created: 20/2/96 Inputs: pUndoOp - Returns: FALSE if fails Purpose: Delete all selected text objects in story, deleteing whole lines where possible ********************************************************************************************/ BOOL TextStory::DeleteSelectedText(UndoableOperation* pUndoOp) { #if !defined(EXCLUDE_FROM_RALPH) ERROR2IF(pUndoOp==NULL,FALSE,"TextStory::DeleteSelectedText() - pUndoOp==NULL"); BOOL ok = DeleteSelectedTextLines(pUndoOp); if (ok) ok = DeleteSelectedTextCharacters(pUndoOp); return ok; #else return FALSE; #endif } /******************************************************************************************** > BOOL TextStory::DeleteSelectedTextLines(UndoableOperation* pUndoOp) Author: Ed_Cornes (Xara Group Ltd) Created: 9/7/96 Inputs: pUndoOp - Returns: FALSE if fails Purpose: Called by DeleteSelectedText to delete all selected TextLines (including TextLines will all children selected) ********************************************************************************************/ BOOL TextStory::DeleteSelectedTextLines(UndoableOperation* pUndoOp) { #if !defined(EXCLUDE_FROM_RALPH) ERROR2IF(pUndoOp==NULL,FALSE,"TextStory::DeleteSelectedTextLines() - pUndoOp==NULL"); // Delete all selected lines except the last one BOOL AttrsLocalisedOnStory = FALSE; TextLine* pLine = this->FindFirstLine(); ERROR2IF(pLine==NULL,FALSE,"TextStory::DeleteSelectedTextLines() - story has no lines"); while (pLine!=NULL) { TextLine* pNextLine = pLine->FindNextLine(); if (pLine->WholeLineSelected()) { // If caret on line to be deleted, move it to next line CaretNode* pCaret = pLine->FindCaret(); if (pCaret!=NULL) { BaseTextClass* pAttachNode = NULL; AttachNodeDirection AttachDir = PREV; TextLine* pPrevLine = pLine->FindPrevLine(); if (pNextLine!=NULL) { pAttachNode = pNextLine->FindFirstVTN(); ERROR2IF(pAttachNode==NULL,FALSE,"TextStory::DeleteSelectedTextLines() - text line has no VTN"); AttachDir = PREV; } else if (pPrevLine!=NULL) { pAttachNode = pPrevLine->FindLastVTN(); ERROR2IF(pAttachNode==NULL,FALSE,"TextStory::DeleteSelectedTextLines() - text line has no VTN"); if (IS_A(pAttachNode,EOLNode)) AttachDir = PREV; else AttachDir = NEXT; } else // deleting only line in story, so create a new one { pAttachNode = TextLine::CreateEmptyTextLine(); if (pAttachNode==NULL) return FALSE; if (!pUndoOp->DoInsertNewNode(pAttachNode,pLine,NEXT, FALSE,FALSE,FALSE,FALSE)) return FALSE; AttachDir = FIRSTCHILD; } if (!PositionCaretAction::DoStoreCaretPosition(pUndoOp,this)) return FALSE; pCaret->MakeAttributeComplete(); pCaret->MoveNode(pAttachNode,AttachDir); // caret should not be selected pCaret->NormaliseAttributes(); } // if not already localised, loaclise attrs on story, then hide line if (!AttrsLocalisedOnStory) if (!pUndoOp->DoLocaliseCommonAttributes(this,FALSE,TRUE)) return FALSE; AttrsLocalisedOnStory = TRUE; if (!pLine->DoHideNode(pUndoOp)) return FALSE; } pLine = pNextLine; } // if attrs were loaclised on story, factor out if (AttrsLocalisedOnStory) if (!pUndoOp->DoFactorOutCommonChildAttributes(this, TRUE)) return FALSE; #endif return TRUE; } /******************************************************************************************** > BOOL TextStory::DeleteSelectedTextCharacters(UndoableOperation* pUndoOp) Author: Ed_Cornes (Xara Group Ltd) Created: 20/2/96 Inputs: pUndoOp - Returns: FALSE if fails Purpose: delete all (remaining) selected VTN in story, joining lines if EOL deleted and story not word wrapping ********************************************************************************************/ BOOL TextStory::DeleteSelectedTextCharacters(UndoableOperation* pUndoOp) { #if !defined(EXCLUDE_FROM_RALPH) ERROR2IF(pUndoOp==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - pUndoOp==NULL"); // get a set of non line level attrs on this story, which are to be localised to char level AttrTypeSet NonLineLevelAttrs; BOOL ok = this->AddNonLineLevelDescendantAttrsToSet(&NonLineLevelAttrs); // delete all the selected chars (except caret) in the story, joining line if EOL deleted TextLine* pLine = this->FindFirstLine(); ERROR2IF(pLine==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - story has no lines"); while (ok && pLine!=NULL) { BOOL AttrsLocalisedOnLine = FALSE; VisibleTextNode* pVTN = pLine->FindFirstVTN(); ERROR2IF(pVTN==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - line has no VTN"); while (ok && pVTN!=NULL) { // ensure last EOL in story not selected (so not deleted) if (pVTN->IsAnEOLNode() && pVTN->IsSelected() && pLine->FindNextLine()==NULL) { ERROR3("TextStory::DeleteSelectedTextCharacters() - last EOL of the story selected"); pVTN->DeSelect(FALSE); } // if char not to be deleted, skip to next char, else ... if (!pVTN->IsSelected() || pVTN->IsACaret()) pVTN = pVTN->FindNextVTNInLine(); else { // if attrs not already loaclised on this line, do so if (!AttrsLocalisedOnLine) ok = pUndoOp->DoLocaliseCommonAttributes(pLine,FALSE,TRUE,&NonLineLevelAttrs); AttrsLocalisedOnLine = TRUE; // if deleting EOL move all chars from next line to end of this and hide next line if (pVTN->IsAnEOLNode()) { TextLine* pNextLine = pLine->FindNextLine(); ERROR2IF(pNextLine==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - trying to delete last EOL in story!"); VisibleTextNode* pFirstVTN = pNextLine->FindFirstVTN(); VisibleTextNode* pLastVTN = pNextLine->FindLastVTN(); ERROR2IF(pFirstVTN==NULL || pLastVTN==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - no VTN on next line"); if (ok) ok = pUndoOp->DoLocaliseCommonAttributes(pNextLine,FALSE,TRUE,&NonLineLevelAttrs); if (ok) ok = pFirstVTN->DoMoveNodes(pUndoOp,pLastVTN,pLine,LASTCHILD); if (ok) ok = pNextLine->DoHideNode(pUndoOp); } // hide VTN, getting pointer to next VisibleTextNode* pNextVTN = pVTN->FindNextVTNInLine(); if (ok) ok = pVTN->DoHideNode(pUndoOp); pVTN = pNextVTN; } } // if ok and attrs were localised on the line, factor out if (ok && AttrsLocalisedOnLine) ok = pUndoOp->DoFactorOutCommonChildAttributes(pLine,TRUE,&NonLineLevelAttrs); pLine = pLine->FindNextLine(); } return ok; #else return FALSE; #endif } /******************************************************************************************** > INT32 TextStory::BaseComplexCopy(CopyStage Stage, Range& RangeToCopy, Node** pOutput) Author: Mike_Kenny (Xara Group Ltd) Created: 3/5/95 Inputs: Stage - either COPYOBJECT COPYFINISHED RangeToCopy - Describes the range which is currently being copied. pOutput - Depends on the Stage parameter if Stage= COPYOBJECT Then the node pointer pOutput points at NULL. It should be set on exit to point at the copied object or tree of objects COPYFINISHED Then the node pointer pOutput points at the resulting copy from COPYOBJECT as it has been inserted into the destination tree. Returns: -1 = The routine failed to make a copy. 0 = No copy has been made. +1 = pOutput points to the copy. Purpose: ********************************************************************************************/ INT32 TextStory::BaseComplexCopy(CopyStage Stage, Range& RangeToCopy, Node** pOutput) { ERROR2IF(pOutput==NULL,FALSE,"TextStory::RootComplexCopy() called with NULL output pointer"); switch (Stage) { case COPYOBJECT: if (!BeingCopied) { BeingCopied = TRUE; if (Copy(RangeToCopy, pOutput)) return 1; else return -1; } return 0; break; case COPYFINISHED: if (BeingCopied) { BeingCopied = FALSE; Node* pCopy = (*pOutput); if (pCopy && (IS_A(pCopy,TextStory))) ((TextStory*)pCopy)->FormatAndChildren(NULL,FALSE,FALSE); } return 0; break; default: return -1; break; } } /******************************************************************************************** > BOOL TextStory::Copy(Range& RangeToCopy, Node** pOutput) Author: Mike_Kenny (Xara Group Ltd) Created: 3/5/95 Inputs: RangeToCopy - Describes the range which is currently being copied. pOutput - a pointer to a node. Set this pointer to point at the tree copy Returns: FALSE if fails Purpose: Make a copy of the text story and all children which lie inside RangeToCopy ********************************************************************************************/ BOOL TextStory::Copy(Range& RangeToCopy, Node** pOutput) { ERROR2IF(pOutput==NULL,FALSE,"TextStory::Copy() called with NULL output pointer"); /* Notes: ok, at this stage, each object in the copy range has been called with MakeAttributesComplete() apart from those that state they are complex copy nodes. Here I expect simply to make attributes complete on the text story and assume the rest of the subtree is correct. Until I changed this OpCopy used to MakeAttributesComplete on the whole selected range. This was a problem, as we are actually copying more than the selected range. We are in effect grouping the selected range under a parent. On pasting, the paste code only (sensibly) normalises on all first level siblings and hence misses our 'grouped' children, the result being all selected characters having thousands of atts applied to them which cannot be factored out. */ // copy the story TextStory* pStoryCopy = (TextStory*)SimpleCopy(); BOOL ok = (pStoryCopy!=NULL); // Make the attrs complete so we can copy the story correctly if (ok) ok = MakeAttributeComplete(); if (ok) { // Mark all nodes in this current range // why is this not part of SetStoryMarkers?! Node* pNode = RangeToCopy.FindFirst(TRUE); while (pNode) { pNode->SetMarker(TRUE); pNode=RangeToCopy.FindNext(pNode,TRUE); } // specifically set the flags to copy the correct nodes SetStoryMarkers(RangeToCopy); ok = CopyChildrenTo(pStoryCopy, ccMARKED); // Now that EOLNodes take attributes we have all sorts of bother with the last EOL. // The user can't put attributes onto it. We must copy // the attributes applied to the ATC prior to the last EOL onto the EOL. if (ok) { VisibleTextNode* pLastNode = pStoryCopy->FindLastVTN(); AbstractTextChar* pPrevLast = NULL; if (pLastNode!=NULL) pPrevLast = (AbstractTextChar*)pLastNode->FindPrevious(CC_RUNTIME_CLASS(AbstractTextChar)); if (pLastNode!=NULL && pPrevLast!=NULL) { // Clear out any attributes off the last EOLNode Node* pLastChild = pLastNode->FindFirstChild(); if (pLastChild!=NULL) pLastNode->DeleteChildren(pLastChild); // Copy the correct ones onto it. if (pPrevLast->FindFirstChild() != NULL) ok = pPrevLast->CopyChildrenTo(pLastNode); } } // Mark all nodes in this current range // is this not made redundant by ClearMarks()?! pNode = RangeToCopy.FindFirst(TRUE); while (pNode) { pNode->SetMarker(FALSE); pNode=RangeToCopy.FindNext(pNode,TRUE); } ClearMarks(); // clear flags on source story pStoryCopy->ClearMarks(); // clear flags on destination story ResetEOLs(FALSE); // make EOLs physical again (ie not virtual!) // why does this take a param?! NormaliseAttributes(); // on source story // why not dest? } // unwrap the story on the clipboard if (ok) ok = pStoryCopy->UnWrapStory(NULL); // insert a caret into the copied story CaretNode* pCaret = NULL; VisibleTextNode* pAttachNode = NULL; if (ok) pAttachNode = pStoryCopy->FindFirstVTN(); ok = (pAttachNode!=NULL); if (ok) pCaret = new CaretNode; ok = (pCaret!=NULL); // should an error be set if caret not created? if (ok) pCaret->AttachNode(pAttachNode, PREV); // if we failed but a story copy was created, delete it if (!ok && pStoryCopy!=NULL) { pStoryCopy->CascadeDelete(); delete pStoryCopy; pStoryCopy = NULL; } *pOutput = pStoryCopy; return ok; } /******************************************************************************************** > BOOL TextStory::UnWrapStory(UndoableOperation* pUndoOp) Author: Ed_Cornes (Xara Group Ltd) Created: 10/3/96 Inputs: pUndoOp - undoable operation (NULL if not undo-able) Purpose: Join consecutive soft lines into single hard lines and append a final (virtual) EOL to the story if none ********************************************************************************************/ BOOL TextStory::UnWrapStory(UndoableOperation* pUndoOp) { TextLine* pThisLine = FindFirstLine(); ERROR2IF(pThisLine==NULL,FALSE,"TextStory::UnWrapStory() - story has no text lines!"); while (pThisLine!=NULL) { TextLine* pNextLine = pThisLine->FindNextLine(); if (pThisLine->FindEOLNode()==NULL) { if (pNextLine!=NULL) { // wrap next line back to end of this VisibleTextNode* pLastVTN = pNextLine->FindLastVTN(); ERROR2IF(pLastVTN==NULL,FALSE,"TextStory::UnWrapStory() - no VTN on soft line"); pLastVTN->WrapFromStartOfLineBack(pUndoOp); } else { // add a virtual EOL to keep Mike's stuff from falling over EOLNode* pEOL = new EOLNode; if (pEOL==NULL) return FALSE; pEOL->SetVirtual(TRUE); pEOL->AttachNode(pThisLine,LASTCHILD); } } else pThisLine = pNextLine; // move onto start of next paragraph } // ensure op flags not set in story on clipboard ClearNodeAndDescendantsFlags(); Validate(); return TRUE; } /******************************************************************************************** > void TextStory::SetStoryMarkers(Range& RangeToCopy) Author: Mike_Kenny (Xara Group Ltd) Created: 3/5/95 Inputs: RangeToCopy - Describes the range which is currently being copied. Purpose: Scan through the text story setting the necessary copyme bits to make sure the text story is copied correctly. The range is used to determin what items are actually being copied. If characters are being copied, we need to copy a line and text story as well. ********************************************************************************************/ void TextStory::SetStoryMarkers(Range& RangeToCopy) { // find the first object in the story Node* pNode = FindFirstChild(); while (pNode) { if (pNode->IsAnAttribute()) { pNode->MarkNodeAndChildren(); } else { // ok check if its a line and if so do all the character level stuff if (IS_A(pNode,TextLine)) { // find the first child of this particular line Node* cNode = NULL; Node* qNode = pNode->FindFirstChild(); while (qNode) { // Mark in range characters, kern codes and other abstract characters // Only copy the char if it is in the search range. if (qNode->IsMarked()) { qNode->MarkNodeAndChildren(); // Save a copy of this chap for later cNode=qNode; } qNode=qNode->FindNext(); } // we've found an item inrange in this line..... if (cNode) { // Make sure we mark the line too qNode = cNode->FindParent(); qNode->SetMarker(TRUE); // take all the line level attributes with us too qNode = qNode->FindFirstChild(); while (qNode) { if (qNode->IsAnAttribute()) qNode->MarkNodeAndChildren(); qNode=qNode->FindNext(); } // Now we need to make sure we take the end of line node with us. qNode=cNode->FindParent(); qNode=qNode->FindFirstChild(CC_RUNTIME_CLASS(EOLNode)); ERROR3IF((qNode) && (!IS_A(qNode,EOLNode)),"Where has the lines EOLnode gone? - (TextStory::SetStoryMarkers())"); // We assume the last node is an EOLnode. if (qNode && IS_A(qNode,EOLNode)) { if (!qNode->IsMarked()) { // It wasn't already marked ie it can't have been 'inrange' qNode->MarkNodeAndChildren(); // We need to mark the eolnode as transient // ie so we know the difference between unselected // and selected nodes. ((EOLNode*)qNode)->SetVirtual(TRUE); } } } } else { // otherwise its not a line and its not an attribute so is it in the range? // if its in range, take it and its children with us if (pNode->IsMarked()) pNode->MarkNodeAndChildren(); } } // Scan for the next object pNode=pNode->FindNext(); } /* I suppose we need to take the caret too, erh no we need to wait and insert a caret later, it could be in a different line to the selection if select inside has occured! and hence wont be copied properly CaretNode* pCaret = GetCaret(); if (pCaret) pCaret->MarkNodeAndChildren(); */ } /******************************************************************************************** > void TextStory::ResetEOLs(BOOL Status) Author: Mike_Kenny (Xara Group Ltd) Created: 10/5/95 Inputs: Status - the status to set for each EOL node. Purpose: Scan through all end of line nodes in this text story and set the status of their virtual flag ********************************************************************************************/ void TextStory::ResetEOLs(BOOL Status) { // find the first line in this story TextLine* pLine = this->FindFirstLine(); while (pLine) { // for each line, set its EOL nodes status EOLNode* pEOL = pLine->FindEOLNode(); if (pEOL!=NULL) pEOL->SetVirtual(Status); pLine=pLine->FindNextLine(); } } /******************************************************************************************** > INT32 TextStory::BaseComplexHide(UndoableOperation *pOp) Author: Mike_Kenny (Xara Group Ltd) Created: 3/5/95 Inputs: pOp - a pointer to an undoable operation Returns: -1 = The routine failed to hide this node. 0 = Ignored, this object does not support complex hide operations +1 = The node and possibly various others have been hidden correctly. Purpose: This function handles the text story being hidden during operations such as OpDelete / OpCut / etc. The function is called indirectly via it child nodes. It should never be called directly. Its purpose is to cope with various parts of the text story being hidden. It is not possible to DoHideNodes() on a selection of the text stories children as this may leave the story in an invalid state. Hence all children ask their parent text story to sort out the problem. The function is usually called from a derived version of Node::ComplexHide() ********************************************************************************************/ INT32 TextStory::BaseComplexHide(UndoableOperation *pOp) { ERROR2IF(pOp==NULL, -1, "Operation pointer is NULL in TextStory::BaseComplexHide()"); if (!DeleteSelectedText(pOp)) return -1; #if !defined(EXCLUDE_FROM_RALPH) // try to regain the caret on completion TextInfoBarOp::RegainCaretOnOpEnd(); #endif return 1; } /******************************************************************************************** > static TextStory* TextStory::CreateFromChars(DocCoord Pos, char* pChars, WCHAR* pWChars, Document* pCreateDoc, LOGFONT* pLogFont=NULL, BOOL ControlCodes=FALSE DocColour* pColour=NULL) Author: Ed_Cornes (Xara Group Ltd) Created: 25/5/95 Inputs: Pos - coord in doc at which to position TextStory pChars - pointer to array of chars ('\0' terminated) pWChars - pointer to array of WCHARs ('\0' terminated) pCreateDoc - pointer to document story is to be created in pLogFont - optional pointer to a LogFont from which to derive attributes ControlCodes - TRUE if control codes should also be processed pColour - text colour Returns: Pointer to TextStory, or NULL if fails Purpose: Create a TextStory from an array of chars OR WCHARs and optionally account for control codes and a LogFont (ie size/aspect/bold/italic/typeface/rotation) Notes: Calls ContinueSlowJob periodically to allow progress displays to be updated ********************************************************************************************/ TextStory* TextStory::CreateFromChars(DocCoord Pos, char* pChars, WCHAR* pWChars, Document* pCreateDoc, LOGFONT* pLogFont, BOOL ControlCodes, DocColour* pColour) { ERROR2IF(pChars==NULL && pWChars==NULL || pChars!=NULL && pWChars!=NULL, NULL, "TextStory::CreateFromCharArray() - must specify one and only one char array"); ERROR2IF(pCreateDoc==NULL, NULL, "No creation document"); // create an empty text story, getting pointers to TextLine and Caret TextStory* pTextStory=TextStory::CreateTextObject(Pos); TextLine* pTextLine =NULL; CaretNode* pCaretNode=NULL; BOOL ok=(pTextStory!=NULL); if (ok) { pTextLine = pTextStory->FindFirstLine(); if (pTextLine!=NULL) pCaretNode=pTextLine->FindCaret(); if (pCaretNode==NULL) { ok=FALSE; ERROR2RAW("TextStory::CreateFromChars() - failed to find TextLine or Caret"); } } // Put the current attributes onto the story if (ok) ok = pCreateDoc->GetAttributeMgr().ApplyCurrentAttribsToNode(pTextStory); // Before forcing on attributes we need to delete the current instances first if (ok) ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrFillGeometry)); // if (ok) // ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrStrokeTransp)); if (ok) ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrLineWidth)); if (ok) ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrStrokeColour)); // add black fill colour, no/transparent line colour, 0.25pt line width attributes if (ok) { FlatFillAttribute FlatFillAttr(pColour!=NULL ? *pColour : AttributeManager::DefaultBlack); StrokeTranspAttribute StrokeTranspAttr(255); LineWidthAttribute LineWidthAttr(250); if (ok) ok = ((AttributeValue*) &FlatFillAttr)->MakeNode(pTextLine,PREV) != NULL; // if (ok) ok = ((AttributeValue*)&StrokeTranspAttr)->MakeNode(pTextLine,PREV) != NULL; if (ok) ok = ((AttributeValue*) &LineWidthAttr)->MakeNode(pTextLine,PREV) != NULL; // BODGE TEXT - this is the only way this attribute will work!!!! AttributeValue* pAttr=NULL; DocColour trans(COLOUR_TRANS); if (ok) ok = NULL!=(pAttr=new StrokeColourAttribute(trans)); if (ok) ok = NULL!=pAttr->MakeNode(pTextLine,PREV); if (pAttr!=NULL) delete pAttr; } // if we have a LogFont, set story rotation, add typeface, size, aspect, bold, italic attributes if (ok && pLogFont!=NULL) { // Delete the current instances of the attribute first if (ok) ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtFontTypeface)); if (ok) ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtFontSize)); if (ok) ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtAspectRatio)); if (ok) ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtBold)); if (ok) ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtItalic)); if (ok) { FontInfo info; ok=TextManager::GetInfoFromLogFont(&info,pLogFont); if (ok) { Matrix StoryMatrix(info.Rotation); StoryMatrix.translate(Pos.x,Pos.y); pTextStory->SetStoryMatrix(StoryMatrix); TxtFontTypefaceAttribute FontAttr(info.Handle); TxtFontSizeAttribute SizeAttr(info.Size); TxtAspectRatioAttribute AspectAttr(info.Aspect); TxtBoldAttribute BoldAttr(info.Bold); TxtItalicAttribute ItalicAttr(info.Italic); if (ok) ok = ((AttributeValue*) &FontAttr)->MakeNode(pTextLine,PREV) != NULL; if (ok) ok = ((AttributeValue*) &SizeAttr)->MakeNode(pTextLine,PREV) != NULL; if (ok) ok = ((AttributeValue*)&AspectAttr)->MakeNode(pTextLine,PREV) != NULL; if (ok) ok = ((AttributeValue*) &BoldAttr)->MakeNode(pTextLine,PREV) != NULL; if (ok) ok = ((AttributeValue*)&ItalicAttr)->MakeNode(pTextLine,PREV) != NULL; } } } // insert each char before the caret INT32 index=0; WCHAR LastChar=0; while (ok) { // get a char from the appropriate array, increase index, exitting if none left WCHAR ch = 0; if (pChars==NULL) ch = pWChars[index++] ; else { // Read the next character from the array if (UnicodeManager::IsDBCSOS() && UnicodeManager::IsDBCSLeadByte(pChars[index])) { ch = UnicodeManager::ComposeMultiBytes(pChars[index], pChars[index+1]); index += 2; } else { ch = (unsigned char)(pChars[index]); index += 1; } // Convert into Unicode if it's not a control char if ((pChars != NULL) && (ch>=32 && ch!=127)) ch = UnicodeManager::MultiByteToUnicode(ch); } if (ch=='\0') break; // if not a control code, just insert the char to the left of the caret // else, if handling control codes, do what is required if (ch>=32 && ch!=127) { TextChar* pTextChar = new TextChar(pCaretNode, PREV, ch); ok=(pTextChar!=NULL); } else if (ControlCodes) { switch (ch) { case '\n': { // Give the progress system a chance to update its displays ContinueSlowJob(); // if part of '\r\n' pair ignore it as already insereted new line, // else continue into '\r' case and insert a new line if (LastChar=='\r') break; } case '\r': // insert a new line { pTextLine = TextLine::CreateEmptyTextLine(pTextLine, NEXT); ok=(pTextLine!=NULL); if (ok) pCaretNode->MoveNode(pTextLine,FIRSTCHILD); break; } case '\t': // insert a horizontal tab { HorizontalTab* pTab = new HorizontalTab(pCaretNode, PREV); ok = (pTab != NULL); break; } default: TRACE( _T("Control Code %d, not yet supported\n"),ch); } } LastChar=ch; } if (ok) { EOLNode* pLastEOL=(EOLNode*)(pCaretNode->FindNext()); ok=(pLastEOL!=NULL); if (ok) pLastEOL->SetVirtual(TRUE); else ERROR2RAW("TextStory::CreateFromChars() - couldn't EOL after caret!"); } // if we failed to finish the job, tidy up if (!ok) { pTextStory->CascadeDelete(); delete pTextStory; pTextStory=NULL; } return pTextStory; } /******************************************************************************************** > BOOL TextStory::WillStoryWrapOnPath() Author: Mike_Kenny (Xara Group Ltd) Created: 11/6/95 Inputs: - Returns: TRUE if the text story will wrap on this path FALSE if not Purpose: Calculates whether the formatter will wrap any line of text around the path It calculates this by checking the position of the last character (or VTN in each line). If this wraps it is assumed the whole line will wrap. Note text is assumed to only ever wrap on a path which is closed. ********************************************************************************************/ BOOL TextStory::WillStoryWrapOnPath() { // BODGE TEXT - horrible copy of code elsewhere // first find the text path. NodePath* pNodePath = GetTextPath(); if (pNodePath==NULL) return FALSE; Path* pPath = &(pNodePath->InkPath); // if the path is open, text never wraps. INT32 NumPathCoords=pPath->GetNumCoords(); if (NumPathCoords<2) return FALSE; BOOL PathClosed=FALSE; PathVerb* pPathVerbs=pPath->GetVerbArray(); ERROR2IF(pPathVerbs==NULL,FALSE,"TextStory::IsStoryWrappedOnpath() - pPathVerbs==NULL"); if (pPathVerbs[NumPathCoords-1] & PT_CLOSEFIGURE) PathClosed=TRUE; if (!PathClosed) return FALSE; // find the untransformed path length MILLIPOINT PathLength = 0; double fPathLength=0; ProcessLength PathLengthProcess(64); if (PathLengthProcess.PathLength(pPath,&fPathLength)) PathLength=(MILLIPOINT)fPathLength; FIXED16 scale = GetCharsScale(); FIXED16 xscale = (scale<0 ? -scale : scale) * GetCharsAspect(); BOOL Wrapped = FALSE; TextLine* pLine = FindFirstLine(); while (pLine && !Wrapped) { Wrapped = pLine->WillLineWrapOnPath(xscale,PathLength); pLine = pLine->FindNextLine(); } return Wrapped; } /******************************************************************************************** > static DocCoord TextStory::GetPathBlobPos(MILLIPOINT IndentLength, NodePath* pPath) Author: Peter_Arnold (Xara Group Ltd) Created: 16/12/95 Inputs: IndentLength - the indent length pPath - pointer to the path Returns: The position of a point along the path Purpose: Gets the location of the of a point a certian distance along the path. This function is static so others, eg the drag op, can call it and it's scary math ********************************************************************************************/ DocCoord TextStory::GetPathBlobPos(MILLIPOINT IndentLength, NodePath* pPath) { DocCoord Result(0,0); pPath->InkPath.GetPointAtDistance(IndentLength, &Result); return Result; } /******************************************************************************************** > BOOL TextStory::GetCharacterAtPoint(BOOL InBounds, DocCoord Point, VisibleTextNode** pHitChar) Author: Peter_Arnold (Xara Group Ltd) Created: 22/1/96 Inputs: InBounds - if TRUE then Point must be within the bounds of the story to find a character Point - the location in question Outputs: pHitChar - the character the point is near, NULL if there isn't a character ToLeft - true if the point is to the left of the character, FALSE if to the right Returns: TRUE for success, FALSE if an error occured Purpose: Given a location works out the nearest character to that location. ********************************************************************************************/ BOOL TextStory::GetCharacterAtPoint(BOOL InBounds, DocCoord Point, VisibleTextNode** pHitChar, BOOL* ToLeft) { ERROR3IF(GetTextPath()!=NULL, "GetCharacterAtPoint : Results are unpredictable for stories on a path"); // Get inital pointers, checking them ERROR2IF(pHitChar==NULL || ToLeft==NULL, FALSE, "NULL output param"); CaretNode* pCaret = GetCaret(); ERROR2IF(pCaret == NULL, FALSE, "Story has no caret"); TextLine* pFirstLine = FindFirstLine(); TextLine* pLastLine = FindLastLine(); ERROR2IF((pFirstLine==NULL || pLastLine==NULL), FALSE, "Story has no text lines"); // Set outputs to defaults *pHitChar = NULL; *ToLeft = FALSE; // Untransform the point via the story matrix DocCoord UTPoint = Point; Matrix StoryMat = GetStoryMatrix(); StoryMat=StoryMat.Inverse(); StoryMat.transform(&UTPoint); // Check to see wether the point is within the untransformed bounds of the story if (InBounds) { DocRect UTStoryBounds = GetUTStoryBounds(); if (!UTStoryBounds.ContainsCoord(UTPoint)) { *pHitChar = NULL; *ToLeft = FALSE; return TRUE; } } // See if the point is above the first line INT32 HighestPointFound = INT32_MIN; INT32 LowestPointFound = INT32_MAX; TextLine* HighestLine = NULL; TextLine* LowestLine = NULL; // Scan through the lines to see if one contains the point INT32 PrevHigh = pFirstLine->GetPosInStory() + pFirstLine->GetLineAscent(); INT32 PrevLow = pFirstLine->GetPosInStory() + pFirstLine->GetLineDescent(); TextLine* pLine = pFirstLine; while (pLine != NULL) { INT32 HighestPoint = pLine->GetPosInStory() + pLine->GetLineAscent(); INT32 LowestPoint = pLine->GetPosInStory() + pLine->GetLineDescent(); // Update the highest and lowest known points in the story if (HighestPointFound < HighestPoint) { HighestPointFound = HighestPoint; HighestLine = pLine; } if (LowestPointFound > LowestPoint) { LowestPointFound = LowestPoint; LowestLine = pLine; } // Get the bounds of the current and previous lines INT32 Highest = max( PrevHigh, max( PrevLow, max( HighestPoint, LowestPoint ) ) ); INT32 Lowest = min( PrevHigh, min( PrevLow, min( HighestPoint, LowestPoint ) ) ); if ((UTPoint.y <= Highest) && (UTPoint.y >= Lowest)) break; PrevHigh = HighestPoint; PrevLow = LowestPoint; pLine = pLine->FindNextLine(); } // If pLine is NULL then the point is above/below the story if (pLine == NULL) { if (UTPoint.y >= HighestPointFound) pLine = HighestLine; if (UTPoint.y <= LowestPointFound) pLine = LowestLine; } ERROR2IF(pLine == NULL, FALSE, "Didn't find a line"); // Find a character on the line BOOL ToLeftPosChar = TRUE; VisibleTextNode* pPointerChar = pLine->FindCharAtDistAlongLine(UTPoint.x, &ToLeftPosChar); if (pPointerChar->IsAnEOLNode()) ToLeftPosChar = FALSE; if (pPointerChar->IsACaret()) { if (pPointerChar->FindNextVTNInLine()!=NULL) pPointerChar = pPointerChar->FindNextVTNInLine(); else pPointerChar = pPointerChar->FindPrevVTNInLine(); } *pHitChar = pPointerChar; *ToLeft = ToLeftPosChar; return TRUE; } /******************************************************************************************** > DocCoord TextStory::GetLeftIndentPos() Author: Peter_Arnold (Xara Group Ltd) Created: 14/3/96 Returns: The position of this stories path left indent Purpose: Gets the position in the document of the left indent of this story. ********************************************************************************************/ DocCoord TextStory::GetLeftIndentPos() const { ERROR3IF(GetTextPath() == NULL, "Story not on a path, LeftIndent unused"); if (GetTextPath() != NULL) { if (IsTextOnPathReversed()) { INT32 TotalPathLength = (INT32)GetTextPath()->InkPath.GetPathLength(); return GetPathBlobPos(TotalPathLength-GetLeftIndent(), GetTextPath()); } else return GetPathBlobPos(GetLeftIndent(), GetTextPath()); } else return DocCoord(0,0); } /******************************************************************************************** > DocCoord TextStory::GetRightIndentPos() Author: Peter_Arnold (Xara Group Ltd) Created: 16/8/96 Returns: The position of this stories right indent Purpose: Gets the position in the document of the right indent of this story. Remember that the right inrent is relative to the RHS of the path ********************************************************************************************/ DocCoord TextStory::GetRightIndentPos() const { ERROR3IF(GetTextPath() == NULL, "Story not on a path, RightIndent unused"); if (GetTextPath() != NULL) { if (IsTextOnPathReversed()) { return GetPathBlobPos(GetRightIndent(), GetTextPath()); } else { INT32 TotalPathLength = (INT32)GetTextPath()->InkPath.GetPathLength(); return GetPathBlobPos(TotalPathLength-GetRightIndent(), GetTextPath()); } } else return DocCoord(0,0); } /******************************************************************************************** > virtual BOOL TextStory::WritePreChildrenWeb(BaseCamelotFilter *pFilter) virtual BOOL TextStory::WritePreChildrenNative(BaseCamelotFilter *pFilter) Author: Andy_Hayward (Xara Group Ltd) Created: 10/07/96 Inputs: pFilter = ptr to the filter to write out to Returns: TRUE if successful, FALSE otherwise Purpose: Writes out a records associated with this node We currently call a helper class to do the work for us. Its been written this way to (a) keep all the text exporting code in one place (cxftext.h, cxftext.cpp), (b) to stop this file and its header from becoming bloated (reducing dependencies and build times) and (c) because I feel its neater (ie I want to). Ach ********************************************************************************************/ BOOL TextStory::WritePreChildrenWeb(BaseCamelotFilter *pFilter) { #ifdef DO_EXPORT return CXaraFileTxtStory::WritePreChildrenWeb(pFilter, this); #else return FALSE; #endif } BOOL TextStory::WritePreChildrenNative(BaseCamelotFilter *pFilter) { #ifdef DO_EXPORT return CXaraFileTxtStory::WritePreChildrenNative(pFilter, this); #else return FALSE; #endif } BOOL TextStory::CanWriteChildrenWeb(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtStory::CanWriteChildrenWeb(pFilter, this); #else return FALSE; #endif } BOOL TextStory::CanWriteChildrenNative(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtStory::CanWriteChildrenNative(pFilter, this); #else return FALSE; #endif } /******************************************************************************************** > BOOL TextStory::WriteBeginChildRecordsWeb(BaseCamelotFilter* pFilter); BOOL TextStory::WriteBeginChildRecordsNative(BaseCamelotFilter* pFilter); BOOL TextStory::WriteEndChildRecordsWeb(BaseCamelotFilter* pFilter); BOOL TextStory::WriteEndChildRecordsNative(BaseCamelotFilter* pFilter); Author: Mark_Neves (Xara Group Ltd) Created: 21/08/96 Inputs: pFilter = ptr to the filter to write out to Returns: TRUE if successful, FALSE otherwise Purpose: Writes out child records associated with the text story It uses the CXaraFileTxtStory class, because this is where all the rest of the code for text story output is. ********************************************************************************************/ BOOL TextStory::WriteBeginChildRecordsWeb(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtStory::WriteBeginChildRecordsWeb(pFilter, this); #else return FALSE; #endif } BOOL TextStory::WriteBeginChildRecordsNative(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtStory::WriteBeginChildRecordsNative(pFilter, this); #else return FALSE; #endif } BOOL TextStory::WriteEndChildRecordsWeb(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtStory::WriteEndChildRecordsWeb(pFilter, this); #else return FALSE; #endif } BOOL TextStory::WriteEndChildRecordsNative(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtStory::WriteEndChildRecordsNative(pFilter, this); #else return FALSE; #endif } /******************************************************************************************** > void TextStory::AddImportedString(ImportedString* pImportedString) Author: Mark_Neves (Xara Group Ltd) Created: 14/8/96 Inputs: pImportedString = ptr to the imported string object Outputs: - Returns: - Purpose: Adds the object to the list of imported strings ********************************************************************************************/ void TextStory::AddImportedString(ImportedString* pImportedString) { if (pImportedStringList == NULL) pImportedStringList = new ImportedStringList; if (pImportedStringList != NULL) pImportedStringList->AddTail(pImportedString); } /******************************************************************************************** > BOOL TextStory::ExpandImportedStrings() Author: Mark_Neves (Xara Group Ltd) Created: 14/8/96 Inputs: pImportedString = ptr to the imported string object Outputs: - Returns: - Purpose: Expands any strings that were imported into a series of text characters. When text strings are imported from the v2 file format, the first char in the string gets inserted into the tree, which marks the position of the begining of the string. The whole string is then saved (in an ImportedString object), along with the inserted text char node, until post-import. Post-import then inserts text chars for the rest of the string next to the first text char of the string, copying child attrs from the first char to each of the string's remaining chars, to ensure that they look correct. Why do this on post import? Why not do it when you get a string record from the file? Well, at the point that the string record is read, we don't know what child attrs will be applied to it, because, if there are any child attrs, they will appear later in the file. There are a number of ways to do it, but they all involve doing something to the string *after* you know that the child attrs have been read in. Post-import was considered a good place because the necessary processing can be done before the story is formatted, it doesn't assume anything about the order of records in the file format, and it was a Tuesday. ********************************************************************************************/ BOOL TextStory::ExpandImportedStrings() { // If the list is empty, get out of here, now! if (pImportedStringList == NULL) return TRUE; BOOL ok = TRUE; // Expand each string in the list ImportedString* pImportedString = pImportedStringList->GetHead(); while (ok && pImportedString != NULL) { // get the char node, and a copy of the original record TextChar* pChar = pImportedString->GetFirstChar(); CXaraFileRecord* pRecord = pImportedString->GetRecord(); if (pChar != NULL && pRecord != NULL) { WCHAR wChar; // The first char is read and discarded because pChar contains the char. ok = pRecord->ReadWCHAR(&wChar); // Find out the size of the record (and hence the num chars in the string) // Num chars = Size/2 (Unicode - double byte chars) UINT32 Size = pRecord->GetSize(); // We only iterate until Size <=2, because we have read and discarded the first // char in the string. for (;ok && Size > 2;Size-=2) { // Read the next char code ok = pRecord->ReadWCHAR(&wChar); TextChar* pNewChar = NULL; if (ok) pNewChar = new TextChar(); // Create a new TextChar if (ok) ok = (pNewChar != NULL); // if (ok) pNewChar->SetUnicodeValue(wChar); // Set the char's code if (ok) pNewChar->AttachNode(pChar,NEXT); // Attach it next to the last inserted char if (ok) ok = pChar->CopyChildAttrs(pNewChar); // Make sure it looks the same as the last char if (ok) pChar = pNewChar; // pNewChar now becomes the last char inserted } } // Get the next imported string pImportedString = pImportedStringList->GetNext(pImportedString); } // After expanding the strings, throw them away, and throw the list away too. pImportedStringList->DeleteAll(); delete pImportedStringList; pImportedStringList = NULL; return ok; } /******************************************************************************************** > void TextStory::Validate(BOOL EnsureOpFlagsReset=TRUE) Author: Ed_Cornes (Xara Group Ltd) Created: 16/6/96 Inputs: EnsureOpFlagsReset - Purpose: Perform various integrity checks on a text story in debug builds ********************************************************************************************/ #ifdef _DEBUG #define VALIDATE #define TRACEIF(a,b) { if (a) { TRACE b; } } #endif void TextStory::Validate(BOOL EnsureOpFlagsReset) { PORTNOTE("text","Removed 'Ed' validate from TextStory::Validate") #ifndef EXCLUDE_FROM_XARALX #ifdef VALIDATE if (IsUserName("Ed")) { // choose parent to check from Node* pNode = this; if (pNode==NULL) { ERROR3("TestStory::Validate() - could not find desired parent, using top parent"); pNode = this; } static INT32 count=0; if ((count++ % 12)==0) CheckSubtree(pNode); ERROR3IF(CharsScale !=FIXED16(1), "TextStory::Validate() - CharsScale!=1"); ERROR3IF(CharsAspect!=FIXED16(1), "TextStory::Validate() - CharsAspect!=1"); ERROR3IF(CharsRotation !=0, "TextStory::Validate() - CharsRoation!=0 - not yet supported"); ERROR3IF(ImportFormatWidth !=0, "TextStory::Validate() - ImportFormatWidth!=0"); ERROR3IF(!TextOnPathTangential, "TextStory::Validate() - TextOnPathTangential!=0 - not yet supported"); ERROR3IF(ImportBaseShift!=AlignBaseline,"TextStory::Validate() - ImportBaseShift!=AlignBaseline"); VisibleTextNode* pLastVTNInStory = this->FindLastVTN(); ERROR3IF(pLastVTNInStory==NULL,"TextStory::Validate() - story has no VTN!"); ERROR3IF(!pLastVTNInStory->IsAnEOLNode(),"TextStory::Validate() - last VTN in story is not an EOLNode"); FIXED16 Scale = 1; FIXED16 Aspect = 1; ANGLE Rotation = 0; ANGLE Shear = 0; DocCoord Translation(0,0); if (StoryMatrix.Decompose(&Scale,&Aspect,&Rotation,&Shear,&Translation)) { const FIXED16 NormaliseLimit = 0.001; if (Scale<0) { Scale = -Scale; Shear = -Shear; } FIXED16 AbsScaleError = Scale-1; FIXED16 AbsAspectError = Aspect-1; AbsScaleError = AbsScaleError<0 ? -AbsScaleError : AbsScaleError; AbsAspectError = AbsAspectError<0 ? -AbsAspectError : AbsAspectError; if (AbsScaleError>NormaliseLimit || AbsAspectError>NormaliseLimit) ERROR3("TextStory::Validate() - StroyMatrix scale or aspect not normalised!"); } else ERROR3("TextStory::Validate() - failed to decompose story matrix"); BOOL LineFound = FALSE; BOOL PathFound = FALSE; BOOL CaretFound = FALSE; Node* pNodeInStory = this->FindFirstChild(); while (pNodeInStory!=NULL) { if (EnsureOpFlagsReset && pNodeInStory->IsABaseTextClass()) { BaseTextClass* pBTC = (BaseTextClass*)pNodeInStory; ERROR3IF(pBTC->Affected(), "TextStory::Validate() - AffectedFlag Set!"); ERROR3IF(pBTC->DescendantAffected(), "TextStory::Validate() - DescendantAffectedFlag Set!"); ERROR3IF(pBTC->ModifiedByOp(), "TextStory::Validate() - ModifiedByOpFlag Set!"); ERROR3IF(pBTC->DescendantModifiedByOp(),"TextStory::Validate() - DescendantModifiedByOpFlag Set!"); } if (pNodeInStory->IsAnAttribute()) { ERROR3IF(LineFound,"TextStory::Validate() - Attribute found after first line in story"); ERROR3IF(PathFound,"TextStory::Validate() - Attribute found after path in story"); NodeAttribute* pAttr = (NodeAttribute*)pNodeInStory; if (pAttr->IsALineLevelAttrib() && !TextLine::IsAttrTypeLineLevel(pAttr->GetAttributeType())) ERROR3("TextStory::Validate() - TextLine::IsAttrTypeLineLevel() out of date?"); } else if (IS_A(pNodeInStory,NodePath)) { ERROR3IF(LineFound,"TextStory::Validate() - Path is not first object in story"); CheckLeaf(pNodeInStory); PathFound = TRUE; } else if (IS_A(pNodeInStory,TextLine)) { BOOL VTNFound = FALSE; BOOL EOLFound = FALSE; Node* pNodeInLine = pNodeInStory->FindFirstChild(); while (pNodeInLine!=NULL) { if (EnsureOpFlagsReset && pNodeInStory->IsABaseTextClass()) { BaseTextClass* pBTC = (BaseTextClass*)pNodeInStory; ERROR3IF(pBTC->Affected(), "TextStory::Validate() - AffectedFlag Set!"); ERROR3IF(pBTC->DescendantAffected(), "TextStory::Validate() - DescendantAffectedFlag Set!"); ERROR3IF(pBTC->ModifiedByOp(), "TextStory::Validate() - ModifiedByOpFlag Set!"); ERROR3IF(pBTC->DescendantModifiedByOp(),"TextStory::Validate() - DescendantModifiedByOpFlag Set!"); } if (pNodeInLine->IsAnAttribute()) { ERROR3IF(VTNFound,"TextStory::Validate() - Attribute found after first VTN in line"); NodeAttribute* pAttr = (NodeAttribute*)pNodeInLine; if (pAttr->IsALineLevelAttrib() && !TextLine::IsAttrTypeLineLevel(pAttr->GetAttributeType())) ERROR3("TextStory::Validate() - TextLine::IsAttrTypeLineLevel() out of date?"); } else if (IS_A(pNodeInLine,CaretNode)) { ERROR3IF(CaretFound,"TextStory::Validate() - story has more than 1 caret"); CheckLeaf(pNodeInLine); VTNFound = TRUE; CaretFound = TRUE; } else if (IS_A(pNodeInLine,EOLNode)) { ERROR3IF(EOLFound,"TextStory::Validate() - line has more than 1 EOL"); CheckLeaf(pNodeInLine); VTNFound = TRUE; EOLFound = TRUE; } else if (pNodeInLine->IsAVisibleTextNode()) { ERROR3IF(EOLFound,"TextStory::Validate() - VTN found after EOL on line"); CheckLeaf(pNodeInLine); VTNFound = TRUE; } else if (!pNodeInLine->IsNodeHidden()) ERROR3("TextStory::Validate() - unknown node in line (ie not a NodeAttribute, VisibleTextNode or NodeHidden)"); pNodeInLine = pNodeInLine->FindNext(); } LineFound = TRUE; } else if (!pNodeInStory->IsNodeHidden()) ERROR3("TextStory::Validate() - unknown node in story (ie not a NodeAttribute, NodePath, TextLine or NodeHidden)"); pNodeInStory = pNodeInStory->FindNext(); } ERROR3IF(!LineFound, "TextStory::Validate() - story has no lines!"); // ERROR3IF(!CaretFound,"TextStory::Validate() - story has no caret!"); TRACEIF(!CaretFound,("TextStory::Validate() - story has no caret!\n")); } #endif #endif } /******************************************************************************************** > void TextStory::CheckLeaf(Node* pLeafNode) Author: Ed_Cornes (Xara Group Ltd) Created: 17/6/96 Inputs: pLeafNode - Purpose: Ensure no Linelevel attrs below line level ********************************************************************************************/ void TextStory::CheckLeaf(Node* pLeafNode) { #ifdef VALIDATE if (pLeafNode==NULL) { ERROR3("TextStory::CheckLeaf() - pNode==NULL"); return; } Node* pNodeOnLeaf = pLeafNode->FindFirstChild(); while (pNodeOnLeaf!=NULL) { if (pNodeOnLeaf->IsAnAttribute()) { if ( ((NodeAttribute*)pNodeOnLeaf)->IsALineLevelAttrib() ) { if (IS_A(pLeafNode,CaretNode)) TRACE( _T("TextStory::CheckLeaf() - Line level attr applied to caret!\n")); else ERROR3("TextStory::CheckLeaf() - Line level attr applied below line level"); } } pNodeOnLeaf = pNodeOnLeaf->FindNext(); } #endif } /******************************************************************************************** > void TextStory::CheckSubtree(Node* pNode, AttrTypeSet* pParentAttrSet = NULL, BOOL* pDescendantSelected = NULL) Author: Ed_Cornes (Xara Group Ltd) Created: 17/6/96 Inputs: pNode - pParentAttrSet - Outputs: pDescendantSelected - Purpose: Validate a subtree, checking for: - duplication of attribute on node or parent (except caret) - nodes with child objects must be flagged as compound - attributes are not flagged as selected or parent of selected - hidden nodes are not flagged as selected, parent of selected or compound - node is not both flagged as selected and parent of selected - node with selected descendant is flagged as parent of selected - node with no selected descendant is not flagged as parent of selected - no sibling attributes ********************************************************************************************/ #include "nodedoc.h" void TextStory::CheckSubtree(Node* pNode, AttrTypeSet* pParentAttrSet, BOOL* pDescendantSelected) { #ifdef VALIDATE if (pNode==NULL) { ERROR3("CheckSubtree() - pNode==NULL"); return; } PORTNOTE("text","Removed AfxCheckMemory") #ifndef EXCLUDE_FROM_XARALX ERROR3IF(!AfxCheckMemory(),"CheckSubtree() - memory corrupt!"); #endif // get ptr to doc (if not an isolated subtree) as children are default attrs NodeDocument* pDoc = NULL; if (IS_A(pNode,NodeDocument)) pDoc = (NodeDocument*)pNode; else pDoc = (NodeDocument*)(pNode->FindParent(CC_RUNTIME_CLASS(NodeDocument))); // flag attr set not yet coied - only do so if we have to as it is SLOOOW AttrTypeSet* pAttrSet = NULL; BOOL ChildSelected = FALSE; BOOL ChildParentOfSel = FALSE; BOOL ChildObjectFound = FALSE; Node* pChildNode = pNode->FindFirstChild(); while (pChildNode) { TRACEIF( pChildNode->IsSelected() && pChildNode->IsParentOfSelected(), ( _T("CheckSubtree() - node (%s) both selected and parent of selected\n"), GetNodeInfo(pChildNode) ) ); ChildSelected |= pChildNode->IsSelected(); ChildParentOfSel |= pChildNode->IsParentOfSelected(); if (pChildNode->IsAnAttribute()) { // if not already done, if no Attr set passed in, create one else copy the given one if (pAttrSet==NULL) { if (pParentAttrSet==NULL) pAttrSet = new AttrTypeSet; else pAttrSet = pParentAttrSet->CopySet(); } TRACEIF( ChildObjectFound, ( _T("CheckSubtree() - attribute (%s) found after child object\n"), GetNodeInfo(pChildNode) ) ); NodeAttribute* pAttr = (NodeAttribute*)pChildNode; TRACEIF(pAttr->IsSelected(), ( _T("CheckSubtree() - attribute (%s) selected\n"), GetNodeInfo(pAttr) ) ); TRACEIF(pAttr->IsParentOfSelected(),( _T("CheckSubtree() - attribute (%s) parent of selected\n"), GetNodeInfo(pAttr) ) ); if (!IS_A(pNode,CaretNode)) { // if attrbute already in set it is a duplicate of one on a parent (exc defaults) CCRuntimeClass* pAttrType = pAttr->GetAttributeType(); TRACEIF( pAttrSet->InSet(pAttrType), ( _T("CheckSubtree() - attribute (%s) duplicate of one on self (%s) or parent\n"), GetNodeInfo(pAttr),GetNodeInfo(pNode))); // if not an isolated subtree and not default attr, check for duplicated default attrs if (pDoc!=NULL && pNode!=pDoc) { NodeAttribute* pDefaultAttr = (NodeAttribute*)(pDoc->FindFirstChild(pAttrType)); TRACEIF( pDefaultAttr==NULL, ( _T("CheckSubtree() - attribute (%s) found for which there is no default!\n"), GetNodeInfo(pAttr))); TRACEIF( (*pDefaultAttr)==(*pAttr), ( _T("CheckSubtree() - duplicate of default attr (%s) found!\n"), GetNodeInfo(pAttr))); } // if not default doc attrs, add to attr set if (pNode!=pDoc) if (pAttrSet->AddToSet(pAttrType)==FALSE) ERROR3("CheckSubtree() - AttrTypeSet::AddToSet() failed"); } } else if (pChildNode->IsNodeHidden()) { NodeHidden* pNodeHidden = (NodeHidden*)pChildNode; TRACEIF(pNodeHidden->IsSelected() ,( _T("CheckSubtree() - hidden node (%s) selected\n"), GetNodeInfo(pChildNode))); TRACEIF(pNodeHidden->IsParentOfSelected(),( _T("CheckSubtree() - hidden node (%s) parent of selected\n"), GetNodeInfo(pChildNode))); TRACEIF(pNodeHidden->IsCompound(), ( _T("CheckSubtree() - hidden node (%s) is compound\n"), GetNodeInfo(pChildNode))); } else { Node* pNode = pChildNode; BOOL DescendantSelected = FALSE; // if not already done, if no Attr set passed in, create one else copy the given one if (pAttrSet==NULL) { if (pParentAttrSet==NULL) pAttrSet = new AttrTypeSet; else pAttrSet = pParentAttrSet->CopySet(); } CheckSubtree(pNode, pAttrSet, &DescendantSelected); TRACEIF( DescendantSelected && !pChildNode->IsParentOfSelected(), ( _T("CheckSubtree() - descendant selected but node (%s) not parent of selected\n"), GetNodeInfo(pNode))); // TRACEIF(!DescendantSelected && pNode->IsParentOfSelected(),("CheckSubtree() - node (%s) parent of selected but no descendant selected\n",GetNodeInfo(pNode))); if (!DescendantSelected && pNode->IsParentOfSelected()) TRACE( _T("CheckSubtree() - node (%s) parent of selected but no descendant selected\n"),GetNodeInfo(pNode)); ChildObjectFound = TRUE; } pChildNode = pChildNode->FindNext(); } TRACEIF( ChildObjectFound && !pNode->IsCompound(), ( _T("CheckSubtree() - child object found on non-compound node (%s)\n"), GetNodeInfo(pNode))); if (pAttrSet!=NULL) { pAttrSet->DeleteAll(); delete pAttrSet; } if (pDescendantSelected!=NULL) *pDescendantSelected = ChildSelected || ChildParentOfSel; #endif } /******************************************************************************************** > TCHAR* TextStory::GetNodeInfo(Node* pNode) Author: Ed_Cornes (Xara Group Ltd) Created: 11/7/96 Inputs: pNode - Purpose: return string with CC_RUNTIME_CLASS name of node and address ********************************************************************************************/ TCHAR* TextStory::GetNodeInfo(Node* pNode) { static TCHAR pDesc[256]; if (pNode!=NULL) camSnprintf( pDesc, 256, _T("%s @ 0x%p\0"), pNode->GetRuntimeClass()->GetClassName(), pNode ); else camSnprintf( pDesc, 256, _T("\0") ); return pDesc; } /******************************************************************************************** > void TextStory::DeleteChildAttribute(NodeRenderableInk* pParent, CCRuntimeClass* pReqdAttrib) Author: Peter_Arnold (Xara Group Ltd) Created: 12/8/96 Inputs: pParent - Parent node of the attribute pReqdAttrib - Attribute type to delete Outputs: - Returns: TRUE/FALSE for success/failure Purpose: Finds and deletes a child attribute of the given type SeeAlso: TextStory::CreateFromChars. ********************************************************************************************/ BOOL TextStory::DeleteChildAttribute(NodeRenderableInk* pParent, CCRuntimeClass* pReqdAttrib) { ERROR2IF(pParent==NULL || pReqdAttrib==NULL, FALSE, "NULL parameter"); NodeAttribute* pAppliedAttr = pParent->GetChildAttrOfType(pReqdAttrib); ERROR3IF(pAppliedAttr==NULL, "Attribute not found"); if (pAppliedAttr != NULL) { pAppliedAttr->CascadeDelete(); delete pAppliedAttr; } return TRUE; } /******************************************************************************************** > BOOL TextStory::OKToExport() Author: Peter_Arnold (Xara Group Ltd) Created: 30/8/96 Inputs: - Outputs: - Returns: FALSE if this story should not be exported (ie it's duff). TRUE otherwise Purpose: Determines wether or not this text story should be exported. If there are no characters in it then we don't want to save it as it will cause bother when loaded back. ********************************************************************************************/ BOOL TextStory::OKToExport() { // See if we have any visible characters inside us. VisibleTextNode* pVTN = FindFirstVTN(); BOOL FoundCharacter = FALSE; while (pVTN!=NULL && !FoundCharacter) { if (pVTN->IsATextChar()) FoundCharacter = !pVTN->IsAVisibleSpace(); pVTN = pVTN->FindNextVTNInStory(); } if (FoundCharacter) return TRUE; // If we get here then we are an empty story. Is it safe to not export us? // Scan up the tree. If we reach a layer without passing though compounds other than groups it's fine. Node* pParent = FindParent(); while (pParent!=NULL && !pParent->IsLayer()) { if (pParent->IsCompound() && !IS_A(pParent, NodeGroup)) return TRUE; // Must export the story as the compound may rely on its presence. pParent = pParent->FindParent(); } // If we get here it's safe to not export this story. return FALSE; } /******************************************************************************************** > DocRect TextStory::GetUTStoryBounds() Author: Peter_Arnold (Xara Group Ltd) Created: 5/9/96 Inputs: - Outputs: - Returns: The untransformed bounds of the story Purpose: The bounding box of the story in it's untransformed space. ********************************************************************************************/ DocRect TextStory::GetUTStoryBounds() { // Get pointers to first and last lines in story TextLine* pFirstLine = FindFirstLine(); TextLine* pLastLine = FindLastLine(); ERROR3IF((pFirstLine==NULL || pLastLine==NULL), "Story has no text lines"); if (pFirstLine!=NULL && pLastLine!=NULL) { // Get the width of the widest line in the story MILLIPOINT WidestLineWidth = 0; TextLine* pLine = pFirstLine; while (pLine != NULL) { VisibleTextNode* pLastVTN = pLine->FindLastVTN(); if (pLastVTN!=NULL) { const MILLIPOINT Dist = pLastVTN->CalcCharDistAlongLine(TRUE); if (Dist > WidestLineWidth) WidestLineWidth = Dist; } pLine = (TextLine*)pLine->FindNext(CC_RUNTIME_CLASS(TextLine)); } INT32 LowY = pLastLine->GetPosInStory() + pLastLine->GetLineDescent(); INT32 HighY = -pFirstLine->GetLineAscent(); return DocRect( 0, // Err, this only works for left justified stories! (LowY < HighY) ? LowY : HighY, WidestLineWidth, (LowY < HighY) ? HighY : LowY); } else return DocRect(0,0,0,0); } /******************************************************************************************** > DocRect TextStory::ValidateExtend(const ExtendParams& ExtParams) Author: Karim_MacDonald (Xara Group Ltd) Created: 01/12/1999 Inputs: ExtParams parameters describing the extension. Returns: TRUE if this text story can be validly extended, FALSE otherwise. Purpose: Tests to see whether this text-story's extend-centre is positioned so as to make an extend operation irreversible. See also: Extender class. ********************************************************************************************/ DocRect TextStory::ValidateExtend(const ExtendParams& ExtParams) { // if we lie on a curve, we must test that curve and our attributes, // otherwise we must test ourself, then pass the test on to our children. DocRect drMinExtend(INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX); NodePath* pnPath = GetTextPath(); if (pnPath == NULL) { DocCoord doccArray[1] = { FindExtendCentre() }; drMinExtend = Extender::ValidateControlPoints(1, doccArray, ExtParams); // if we didn't invalidate the extension, we must call the base class // implementation, which will validate our children. if (drMinExtend.lo.x == INT32_MAX && drMinExtend.lo.y == INT32_MAX && drMinExtend.hi.x == INT32_MAX && drMinExtend.hi.y == INT32_MAX) drMinExtend = Node::ValidateExtend(ExtParams); } else { drMinExtend = pnPath->ValidateExtend(ExtParams); DocRect drThisMinExtend; for ( Node* pChildNode = FindFirstChild(); pChildNode != NULL; pChildNode = pChildNode->FindNext() ) { if (!pChildNode->IsAnAttribute()) continue; drThisMinExtend = pChildNode->ValidateExtend(ExtParams); if (drMinExtend.lo.x > drThisMinExtend.lo.x) drMinExtend.lo.x = drThisMinExtend.lo.x; if (drMinExtend.lo.y > drThisMinExtend.lo.y) drMinExtend.lo.y = drThisMinExtend.lo.y; if (drMinExtend.hi.x > drThisMinExtend.hi.x) drMinExtend.hi.x = drThisMinExtend.hi.x; if (drMinExtend.hi.y > drThisMinExtend.hi.y) drMinExtend.hi.y = drThisMinExtend.hi.y; } } return drMinExtend; } /******************************************************************************************** > void TextStory::Extend(const ExtendParams& ExtParams) Author: Karim_MacDonald (Xara Group Ltd) Created: 01/12/1999 Inputs: ExtParams parameters describing the extension. Purpose: Perform an Extend operation on this text-story. See also: Extender class. ********************************************************************************************/ void TextStory::Extend(const ExtendParams& ExtParams) { // if we lie on a curve, we must extend that curve, together with our attributes, // otherwise we must extend ourself, then extend our children. NodePath* pnPath = GetTextPath(); if (pnPath == NULL) { // do the extension operations on ourself. // text stories never stretch, they just translate. TransformTranslateNoStretchObject(ExtParams); TransformTranslateObject(ExtParams); // do the base-class implementation to extend our children. Node::Extend(ExtParams); } else { // extend our path. pnPath->Extend(ExtParams); // extend our attributes. for ( Node* pChildNode = FindFirstChild(); pChildNode != NULL; pChildNode = pChildNode->FindNext() ) { if (pChildNode->IsAnAttribute()) pChildNode->Extend(ExtParams); } } } /******************************************************************************************** > DocCoord TextStory::FindExtendCentre() Author: Karim_MacDonald (Xara Group Ltd) Created: 01/12/1999 Returns: The centre-point which the extender routines use to determine whether an extension is valid, and in which detection it should occur. Purpose: Find the extend-centre reference point. See also: Extender class. ********************************************************************************************/ DocCoord TextStory::FindExtendCentre() { DocRect rectNode = GetBoundingRect(); DocCoord doccCentre = rectNode.Centre(); switch ( FindJustification() ) { case JCENTRE: case JFULL: // do nothing - the centre of the rectangle is required. break; case JRIGHT: doccCentre.x = rectNode.hi.x; break; case JLEFT: default: doccCentre.x = rectNode.lo.x; break; } return doccCentre; } /******************************************************************************************** > Justification TextStory::FindJustification() Author: Karim_MacDonald (Xara Group Ltd) Created: 01/12/1999 Returns: The Justification of this text-story. Purpose: Used in conjunction with Extend() and its sister functions to determine which way a text-story should extend. If you use this function for another purpose, read it and make sure it does what you think it should do first. Notes: Be aware that this function uses IsKindOf() to test this node's children within the tree. See also: Extender class. ********************************************************************************************/ Justification TextStory::FindJustification() { // first, search for a text-justify attribute underneath this TextStory in the tree. Node* pNode = FindFirstDepthFirst(); while (pNode != NULL && !pNode->IsKindOf(CC_RUNTIME_CLASS(AttrTxtJustification))) pNode = pNode->FindNextDepthFirst(this); // if we didn't find one, then find the attribute currently applied to pTextStory. BOOL bFoundAttr = FALSE; AttrTxtJustification* pAttrJustify = NULL; if (pNode != NULL) { pAttrJustify = (AttrTxtJustification*)pNode; bFoundAttr = TRUE; } else bFoundAttr = FindAppliedAttribute(CC_RUNTIME_CLASS(AttrTxtJustification), (NodeAttribute**)&pAttrJustify); // if we retrieved an attribute, get its value. // if we have no luck, we'll just use JLEFT as the default. Justification justify = JLEFT; if (bFoundAttr) { TxtJustificationAttribute* textJustAttr = (TxtJustificationAttribute*)pAttrJustify->GetAttributeValue(); if (textJustAttr != NULL) justify = textJustAttr->justification; } return justify; } String_256 TextStory::GetStoryAsString() { String_256 Content = ""; String_256 AddTemplate = "x"; Node * pTextLine = FindFirstChild(CC_RUNTIME_CLASS(TextLine)); INT32 StringMaxSize = 255; while (pTextLine && StringMaxSize > 0) { Node * pChar = pTextLine->FindFirstChild(CC_RUNTIME_CLASS(TextChar)); while (pChar && StringMaxSize > 0) { *AddTemplate = (TCHAR) (((TextChar *)pChar)->GetUnicodeValue()); Content += AddTemplate; StringMaxSize--; pChar = pChar->FindNext(CC_RUNTIME_CLASS(TextChar)); } pTextLine = pTextLine->FindNext(CC_RUNTIME_CLASS(TextLine)); if (pTextLine) { Content += _T("\n"); StringMaxSize -=2; } } return Content; } /******************************************************************************************** > BOOL TextStory::IsGradientFilled() Author: Chris_Gallimore (Xara Group Ltd) Created: 9/11/00 Returns: Whether the text story is gradient filled (true), or not (false) Purpose: Looks through the list of nodes under the text story node to see if there are any gradient fills on this object. . See also: TextStory::PreExportRender ********************************************************************************************/ BOOL TextStory::IsGradientFilled () { ListItem *pItem = NULL; List GradientList; BOOL filled = FALSE; // Now get the list of nodes. BevelTools::GetAllNodesUnderNode ( this, &GradientList, CC_RUNTIME_CLASS ( AttrFillGeometry ) ); // (ChrisG 8/11/00) - Cycle through the gradient list finding if there are any // gradient fills. pItem = GradientList.GetHead (); while ( (pItem != NULL) && (filled == FALSE) ) { NodeListItem *pNodeItem = ( NodeListItem* ) pItem; AttrFillGeometry *pFill = ( AttrFillGeometry* ) pNodeItem->pNode; // if it is a graduated colour fill, then it is gradient filled if ( pFill->IsAColourFill () && // Is it a colour fill? pFill->IsAGradFill ()) // Is it a graduated fill? { filled = TRUE; } // Get the next list item. pItem = GradientList.GetNext ( pItem ); } GradientList.DeleteAll(); return filled; }