// $Id: nodetxtl.cpp 1535 2006-07-25 16:50:32Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ /* */ #include "camtypes.h" #include "nodetxtl.h" //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "blobs.h" //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "mario.h" //#include "nodeattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodepath.h" #include "nodetext.h" #include "nodetxts.h" //#include "simon.h" //#include "textinfo.h" //#include "matrix.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "cameleps.h" #include "pathproc.h" #include "fontman.h" #include "attrmap.h" #include "cxftext.h" DECLARE_SOURCE("$Revision: 1535 $") CC_IMPLEMENT_MEMDUMP(TextLineInfo, CC_CLASS_MEMDUMP) CC_IMPLEMENT_DYNAMIC(TextLine, BaseTextClass) CC_IMPLEMENT_DYNAMIC(FormatRegion, RenderRegion) // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW ///////////////////////////////////////////////////////////////////////////////////////////// // TextLineInfo Methods /******************************************************************************************** > TextLineInfo::TextLineInfo() Author: Ed_Cornes (Xara Group Ltd) Created: 4/2/96 Purpose: default constructor ********************************************************************************************/ TextLineInfo::TextLineInfo() { SumCharAdvances = 0; justification = JLEFT; LeftMargin = 0; RightMargin = 0; ParaLeftMargin = 0; ParaRightMargin = 0; Ruler = NULL; WordWrapping = FALSE; NumChars = 0; NumSpaces = 0; } ///////////////////////////////////////////////////////////////////////////////////////////// // TextLine Methods /******************************************************************************************** > void TextLine::Init() Author: Ed_Cornes (Xara Group Ltd) Created: 3/2/96 Purpose: common init function for constructors ********************************************************************************************/ void TextLine::Init() { mLineAscent = 0; mLineDescent = 0; mLineSize = 0; mJustification = JLEFT; mLineSpacing = 0; mLineSpaceRatio = 1; mpRuler = new TxtRuler; mPosInStory = 0; } /******************************************************************************************** > TextLine::TextLine() Author: Simon_Maneggio (Xara Group Ltd) Created: 21/12/94 Purpose: Simple TextLine constructor, it is required so that SimpleCopy will work. ********************************************************************************************/ TextLine::TextLine(): BaseTextClass() // Call the base class { Init(); } /******************************************************************************************** > TextLine::~TextLine() Author: Martin Wuerthner Created: 20/07/06 Purpose: Destructor ********************************************************************************************/ TextLine::~TextLine() { delete mpRuler; } /******************************************************************************************** > TextLine::TextLine(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 TextLine constructor ********************************************************************************************/ TextLine::TextLine(Node* ContextNode, AttachNodeDirection Direction):BaseTextClass(ContextNode, Direction) { Init(); } /******************************************************************************************** > virtual CopyType TextLine::GetCopyType() Author: Mike_Kenny (Xara Group Ltd) Created: 3/5/95 Returns: A copy type describing how to copy this object Purpose: This function returns a type describing how this object is to be copied. The fuction is called from the low level copy operation CopyObjects. There are two options at present, these being SIMPLECOPY and COMPLEXCOPY. SIMPLECOPY indicates that the node can be copied by a call to its virtual function SimpleCopy(). COMPLEXCOPY however indicates that the node needs to do its own thing when copying and must be called via the ComplexCopy() virtual function. This virtual will likely return a tree of copied objects rather than just a copy of itself. ********************************************************************************************/ CopyType TextLine::GetCopyType() { return COMPLEXCOPY; } /******************************************************************************************** > Node* TextLine::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* TextLine::SimpleCopy() { // Make a new TextLine and then copy things into it TextLine* NodeCopy = new TextLine(); ERROR1IF(NodeCopy == NULL, NULL, _R(IDE_NOMORE_MEMORY)); if (NodeCopy) CopyNodeContents(NodeCopy); return NodeCopy; } /******************************************************************************************** > virtual INT32 TextLine::ComplexCopy(CopyStage Stage, Range& RangeToCopy, Node** pOutput) Author: Mike_Kenny (Xara Group Ltd) Created: 3/5/95 Inputs: Stage - COPYOBJECT if we should make a copy - COPYFINISHED once the entire copy operation has completed RangeToCopy - Describes the range which is currently being copied. pOutput - a pointer to a pointer to a node. Set this pointer to point at the tree copy Returns: -1 = The routine failed to make a copy. 0 = No copy has been made. +1 = pOutput points to the copy. Purpose: If the copystage is COPYOBJECT, The node has been called to copy itself and do what ever it needs to to make a sensible copy of other items such as attributes. The caller (CopyObjects) will not deep copy this node (as this is a complex copy and it expects the handler to know what its doing). In this case the TextLine object cannot exist on its own. It needs a TextStory as a parent and however many text characters that live inside it. Hence this is what is returned. SeeAlso Node::ComplexCopy(), CopyObjects() ********************************************************************************************/ INT32 TextLine::ComplexCopy(CopyStage Stage, Range& RangeToCopy, Node** pOutput) { TextStory* pTextStory = FindParentStory(); ERROR2IF(pTextStory==NULL,FALSE,"TextLine::ComplexCopy() - pTextStory==NULL"); return pTextStory->BaseComplexCopy(Stage, RangeToCopy, pOutput); } /******************************************************************************************** > virtual INT32 TextLine::ComplexHide(UndoableOperation* pOp, Node* pNextNode) Author: Mike_Kenny (Xara Group Ltd) Created: 3/5/95 Inputs: pOp - a pointer to an undoable operation pNextNode - a pointer to the next node in range. Returns: -1 = The routine failed to hide this node. 0 = Ignored, this object does not support complex hide operations, so carry on and hide the node as normal. +1 = The node and possibly various others have been hidden correctly. Purpose: Override the node level virtual function ComplexHide. This gives us a chance to get in and hide the various selected members of a text story sensibly. We hide all necessary nodes when the last member of the text story is called to complex hide itself, otherwise we may corrupt the range being scanned ********************************************************************************************/ INT32 TextLine::ComplexHide(UndoableOperation* pOp, Node* pNextNode) { // if there is no next node in the range then we need to hide all nodes BOOL CallComplexHide = TRUE; TextStory* pThisStory = FindParentStory(); ERROR2IF(pThisStory==NULL,FALSE,"VisibleTextNode::ComplexHide() - pThisStory==NULL"); if (pNextNode) { if ( (pNextNode->IsAVisibleTextNode()) || (IS_A(pNextNode,TextLine)) ) { TextStory* pTextStory = FindParentStory(); ERROR2IF(pTextStory==NULL,FALSE,"VisibleTextNode::ComplexHide() - pTextStory==NULL"); if (pThisStory==pTextStory) CallComplexHide=FALSE; } } if (CallComplexHide) return pThisStory->BaseComplexHide(pOp); else return 1; } /*********************************************************************************************** > void TextLine::CopyNodeContents(TextLine* 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 Scope: protected ***********************************************************************************************/ void TextLine::CopyNodeContents(TextLine* NodeCopy) { // Ask the base class to do its bit NodeRenderableBounded::CopyNodeContents(NodeCopy); // Copy specifics NodeCopy->mLineAscent = mLineAscent; NodeCopy->mLineDescent = mLineDescent; NodeCopy->mLineSize = mLineSize; NodeCopy->mJustification = mJustification; NodeCopy->mLineSpacing = mLineSpacing; NodeCopy->mLineSpaceRatio = mLineSpaceRatio; NodeCopy->mLeftMargin = mLeftMargin; NodeCopy->mFirstIndent = mFirstIndent; NodeCopy->mRightMargin = mRightMargin; *NodeCopy->mpRuler = *mpRuler; NodeCopy->mPosInStory = mPosInStory; } /*********************************************************************************************** > void TextLine::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 TextLine::PolyCopyNodeContents(NodeRenderable* pNodeCopy) { ENSURE(pNodeCopy, "Trying to copy a node's contents into a NULL node"); ENSURE(IS_A(pNodeCopy, TextLine), "PolyCopyNodeContents given wrong dest node type"); if (IS_A(pNodeCopy, TextLine)) CopyNodeContents((TextLine*)pNodeCopy); } /******************************************************************************************** > BOOL TextLine::CreateNodeGroup(NodeGroup** ppNodeGroup, FormatRegion* pFormatRegion, BecomeA* pBecomeA) Author: Ed_Cornes (Xara Group Ltd) Created: 30/3/95 Inputs: pFormatRegion - format region with current attr stack pBecomeA - ptr to class containing info needed to become a new type of node Outputs: ppNodeGroup - pointer to new node group (if not passing back) Returns: FALSE if fails Purpose: Creates a NodeGroup containing all chars converted to NodePaths ********************************************************************************************/ BOOL TextLine::CreateNodeGroup(NodeGroup** ppNodeGroup, FormatRegion* pFormatRegion, BecomeA* pBecomeA) { #ifndef DISABLE_TEXT_RENDERING ERROR2IF(pFormatRegion==NULL,FALSE,"TextChar::CreateNodeGroup() - pFormatRegion==NULL"); ERROR2IF( pBecomeA==NULL,FALSE,"TextChar::CreateNodeGroup() - pBecomeA==NULL"); ERROR2IF( ppNodeGroup==NULL,FALSE,"TextChar::CreateNodeGroup() - ppNodeGroup==NULL"); pFormatRegion->SaveContext(); // here to overcome scope problem Node* pNode=NULL; TextChar* pTextChar=NULL; // if not passing back, create a NodeGroup to encompass the line, and copy non-text line attributes // BODGE - should use ALLOC_WITH_FAIL NodeGroup* pLineNodeGroup=NULL; if (pBecomeA->GetReason()!=BECOMEA_PASSBACK) { pLineNodeGroup=new NodeGroup; if (pLineNodeGroup==NULL) goto Fail; } pNode=FindFirstChild(); while (pNode!=NULL) { if (pNode->IsAnAttribute()) { pNode->Render(pFormatRegion); // render attributes if (pBecomeA->GetReason()!=BECOMEA_PASSBACK) if (!pNode->IsKindOfTextAttribute()) if (pNode->CopyNode(pLineNodeGroup, LASTCHILD)==FALSE) goto Fail; } pNode=pNode->FindNext(); } // For each child TextChar convert it to a NodePath // then EITHER pass it back OR attach it to the line group with non-text char attributes pTextChar=(TextChar*)FindFirstChild(CC_RUNTIME_CLASS(TextChar)); while (pTextChar) { pFormatRegion->SaveContext(); pTextChar->RenderChildAttrs(pFormatRegion); NodePath* pCharNodePath=NULL; if (pTextChar->CreateNodePath(&pCharNodePath,pFormatRegion)==FALSE) goto Fail; if (pCharNodePath) { if (pBecomeA->GetReason()==BECOMEA_PASSBACK) { if (pBecomeA->PassBack(pCharNodePath,pTextChar)==FALSE) goto Fail; } else { pCharNodePath->AttachNode(pLineNodeGroup,LASTCHILD); Node* pNode=pTextChar->FindFirstChild(); while (pNode) { if (pNode->IsAnAttribute() && !pNode->IsKindOfTextAttribute()) if (pNode->CopyNode(pCharNodePath, LASTCHILD)==FALSE) goto Fail; pNode=pNode->FindNext(); } pBecomeA->PassBack(pCharNodePath, pTextChar); } } pFormatRegion->RestoreContext(); pTextChar=(TextChar*)(pTextChar->FindNext(CC_RUNTIME_CLASS(TextChar))); } *ppNodeGroup=pLineNodeGroup; pFormatRegion->RestoreContext(); return TRUE; Fail: if (pLineNodeGroup) { pLineNodeGroup->CascadeDelete(); delete pLineNodeGroup; } pFormatRegion->RestoreContext(); #endif return FALSE; } /******************************************************************************************** > static TextLine* TextLine::CreateEmptyTextLine(Node* pContextNode=NULL, AttachNodeDirection Direction=FIRSTCHILD) Author: Ed_Cornes (Xara Group Ltd) Created: 28/5/95 Inputs: pContextNode - node to attach it to (or NULL if not to be attached) Direction - direction in which to attach Returns: pointer to new TextLine (or NULL if fails) Purpose: Create a TextLine with EOL and attach it to another node if required ********************************************************************************************/ TextLine* TextLine::CreateEmptyTextLine(Node* pContextNode, AttachNodeDirection Direction) { TextLine* pTextLine = new TextLine(); if (pTextLine==NULL) return NULL; EOLNode* pEOL = new EOLNode(pTextLine,FIRSTCHILD); if (pEOL==NULL) { pTextLine->CascadeDelete(); delete pTextLine; return NULL; } if (pContextNode!=NULL) pTextLine->AttachNode(pContextNode,Direction); return pTextLine; } /******************************************************************************************** > String TextLine::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 TextLine node for the status line etc ********************************************************************************************/ String TextLine::Describe(BOOL Plural, BOOL Verbose) { if (Plural) return(String(_R(IDS_DESCRIBE_TEXTLINEP))); else return(String(_R(IDS_DESCRIBE_TEXTLINES))); } /******************************************************************************************* void TextLine::GetBlobBoundingRect() Author: Peter_Arnold (Xara Group Ltd) Created: 10/4/95 Returns: The rectangle of the document affected by the blobs on this TextLine Purpose: This function returns the blob bounding rectangle of this TextLine. There is a tiny blob on the top left of the first character on the line. ********************************************************************************************/ DocRect TextLine::GetBlobBoundingRect() { #if !defined(EXCLUDE_FROM_RALPH) DocCoord BlobPos=GetTinyBlobPos(); DocRect BlobBounds; BlobManager* pBlobMgr=GetApplication()->GetBlobManager(); if (pBlobMgr) pBlobMgr->GetBlobRect(BlobPos,&BlobBounds); else ERROR3("TextLine::GetBlobBoundingRect() - Couldn't find the BlobManager"); IncludeChildrensBoundingRects(&BlobBounds); return BlobBounds; #else return DocRect(0,0,0,0); #endif } /******************************************************************************************* DocCoord TextLine::GetTinyBlobPos() Author: Peter_Arnold (Xara Group Ltd) Created: 10/4/95 Returns: The position of the tiny bob on this TextLine Purpose: This function returns the location of the tiny blob on this TextLine. It is either on the top-left of the first character on the line, or if there isn't a char, on the top-left of the line bounding rect. ********************************************************************************************/ DocCoord TextLine::GetTinyBlobPos() { #if !defined(EXCLUDE_FROM_RALPH) // set a fall back position - the top left of the line bounds DocRect TempRect=GetBoundingRect(); DocCoord BlobPos=DocCoord(TempRect.lo.x,TempRect.hi.y); // but really we want the top left of the first char's metrics rect AbstractTextChar* pFirstChar=(AbstractTextChar*)FindFirstChild(CC_RUNTIME_CLASS(AbstractTextChar)); if (pFirstChar) { if (pFirstChar->GetMetricsRect(&TempRect)) { BlobPos=DocCoord(TempRect.lo.x,TempRect.hi.y); Matrix matrix; if (pFirstChar->GetStoryAndCharMatrix(&matrix)) matrix.transform(&BlobPos); else ERROR3("TextLine::GetTinyBlobPos() - GetStoryAndCharMatrix() failed"); } else ERROR3("TextLine::GetTinyBlobPos() - GetMetricsRect() failed"); } return BlobPos; #else return DocCoord(0,0); #endif } /******************************************************************************************* void TextLine::RenderTinyBlobs(RenderRegion* pRRegion) Author: Peter_Arnold (Xara Group Ltd) Created: 10/4/95 Inputs: pRegion - points to the RenderRegion to render into Purpose: Renders the TextLines tiny blobs ********************************************************************************************/ void TextLine::RenderTinyBlobs(RenderRegion* pRRegion) { #if !defined(EXCLUDE_FROM_RALPH) pRRegion->SetLineColour(COLOUR_NONE); pRRegion->SetFillColour(COLOUR_UNSELECTEDBLOB); pRRegion->DrawBlob(GetTinyBlobPos(), BT_UNSELECTED); #endif } /******************************************************************************************* void TextLine::RenderObjectBlobs(RenderRegion* pRRegion) Author: Peter_Arnold (Xara Group Ltd) Created: 10/4/95 Inputs: pRegion - points to the RenderRegion to render into Purpose: Renders the TextLines object blobs ********************************************************************************************/ void TextLine::RenderObjectBlobs(RenderRegion* pRRegion) { #if !defined(EXCLUDE_FROM_RALPH) RenderTinyBlobs(pRRegion); #endif } /******************************************************************************************** > UINT32 TextLine::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 SeeAlso: Node::GetSubtreeSize ********************************************************************************************/ UINT32 TextLine::GetNodeSize() const { return (sizeof(TextLine)); } /******************************************************************************************** > void TextLine::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 TextLine::GetDebugDetails(StringBase* Str) { NodeRenderableBounded::GetDebugDetails(Str); } /******************************************************************************************** > virtual BOOL TextLine::ReCacheMetrics(FormatRegion* pFormatRegion) Author: Ed_Cornes (Xara Group Ltd) Created: 2/2/96 Inputs: pFormatRegion - Returns: FALSE if fails Purpose: Recache metrics in text line ********************************************************************************************/ BOOL TextLine::ReCacheMetrics(FormatRegion* pFormatRegion) { TRACEUSER("wuerthne", _T("TextLine::ReCacheMetrics")); SetJustification( pFormatRegion->GetJustification()); SetLineSpacing( pFormatRegion->GetLineSpacing()); SetLineSpaceRatio(pFormatRegion->GetLineSpaceRatio()); SetParaLeftMargin(pFormatRegion->GetLeftMargin()); SetParaFirstIndent(pFormatRegion->GetFirstIndent()); SetParaRightMargin(pFormatRegion->GetRightMargin()); SetRuler(pFormatRegion->GetRuler()); #if defined(_DEBUG) && 0 String_256 TempStr; String Str(_T(" ")); for (TxtTabStopIterator It = mpRuler->begin(); It != mpRuler->end(); ++It) { switch((*It).GetType()) { case LeftTab: TempStr._MakeMsg( TEXT("L(#1%ld)"), (*It).GetPosition()); Str += TempStr; break; case RightTab: TempStr._MakeMsg( TEXT("R(#1%ld)"), (*It).GetPosition()); Str += TempStr; break; case CentreTab: TempStr._MakeMsg( TEXT("C(#1%ld)"), (*It).GetPosition()); Str += TempStr; break; case DecimalTab: TempStr._MakeMsg( TEXT("D(#1%ld)"), (*It).GetPosition()); Str += TempStr; break; } } TRACEUSER("wuerthne", _T("ruler at %08x:%s"), mpRuler, (TCHAR*)Str); #endif return TRUE; } /******************************************************************************************** > MILLIPOINT TextLine::GetEffectiveLeftMargin() Author: Martin Wuerthner Created: 18/07/06 Inputs: - Returns: the left margin value to use Purpose: Select the left margin or first indent value depending on whether this is the first line in the paragraph ********************************************************************************************/ MILLIPOINT TextLine::GetEffectiveLeftMargin() { // we need to find out whether this is the first line in the paragraph, so we know // whether we need to use FirstLineIndent or LeftMargin TextLine* pPrevLine = FindPrevLine(); if (pPrevLine==NULL || pPrevLine->FindEOLNode() != NULL) { // first line in paragraph return mFirstIndent; } else { return mLeftMargin; } } /******************************************************************************************** > BOOL TextLine::Format(TextStoryInfo* pStoryInfo) Author: Ed_Cornes (Xara Group Ltd) Created: 24/3/95 Inputs: pStoryInfo - current story level info Returns: FALSE if fails Purpose: perform line level formatting Note: on entry pStoryInfo->DescentLine hold the DescentLine of the last line (pStoryInfo->DescentLineValid indicating if there was none (eg first line) and on exit, it holds the DescentLine for this line and DescentLineValid is TRUE ********************************************************************************************/ BOOL TextLine::Format(TextStoryInfo* pStoryInfo) { ERROR2IF(pStoryInfo==NULL,FALSE,"TextLine::Format() - pStoryInfo == NULL"); TRACEUSER("wuerthne", _T("TextLine::Format")); MILLIPOINT PhysicalLeftMargin = pStoryInfo->LeftPathIndent; // 0 if not on path MILLIPOINT PhysicalRightMargin = pStoryInfo->StoryWidth - pStoryInfo->RightPathIndent; BOOL WordWrapping = pStoryInfo->WordWrapping; MILLIPOINT RightMargin = PhysicalRightMargin - mRightMargin; MILLIPOINT LeftIndent = GetEffectiveLeftMargin(); // if word wrapping, and not text at a point, and undoably 'do'ing op, word wrap the line MILLIPOINT WrapWidth = 0; if (WordWrapping) WrapWidth = RightMargin - PhysicalLeftMargin; // will be 0 if text at a point if (WrapWidth!=0 && pStoryInfo->WordWrap) { if (!this->Wrap(pStoryInfo->pUndoOp, WrapWidth, LeftIndent)) return FALSE; } else if (WrapWidth != 0) { // when called during undo (i.e., WordWrap = FALSE) and the story is word wrapping, // we do not want to wrap, but we still need to make sure that each tab gets its width // set correctly, otherwise PositionCharsInLine will not do the right thing FindBreakChar(WrapWidth, FALSE, LeftIndent); } // if line affected in any way, reposition chars in line if (NodeOrDescendantAffected()) { TextLineInfo LineInfo; if (ReCalcLineInfo(&LineInfo)==FALSE) return FALSE; LineInfo.LeftMargin = PhysicalLeftMargin; LineInfo.RightMargin = PhysicalRightMargin; LineInfo.WordWrapping = WordWrapping; if (PositionCharsInLine(&LineInfo)==FALSE) return FALSE; } // calculate this line's BaseLine, and keep the DescentLine up to date MILLIPOINT BaseLine = 0; MILLIPOINT DescentLine = 0; if (CalcBaseAndDescentLine(&BaseLine,&DescentLine,pStoryInfo->DescentLine,pStoryInfo->DescentLineValid)==FALSE) return FALSE; pStoryInfo->DescentLine = DescentLine; pStoryInfo->DescentLineValid = TRUE; // if the line has moved, set its' new position and flag it is 'affected' if (BaseLine!=GetPosInStory()) { SetPosInStory(BaseLine); FlagNodeAndDescendantsAffectedAndParentsHaveDescendantAffected(); } // if line or descendent 'Affected' recalc matrices for affected children BOOL ok=TRUE; if (NodeOrDescendantAffected()) { if (pStoryInfo->pPath==NULL) ok = SetCharMatrices(GetPosInStory()); else ok = FitTextToPath(pStoryInfo,GetPosInStory()); } return ok; } /******************************************************************************************** > BOOL TextLine::EnsureNextLineOfParagraphHasSameLineLevelAttrs(UndoableOperation* pUndoOp) Author: Ed_Cornes (Xara Group Ltd) Created: 16/7/96 Inputs: pUndoOp - Returns: FALSE if fails Purpose: Ensure the next line of a paragraph has the same line level attibutes ********************************************************************************************/ BOOL TextLine::EnsureNextLineOfParagraphHasSameLineLevelAttrs(UndoableOperation* pUndoOp) { TRACEUSER("wuerthne", _T("EnsureNextLineOfParagraphHasSameLineLevelAttrs")); ERROR2IF(FindEOLNode()!=NULL,FALSE,"TextLine::EnsureNextOfParagraphLineHasSameLineLevelAttrs() - there is no next line in the paragraph!"); TextStory* pStory = this->FindParentStory(); ERROR2IF(pStory==NULL,FALSE,"TextLine::EnsureNextOfParagraphLineHasSameLineLevelAttrs() - line has no parent story!"); TextLine* pNextLine = this->FindNextLine(); ERROR2IF(pNextLine==NULL,FALSE,"TextLine::EnsureNextOfParagraphLineHasSameLineLevelAttrs() - last line of story has no EOL!"); // flag story not localised, and get the set of line level attrs to localise BOOL StoryLocalised = FALSE; AttrTypeSet LLASet; BOOL ok = this->AddChildLineLevelAttrsToSet(&LLASet); if (ok) ok = pNextLine->AddChildLineLevelAttrsToSet(&LLASet); if (!ok) return FALSE; // hide any Line Level Attributes found on the next line // which are different to those on this line or do not exist on this line Node* pNextLineNode = pNextLine->FindFirstChild(); while (pNextLineNode!=NULL) { if (pNextLineNode->IsAnAttribute() && ((NodeAttribute*)pNextLineNode)->IsALineLevelAttrib()) { NodeAttribute* pNextLineLLA = (NodeAttribute*)pNextLineNode; NodeAttribute* pThisLineLLA = (NodeAttribute*)(this->FindFirstChild(pNextLineLLA->GetAttributeType())); if (pThisLineLLA==NULL || !((*pNextLineLLA)==(*pThisLineLLA)) ) { if (!StoryLocalised) if (!pStory->DoLocaliseCommonAttributes(pUndoOp,FALSE,TRUE,&LLASet)) return FALSE; StoryLocalised = TRUE; if (pUndoOp!=NULL) { if (!pUndoOp->DoHideNode(pNextLineLLA,FALSE)) return FALSE; } else { pNextLineLLA->CascadeDelete(); delete pNextLineLLA; } } } pNextLineNode = pNextLineNode->FindNext(); } // hide any Line Level Attributes on next line which are different to those found on this line // then copy any Line Level Attributes from this line to the next // which don't exist (or have just been hiden) on the next line Node* pThisLineNode = this->FindFirstChild(); while (pThisLineNode!=NULL) { if (pThisLineNode->IsAnAttribute() && ((NodeAttribute*)pThisLineNode)->IsALineLevelAttrib()) { NodeAttribute* pThisLineLLA = (NodeAttribute*)pThisLineNode; NodeAttribute* pNextLineLLA = (NodeAttribute*)(pNextLine->FindFirstChild(pThisLineLLA->GetAttributeType())); if (pNextLineLLA!=NULL && !((*pNextLineLLA)==(*pThisLineLLA)) ) { if (!StoryLocalised) if (!pStory->DoLocaliseCommonAttributes(pUndoOp,FALSE,TRUE,&LLASet)) return FALSE; StoryLocalised = TRUE; if (pUndoOp!=NULL) { if (!pUndoOp->DoHideNode(pNextLineLLA,FALSE)) return FALSE; } else { pNextLineLLA->CascadeDelete(); delete pNextLineLLA; } pNextLineLLA = NULL; // flag its been hidden } if (pNextLineLLA==NULL) { if (!StoryLocalised) if (!pStory->DoLocaliseCommonAttributes(pUndoOp,FALSE,TRUE,&LLASet)) return FALSE; StoryLocalised = TRUE; TRACEUSER("wuerthne", _T("calling SimpleCopy for att %d %d"), IS_A(pThisLineLLA, AttrTxtLineSpace), IS_A(pThisLineLLA, AttrTxtRuler)); NodeAttribute* pNewLLA = (NodeAttribute*)pThisLineLLA->SimpleCopy(); TRACEUSER("wuerthne", _T("node copied %d %d"), IS_A(pNewLLA, AttrTxtLineSpace), IS_A(pNewLLA, AttrTxtRuler)); if (pNewLLA==NULL) return FALSE; pNewLLA->AttachNode(pNextLine,FIRSTCHILD,TRUE,FALSE); if (pUndoOp!=NULL) { Action* UndoHideAttrAction; if (HideNodeAction::Init(pUndoOp,pUndoOp->GetUndoActionList(),pNewLLA,TRUE,&UndoHideAttrAction)==AC_FAIL) return FALSE; } } } pThisLineNode = pThisLineNode->FindNext(); } // if attrs localised on next line, factor out (globally) and flag line affected if (StoryLocalised) { if (!pStory->DoFactorOutCommonChildAttributes(pUndoOp,TRUE,&LLASet)) return FALSE; pNextLine->FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp(); } return TRUE; } /******************************************************************************************** > TextLine* TextLine::FindFirstLineOfParagraph() Author: Ed_Cornes (Xara Group Ltd) Created: 17/7/96 Returns: ptr to first line of paragraph in which this line is to be found (or NULL if error) ********************************************************************************************/ TextLine* TextLine::FindFirstLineOfParagraph() { TextLine* pLine = this; while (1) { TextLine* pPrevLine = pLine->FindPrevLine(); if (pPrevLine==NULL || pPrevLine->FindEOLNode()!=NULL) break; pLine = pPrevLine; } return pLine; } /******************************************************************************************** > static BOOL TextLine::IsAttrTypeLineLevel(CCRuntimeClass* pAttrType) Author: Ed_Cornes (Xara Group Ltd) Created: 17/7/96 Inputs: pAttrType - Returns: TRUE if attr type is a line level attribute ********************************************************************************************/ BOOL TextLine::IsAttrTypeLineLevel(CCRuntimeClass* pAttrType) { return pAttrType==CC_RUNTIME_CLASS(AttrTxtJustification) || pAttrType==CC_RUNTIME_CLASS(AttrTxtLineSpace) || pAttrType==CC_RUNTIME_CLASS(AttrTxtLeftMargin) || pAttrType==CC_RUNTIME_CLASS(AttrTxtRightMargin) || pAttrType==CC_RUNTIME_CLASS(AttrTxtFirstIndent) || pAttrType==CC_RUNTIME_CLASS(AttrTxtRuler); } /******************************************************************************************** > BOOL TextLine::AddChildLineLevelAttrsToSet(AttrTypeSet* pAttrTypeSet) Author: Ed_Cornes (Xara Group Ltd) Created: 18/7/96 Inputs: pAttrTypeSet - Outputs: pAttrTypeSet - Returns: FALSE if fails ********************************************************************************************/ BOOL TextLine::AddChildLineLevelAttrsToSet(AttrTypeSet* pAttrTypeSet) { Node* pNode = FindFirstChild(); while (pNode!=NULL) { if (pNode->IsAnAttribute() && ((NodeAttribute*)pNode)->IsALineLevelAttrib()) if (!pAttrTypeSet->AddToSet(((NodeAttribute*)pNode)->GetAttributeType())) return FALSE; pNode = pNode->FindNext(); } return TRUE; } /******************************************************************************************** > BOOL FormatState::FinishTabSection(VisibleTextNode* pLastFormattedNode, BOOL IsLastTabSection) Author: Martin Wuerthner Created: 23/06/06 Inputs: pLastFormattedNode - the most recently fitted node IsLastTabSection - TRUE if this section has been terminated by EOL or a line break (i.e., not by a Tab), which means we should add ExtraSpaceOnChars/Space here) Returns: ptr to char to break at (or NULL if ERROR) Purpose: This routine is called when a tab or the end of the line is encountered, so the text after the previous tab (or the start of the line) can be finally formatted. The Width/Advance of the Tab is always set and if SetCharPositions is TRUE, then all the character positions are set. ********************************************************************************************/ BOOL FormatState::FinishTabSection(VisibleTextNode* pLastFormattedNode, BOOL IsLastTabSection) { // first, set the Tab character's width - this is only necessary for non-Left tabs // (for left tabs we have already set it when encountering the tab) if (ActiveTabType != LeftTab) { // pLastTabVTN can only be undefined for ActiveTabType == LeftTab ((HorizontalTab*)pLastTabVTN)->SetCharWidth(RemainingSpace); ((HorizontalTab*)pLastTabVTN)->SetCharAdvance(RemainingSpace); } if (!SetCharPositions) return TRUE; // full formatting desired VisibleTextNode* pVTN = pLastTabVTN ? pLastTabVTN : pFirstVTN; MILLIPOINT Pos = AnchorPos; do { ERROR2IF(pVTN == NULL, FALSE, "FinishTabSection - unexpected end of line"); if (Pos + CharPosOffset != pVTN->GetPosInLine()) { pVTN->SetPosInLine(Pos + CharPosOffset); pVTN->FlagNodeAndDescendantsAffectedAndParentsHaveDescendantAffected(); } Pos += pVTN->GetCharAdvance(); if (IsLastTabSection && IS_A(pVTN,TextChar)) Pos += ExtraOnChars; if (IsLastTabSection && pVTN->IsASpace()) Pos += ExtraOnSpaces; if (pVTN == pLastFormattedNode) break; pVTN = pVTN->FindNextVTNInLine(); } while(1); return TRUE; } /******************************************************************************************** > void FormatState::AdvanceBy(MILLIPOINT CharWidth) Author: Martin Wuerthner Created: 23/06/06 Inputs: CharWidth - the amount to advance by IsADecimalPoint - TRUE if the currently active tab is a decimal tab and the character to be formatted is its decimal point character Purpose: Account for a char with width CharWidth to be fitted into the current line. See also: TextLine::FindBreakChar for a description of the formatting algorithm used ********************************************************************************************/ void FormatState::AdvanceBy(MILLIPOINT CharWidth, BOOL IsADecimalPoint) { // Note: This routine may only update Width and RemainingSpace, otherwise // the backtracking in IsAvailable() does not work as expected. if (ActiveTabType == CentreTab && RemainingSpace > 0) { MILLIPOINT SpaceToLeftUsed = 0; // half the width is distributed into RemainingSpace, if it is big enough if (RemainingSpace >= CharWidth / 2) { // half the width fully fits into RemainingSpace SpaceToLeftUsed = CharWidth / 2; } else { // does not fit, so use up all of RemainingSpace SpaceToLeftUsed = RemainingSpace; } // distribute some of the character to the left RemainingSpace -= SpaceToLeftUsed; // and the remainder to the right Width += CharWidth - SpaceToLeftUsed; } else if ((ActiveTabType == RightTab || (ActiveTabType == DecimalTab && !DecimalPointFound && !IsADecimalPoint)) && RemainingSpace > 0) { if (RemainingSpace >= CharWidth) RemainingSpace -= CharWidth; else { Width += CharWidth - RemainingSpace; RemainingSpace = 0; } } else { // We end up here if: // - the last tab was left tab, or // - the last tab was a decimal tab and we have already found the decimal point, or // - the last tab was a decimal tab and this is a decimal point character (NB. - the // decimal point is always formatted to the right of the tab position), or, finally, // - this is any kind of tab and we have no more remaining space to the left of it // In all these cases, we simply format the character to the right of the current position, // which is the easiest thing to do anyway Width += CharWidth; } } /******************************************************************************************** > BOOL FormatState::IsAvailable(MILLIPOINT CharWidth, BOOL IsATab) Author: Martin Wuerthner Created: 23/06/06 Inputs: CharWidth - the amount to advance by IsATab - TRUE if the character to be fitted is a tab IsADecimalPoint - TRUE if the currently active tab is a decimal tab and the character to be formatted is its decimal point character Purpose: Test whether a char with width CharWidth can be fitted into the current line. ********************************************************************************************/ BOOL FormatState::IsAvailable(MILLIPOINT CharWidth, BOOL IsATab, BOOL IsADecimalPoint) { // If the full CharWidth still fits, then we can return TRUE straight away // without having to look at the active tab type if (Width + CharWidth < FitWidth) return TRUE; // If the above did not succeed there is no hope in the standard case (last // tab was left tab or the beginning of the line) - the same is true for a // tab character because it will start a new tab section, so none of its width // can go to the left of the current formatting position if (ActiveTabType == LeftTab || IsATab) return FALSE; // For other tab types, things are a bit more complicated because half or // all of the character can go to the left of the tab stop. Rather than // duplicating all the AdvanceBy code, we simply advance and backtrack. MILLIPOINT OldWidth = Width; MILLIPOINT OldRemainingSpace = RemainingSpace; AdvanceBy(CharWidth, IsADecimalPoint); MILLIPOINT NewWidth = Width; Width = OldWidth; RemainingSpace = OldRemainingSpace; return NewWidth < FitWidth; } /******************************************************************************************** > VisibleTextNode* TextLine::FindBreakChar(MILLIPOINT FitWidth, BOOL SetCharPositions, MILLIPOINT Indent, MILLIPOINT CharPosOffset, MILLIPOINT ExtraOnChars, MILLIPOINT ExtraOnSpaces) Author: Martin Wuerthner (based on routine by Ed_Cornes created 15/7/96) Created: 23/06/06 Inputs: FitWidth - the overall width available SetCharPositions - TRUE if character positions should be set Indent = starting position of first character (logical, counts as far as tab stop positions are concerned) CharPosOffset = positional offset to be added to each character, transparent as far as tab stop positions are concerned ExtraOnChars, ExtraOnSpaces - see PositionCharsInLine The last four are only used if SetCharPositions = TRUE Returns: ptr to char to break at (or NULL if ERROR) Purpose: Find words to fit given width, absorbing spaces, optionally formats line ********************************************************************************************/ VisibleTextNode* TextLine::FindBreakChar(MILLIPOINT FitWidth, BOOL SetCharPositions, MILLIPOINT Indent, MILLIPOINT CharPosOffset, MILLIPOINT ExtraOnChars, MILLIPOINT ExtraOnSpaces) { // This routine does not just find the break char (SetCharPositions = FALSE), it // also formats lines by setting the character positions (SetCharPositions = TRUE). // With the introduction of Tabs, formatting has become more complex, so it makes // a lot of sense to have // depending on whether // Finding the break char is straightforward when no tabs are involved. // You just add up the widths and remember the last space character and // return that when the available width has been exceeded. // // With tabs, things become a bit more complex. Left tabs are easy: A left tab // is treated like a character with the width of the remaining space up to the // tab stop. When dealing with centre or right tabs we need to remember how much // space we had left to the left of the tab stop. Half (for centre tabs) or all // (for right tabs) of the width of all subsequent text goes to the left until // that space is filled up, all remaining space goes to the right only. // Decimal tabs behave like centre tabs except that distributing half the width // to the left stops as soon as the decimal point has been seen. // // The formatting algorithm below divides the line into sections delimited by // tabs or end of line. When a section is finished (i.e., when seeing a tab or // end of line), then the complete section is formatted (using FinishTabSection()). TRACEUSER("wuerthne", _T("FindBreakChar, SetCharPositions = %d"), SetCharPositions); VisibleTextNode* pBreakChar = NULL; BOOL CharOnLine = FALSE; VisibleTextNode* pPrevVTN = NULL; VisibleTextNode* pVTN = this->FindFirstVTN(); // create a FormatState object that takes care of the formatting FormatState State(FitWidth, SetCharPositions, pVTN, Indent, CharPosOffset, ExtraOnChars, ExtraOnSpaces); while(pVTN!=NULL) { BOOL IsATab = IS_A(pVTN, HorizontalTab); BOOL IsADecimalPoint = FALSE; if (State.ActiveTabType == DecimalTab && pVTN->IsATextChar()) { // if the currently active tab is a decimal tab check whether we have hit a decimal point // NB. - the decimal point character is stored in the tab stop IsADecimalPoint = (((TextChar*)pVTN)->GetUnicodeValue() == State.DecimalPointChar); } if (IsATab) { TxtTabType ThisTabType; MILLIPOINT ThisTabPos; MILLIPOINT TabWidth; // finish the previous tab State.FinishTabSection(pVTN, FALSE); // find the next tab stop (always returns usable values - if there are no more // tab stops on the ruler the routine assumes left tabs at equidistant positions) WCHAR DecimalPointChar; TxtRulerAttribute::FindTabStopInRuler(mpRuler, State.Width, &ThisTabType, &ThisTabPos, &DecimalPointChar); TabWidth = ThisTabPos - State.Width; ((HorizontalTab*)pVTN)->SetCharAdvance(TabWidth); ((HorizontalTab*)pVTN)->SetCharWidth(TabWidth); State.AnchorPos = State.Width; if (ThisTabType != LeftTab) { // Space between the tab character and the stop it aligns to State.RemainingSpace = TabWidth; } if (ThisTabType == DecimalTab) { State.DecimalPointChar = DecimalPointChar; State.DecimalPointFound = FALSE; } State.pLastTabVTN = pVTN; State.ActiveTabType = ThisTabType; State.ActiveTabPos = ThisTabPos; // we allow line breaks at tabs pBreakChar = pPrevVTN; } if (!pVTN->IsACaret()) { if (pVTN->IsAnEOLNode()) { State.FinishTabSection(pVTN, TRUE); return pVTN; } else if (pVTN->IsASpace()) { State.AdvanceBy(pVTN->GetCharAdvance(), IsADecimalPoint); pBreakChar = pVTN; } else if (!CharOnLine || State.IsAvailable(pVTN->GetCharWidth(), IsATab, IsADecimalPoint)) { if (IsATab) State.Width += pVTN->GetCharAdvance(); else State.AdvanceBy(pVTN->GetCharAdvance(), IsADecimalPoint); if (pVTN->IsAHyphen()) pBreakChar = pVTN; if (State.ActiveTabType == DecimalTab && !State.DecimalPointFound && IsADecimalPoint) State.DecimalPointFound = TRUE; } else { // did not fit if (pBreakChar==NULL) pBreakChar = pPrevVTN; if (pBreakChar) State.FinishTabSection(pBreakChar, TRUE); return pBreakChar; } CharOnLine = TRUE; } pPrevVTN = pVTN; if (SetCharPositions) { // formatting run - we stay within our line and finish when we run past the end pVTN = pVTN->FindNextVTNInLine(); if (!pVTN) { State.FinishTabSection(pPrevVTN, TRUE); return NULL; } } else { pVTN = pVTN->FindNextVTNInStory(); } } ERROR2(FALSE,"FindBreakChar() - story has no final EOL!"); } /******************************************************************************************** > BOOL TextLine::Wrap(UndoableOperation* pUndoOp, MILLIPOINT WrapWidth) Author: Ed_Cornes (Xara Group Ltd) Created: 15/7/96 Inputs: pUndoOp - WrapWidth - width into which to format LeftIndent - the left indent Returns: FALSE if fails Purpose: Word wrap the line ********************************************************************************************/ BOOL TextLine::Wrap(UndoableOperation* pUndoOp, MILLIPOINT WrapWidth, MILLIPOINT Indent) { VisibleTextNode* pBreakChar = this->FindBreakChar(WrapWidth, FALSE, Indent); if (pBreakChar==NULL) return FALSE; // if break char on this line, wrap the rest of the line forward // else wrap all lines and chars upto the break char back on this line TextLine* pBreakLine = pBreakChar->FindParentLine(); ERROR2IF(pBreakLine==NULL,FALSE,"TextLine::Wrap() - break char has no parent line"); if (pBreakLine==this) { VisibleTextNode* pAfterBreak = pBreakChar->FindNextVTNInLine(); if (pAfterBreak!=NULL) if (!pAfterBreak->WrapRestOfLineForward(pUndoOp)) return FALSE; } else { TextLine* pNextLine = this->FindNextLine(); while (pNextLine!=pBreakLine) { ERROR2IF(pNextLine==NULL,FALSE,"TextLine::Wrap() - did not find line with break char!"); VisibleTextNode* pLastVTN = pNextLine->FindLastVTN(); ERROR2IF(pLastVTN==NULL,FALSE,"TextLine::Wrap() - no VTN on line!"); if (!pLastVTN->WrapFromStartOfLineBack(pUndoOp)) return FALSE; pNextLine = this->FindNextLine(); } if (!pBreakChar->WrapFromStartOfLineBack(pUndoOp)) return FALSE; } return TRUE; } /******************************************************************************************** > BOOL TextLine::ReCalcLineInfo(TextLineInfo* pLineInfo) Author: Ed_Cornes (Xara Group Ltd) Created: 4/2/96 Outputs: pLineInfo - line level info Returns: FALSE if fails Purpose: Recalculate line level info (absorbing space at end of line) Note: Also updates line level info in node itself ********************************************************************************************/ BOOL TextLine::ReCalcLineInfo(TextLineInfo* pLineInfo) { ERROR2IF(pLineInfo==NULL,FALSE,"TextLine::ReCalcLineInfo() - pLineInfo==NULL"); TRACEUSER("wuerthne", _T("RecalcLineInfo")); // reset info in node to be calculated SetLineAscent(0); SetLineDescent(0); SetLineSize(0); // init local info to be calculated MILLIPOINT SumCharAdvances = 0; INT32 NumChars = 0; INT32 NumSpaces = 0; MILLIPOINT SumCharAdvancesToLastNonSpace = 0; INT32 NumCharsToLastNonSpace = 0; INT32 NumSpacesToLastNonSpace = 0; // for each AbstractTextChar on line ... BOOL AbstractTextCharFound = FALSE; Node* pNode = FindFirstChild(); ERROR3IF(pNode==NULL,"TextLine::ReCalcLineInfo() - line has no children!"); while (pNode!=NULL) { if (pNode->IsAnAbstractTextChar()) { AbstractTextChar* pATC= (AbstractTextChar*)pNode; // update info in TextLine (but only use EOL if no chars on line) if (!pATC->IsAnEOLNode() || !AbstractTextCharFound) { UpdateLineAscent( pATC->GetFontAscent()); UpdateLineDescent(pATC->GetFontDescent()); UpdateLineSize( pATC->GetFontSize()); } // update local info if (IS_A(pATC,TextChar)) NumChars += 1; if (IS_A(pATC, HorizontalTab)) { // only spaces/chars after the last tab can be stretched for full justification TRACEUSER("wuerthne", _T("tab at %08x width=%d"), pATC, pATC->GetCharAdvance()); NumSpaces = 0; NumChars = 0; } if (pATC->IsASpace()) NumSpaces += 1; else if (!pATC->IsAnEOLNode()) { NumCharsToLastNonSpace = NumChars; NumSpacesToLastNonSpace = NumSpaces; SumCharAdvancesToLastNonSpace = SumCharAdvances + pATC->GetCharWidth(); // exc tracking of this char } SumCharAdvances += pATC->GetCharAdvance(); AbstractTextCharFound = TRUE; } pNode = pNode->FindNext(); } ERROR2IF(!AbstractTextCharFound,FALSE,"TextLine::ReCalcLineInfo() - line has no AbstractTextChars"); // set outputs pLineInfo->SumCharAdvances = SumCharAdvancesToLastNonSpace; pLineInfo->NumChars = NumCharsToLastNonSpace; pLineInfo->NumSpaces = NumSpacesToLastNonSpace; pLineInfo->justification = GetJustification(); pLineInfo->ParaLeftMargin = GetEffectiveLeftMargin(); pLineInfo->ParaRightMargin = GetParaRightMargin(); pLineInfo->Ruler = GetRuler(); return TRUE; } /******************************************************************************************** > BOOL TextLine::PositionCharsInLine(TextLineInfo* pLineInfo) Author: Ed_Cornes (Xara Group Ltd) Created: 18/4/95 Inputs: pLineInfo - required info about the line Returns: FALSE if fails Purpose: set the position of each char on the line accounting for justification ALSO, flag any chars that have moved as 'affected' ********************************************************************************************/ BOOL TextLine::PositionCharsInLine(TextLineInfo* pLineInfo) { ERROR2IF(pLineInfo==NULL,FALSE,"TextLine::PositionCharsInLine() - pLineInfo==NULL"); // we have two different left/right margins: margin properties of the complete story // (only for text on path) and margins applied as attributes to paragraphs MILLIPOINT PhysicalLeftMargin = pLineInfo->LeftMargin; // story property MILLIPOINT ParagraphLeftMargin = pLineInfo->ParaLeftMargin; // paragraph attribute MILLIPOINT LeftMargin = PhysicalLeftMargin + ParagraphLeftMargin; MILLIPOINT PhysicalRightMargin = pLineInfo->RightMargin; MILLIPOINT ParagraphRightMargin = pLineInfo->ParaRightMargin; MILLIPOINT RightMargin = PhysicalRightMargin - ParagraphRightMargin; MILLIPOINT SumCharAdvances = pLineInfo->SumCharAdvances; BOOL WordWrapping = pLineInfo->WordWrapping; MILLIPOINT JustifyWidth = RightMargin - LeftMargin; TRACEUSER("wuerthne", _T("PositionCharsInLine, Sum=%d, JWidth=%d"), SumCharAdvances, JustifyWidth); // if word wrapping, fully justified, text does not fill line and end of paragraph // left justified Justification justification = pLineInfo->justification; if (WordWrapping && justification==JFULL && SumCharAdvances < JustifyWidth && FindEOLNode()!=NULL) justification = JLEFT; // calc amount by which chars need to be adjusted (default to left justify) MILLIPOINT CharPosOffset = PhysicalLeftMargin; MILLIPOINT ExtraOnSpaces = 0; MILLIPOINT ExtraOnChars = 0; switch (justification) { // for both right and centre justification we subtract ParagraphLeftMargin because this // will be added in again later case JRIGHT: CharPosOffset = RightMargin - SumCharAdvances - ParagraphLeftMargin; break; case JCENTRE: CharPosOffset = (LeftMargin + RightMargin - SumCharAdvances)/2 - ParagraphLeftMargin; break; case JFULL: if (JustifyWidth!=0) { // if text does not fill line, expand spaces so it does, // but if no spaces, and word wrapping, increase inter-char spacing so word fills line // if text overflows line, reduce inter-char spacing so words fit line INT32 gap = JustifyWidth - SumCharAdvances; INT32 NumChars = pLineInfo->NumChars; INT32 NumSpaces = pLineInfo->NumSpaces; if (gap>0) { if (NumSpaces>0) ExtraOnSpaces = gap/NumSpaces; else if (WordWrapping && NumChars>1) ExtraOnChars = gap/(NumChars-1); } else if (pLineInfo->NumChars>1) ExtraOnChars = gap/(NumChars-1); } break; default: break; } // run through line setting all char's 'PosInLine' (flagging those which moved) TRACEUSER("wuerthne", _T("calling FindBreakChar, Indent=%d, Offset=%d, ExtraC=%d, ExtraS=%d"), ParagraphLeftMargin, CharPosOffset, ExtraOnChars, ExtraOnSpaces); FindBreakChar(INT32_MAX, TRUE, ParagraphLeftMargin, CharPosOffset, ExtraOnChars, ExtraOnSpaces); return TRUE; } /******************************************************************************************** > BOOL TextLine::CalcBaseAndDescentLine(MILLIPOINT* pBaseLine, MILLIPOINT* pDescentLine, MILLIPOINT LastDescentLine, BOOL LastDescentLineValid) Author: Ed_Cornes (Xara Group Ltd) Created: 4/2/96 Inputs: LastDescentLine - DescentLine of last line LastDescentLineValid - FALSE if first line in story Outputs: pBaseLine - base line of this line pDescentLine - base line of this line Returns: FALSE if fails Purpose: Calculate BaseLine and DescentLine for this line given the last line's DescentLine if this is the first line in the story, the baseline is 0 and the DescentLine has to be back calculated ********************************************************************************************/ BOOL TextLine::CalcBaseAndDescentLine(MILLIPOINT* pBaseLine, MILLIPOINT* pDescentLine, MILLIPOINT LastDescentLine, BOOL LastDescentLineValid) { // get some info about the line ... MILLIPOINT LineAscent = GetLineAscent(); MILLIPOINT LineDescent = GetLineDescent(); MILLIPOINT LineHeight = LineAscent-LineDescent; MILLIPOINT LineSpacing = GetLineSpacing(); MILLIPOINT BaseLine = 0; MILLIPOINT DescentLine = 0; if (LineSpacing!=0) { MILLIPOINT OffsetFromDescentLineToBaseLine = MulDiv(LineSpacing, -LineDescent, LineHeight); if (LastDescentLineValid) { DescentLine = LastDescentLine - LineSpacing; BaseLine = DescentLine + OffsetFromDescentLineToBaseLine; } else DescentLine = BaseLine - OffsetFromDescentLineToBaseLine; } else { FIXED16 LineSpaceRatio = GetLineSpaceRatio(); MILLIPOINT AbsolutLineSpacing = MILLIPOINT( XLONG(LineHeight) * LineSpaceRatio ); MILLIPOINT OffsetFromLastDescentLineToBaseLine = LineAscent; if (LineSpaceRatio<1) OffsetFromLastDescentLineToBaseLine = XLONG(LineAscent) * LineSpaceRatio; if (LastDescentLineValid) { BaseLine = LastDescentLine - OffsetFromLastDescentLineToBaseLine; DescentLine = LastDescentLine - AbsolutLineSpacing; } else DescentLine = BaseLine + OffsetFromLastDescentLineToBaseLine - AbsolutLineSpacing; } // set outputs *pDescentLine = DescentLine; *pBaseLine = BaseLine; return TRUE; } /******************************************************************************************** > BOOL TextLine::SetCharMatrices(MILLIPOINT LinePos) Author: Ed_Cornes (Xara Group Ltd) Created: 18/4/95 Inputs: LinePos - pos of line in story Returns: FALSE if fails Purpose: set matrices in all VTN on a line ********************************************************************************************/ BOOL TextLine::SetCharMatrices(MILLIPOINT LinePos) { Node* pNode=FindFirstChild(); while (pNode!=NULL) { if (pNode->IsAVisibleTextNode()) { VisibleTextNode* pVTN=(VisibleTextNode*)pNode; if (pVTN->Affected()) pVTN->SetMatrix( Matrix( pVTN->GetPosInLine(), LinePos+pVTN->GetBaseLineShift() ) ); } pNode=pNode->FindNext(); } return TRUE; } /******************************************************************************************** > BOOL TextLine::FitTextToPath(TextStoryInfo* pStoryInfo, MILLIPOINT LinePos) Author: Ed_Cornes (Xara Group Ltd) Created: 18/4/95 Inputs: pStoryInfo - cached info associated with path LinePos - pos of line in story Returns: FALSE if fails Purpose: set matrices in all VTN on a line so as to fit text to the path, accounting for any transform which was applied before the text was fitted to the path ********************************************************************************************/ BOOL TextLine::FitTextToPath(TextStoryInfo* pStoryInfo, MILLIPOINT LinePos) { ERROR2IF(pStoryInfo==NULL,FALSE,"TextLine::FitTextToPath() - pStoryInfo==NULL"); TextStory* pTextStory = FindParentStory(); ERROR2IF(pTextStory==NULL,FALSE,"TextLine::FitTextToPath() - pTextStory==NULL"); // get values required to account for transforms applied to text before it was fitted to the path FIXED16 scale = pTextStory->GetCharsScale(); FIXED16 xscale = (scale<0 ? -scale : scale) * pTextStory->GetCharsAspect(); FIXED16 shear = scale * tan(pTextStory->GetCharsShear().MakeDouble()); // double rotation = pTextStory->GetCharsRotation().MakeDouble(); // scale the line width offset LinePos = XLONG(LinePos) * scale; MILLIPOINT LinePosX = (MILLIPOINT)(LinePos * pStoryInfo->UnitDirectionVectorX + 0.5); MILLIPOINT LinePosY = (MILLIPOINT)(LinePos * pStoryInfo->UnitDirectionVectorY + 0.5); // if chars tangential, get a pointer to a double to read the angle into // else set the angle to 0 and make the pointer NULL (ie don't actually ask for the angle) double TangentAtDist=0; double* pTangentAtDist=NULL; if (pTextStory->IsTextOnPathTangential()) pTangentAtDist=&TangentAtDist; // some vars used repeatedly while scanning line ... BOOL Found=FALSE; DocCoord CoordAtDist(0,0); Matrix VTNMatrix(0,0); ProcessPathDistance PathDistanceProcess(64); Node* pNode=FindFirstChild(); while (pNode) { if (pNode->IsAVisibleTextNode()) { VisibleTextNode* pVTN=(VisibleTextNode*)pNode; if (pVTN->Affected()) { // get distance to centre of char accounting for pre-transform MILLIPOINT CharWidthBy2 = XLONG(pVTN->GetCharWidth()/2) * xscale; MILLIPOINT Dist=XLONG(pVTN->GetPosInLine()) * xscale + CharWidthBy2; if (pStoryInfo->PathClosed) { Dist = Dist % pStoryInfo->PathLength; if (Dist<0) Dist += pStoryInfo->PathLength; } // and thence the coord (and tangent) this distance along the path if (PathDistanceProcess.GetCoordAndTangent(&CoordAtDist,pTangentAtDist,&Found,Dist,pStoryInfo->pPath)==FALSE) return FALSE; // set the char matrix to account for pre-transform and baseline shift, VTNMatrix=Matrix( xscale, 0, shear, scale, 0, XLONG(pVTN->GetBaseLineShift()) * scale ); // then rotate appropriately, // VTNMatrix*=Matrix(DocCoord(CharWidthBy2,0),(TangentAtDist+rotation)/PI*180); VTNMatrix*=Matrix(DocCoord(CharWidthBy2,0),TangentAtDist/PI*180); // then translate to the correct point on the path and account for line spacing VTNMatrix.translate(CoordAtDist.x-CharWidthBy2+LinePosX,CoordAtDist.y+LinePosY); pVTN->SetMatrix(VTNMatrix); } } pNode=pNode->FindNext(); } return TRUE; } /******************************************************************************************* > VisibleTextNode* TextLine::FindCharAtDistAlongLine(MILLIPOINT Distance, BOOL* LeftHandSide) Author: Ed_Cornes (Xara Group Ltd) Created: 12/5/95 Inputs: Distance - distance along the line to look for LeftHandSide - pointer to a BOOL Outputs: LeftHandSide - TRUE if the point supplied is in the left half of the char FALSE if the point supplied is in the right half of the char Returns: A pointer to the character found (NULL if error) Purpose: Call this function to get the character at a certian distance along this TextLine. If the distance is off the right end of the line then a pointer to the EOLNode is returned. ********************************************************************************************/ VisibleTextNode* TextLine::FindCharAtDistAlongLine(MILLIPOINT Distance, BOOL* LeftHandSide) { ERROR2IF(LeftHandSide==NULL,NULL,"TextLine::FindCharAtDistAlongLine() - NULL side pointer"); VisibleTextNode* pVTN = FindFirstVTN(); ERROR2IF(pVTN==NULL,NULL,"TextLine::FindCharAtDistAlongLine() - has no VTN"); while (1) { const MILLIPOINT Left = pVTN->GetPosInLine(); const MILLIPOINT Centre = Left+pVTN->GetCharWidth()/2; const MILLIPOINT Next = Left+pVTN->GetCharAdvance(); // if to left of centre of this char (and to right of previous if any) // or at end of line, return char and 'to left' if (Distance<=Centre || pVTN->IsAnEOLNode()) { *LeftHandSide = TRUE; return pVTN; } // if to left of next char (and right of centre of this char), // return char and 'to right' if (Distance<=Next) { *LeftHandSide = FALSE; return pVTN; } // if no next VTN, return this char and 'to right' VisibleTextNode* pNextVTN = pVTN->FindNextVTNInLine(); if (pNextVTN==NULL) { *LeftHandSide = FALSE; return pVTN; } pVTN = pNextVTN; } } /******************************************************************************************** > BOOL TextLine::LineContainsCaret(CaretNode** pCaret) Author: Ed_Cornes (Xara Group Ltd) Created: 9/7/96 Returns: ptr to caret if line contains caret else NULL ********************************************************************************************/ CaretNode* TextLine::FindCaret() const { return (CaretNode*)FindFirstChild(CC_RUNTIME_CLASS(CaretNode)); } /******************************************************************************************** > BOOL TextLine::WholeLineSelected() Author: Ed_Cornes (Xara Group Ltd) Created: 4/5/95 Returns: TRUE if the line itself or all the chars on the line are selected (including hard EOL but excluding soft EOL and caret) ********************************************************************************************/ BOOL TextLine::WholeLineSelected() { if (IsSelected()==FALSE) { VisibleTextNode* pVTN = FindFirstVTN(); ERROR2IF(pVTN==NULL,FALSE,"TextLine::WholeLineSelected() - line has no VTN!"); while (pVTN!=NULL) { if (!pVTN->IsSelected() && !pVTN->IsACaret()) return FALSE; pVTN = pVTN->FindNextVTNInLine(); } } return TRUE; } /******************************************************************************************** > TextLine* TextLine::FindNextLine() Author: Ed_Cornes (Xara Group Ltd) Created: 24/7/96 Returns: pointer to next TextLine ********************************************************************************************/ TextLine* TextLine::FindNextLine() const { return (TextLine*)FindNext(CC_RUNTIME_CLASS(TextLine)); } /******************************************************************************************** > TextLine* TextLine::FindPrevLine() Author: Ed_Cornes (Xara Group Ltd) Created: 24/7/96 Returns: pointer to previous TextLine ********************************************************************************************/ TextLine* TextLine::FindPrevLine() const { return (TextLine*)FindPrevious(CC_RUNTIME_CLASS(TextLine)); } /******************************************************************************************** > VisibleTextNode* TextLine::FindFirstVTN() Author: Peter_Arnold (Xara Group Ltd) Created: 22/5/95 Returns: A pointer to the first VTN in this line; NULL if there isn't one Purpose: For getting a pointer to the first Visible Text Node in this TextLine ********************************************************************************************/ VisibleTextNode* TextLine::FindFirstVTN() const { return (VisibleTextNode*)FindFirstChild(CC_RUNTIME_CLASS(VisibleTextNode)); } /******************************************************************************************** > VisibleTextNode* TextLine::FindLastVTN() Author: Peter_Arnold (Xara Group Ltd) Created: 22/5/95 Returns: A pointer to the last VTN in this line; NULL if there isn't one Purpose: For getting a pointer to the last Visible Text Node in this TextLine ********************************************************************************************/ VisibleTextNode* TextLine::FindLastVTN() const { return (VisibleTextNode*)FindLastChild(CC_RUNTIME_CLASS(VisibleTextNode)); } /******************************************************************************************** > EOLNode* TextLine::FindEOLNode() Author: Ed_Cornes (Xara Group Ltd) Created: 8/2/96 Returns: ptr to EOL (or NULL if none - error set) ********************************************************************************************/ EOLNode* TextLine::FindEOLNode() const { EOLNode* pEOL = (EOLNode*)FindFirstChild(CC_RUNTIME_CLASS(EOLNode)); ERROR3IF(pEOL!=NULL && pEOL->FindNext(CC_RUNTIME_CLASS(EOLNode))!=NULL, "TextLine::FindEOLNode() - more than one EOLNode on a line!"); return pEOL; } /******************************************************************************************** > MILLIPOINT TextLine::GetLastCharTracking() Author: Ed_Cornes (Xara Group Ltd) Created: 11/1/95 Returns: Returns size of tracking on last char on line ********************************************************************************************/ MILLIPOINT TextLine::GetLastCharTracking() { AbstractTextChar* pATC = (AbstractTextChar*)FindFirstChild(CC_RUNTIME_CLASS(AbstractTextChar)); if (pATC->IsAnEOLNode()) pATC = (AbstractTextChar*)(pATC->FindPrevious(CC_RUNTIME_CLASS(AbstractTextChar))); if (pATC==NULL) return 0; return pATC->GetCharAdvance() - pATC->GetCharWidth(); } /******************************************************************************************** > BOOL TextLine::WillLineWrapOnPath(FIXED16 xscale, MILLIPOINT Length) Author: Mike_Kenny (Xara Group Ltd) Created: 11/6/95 Inputs: pPath - path to fit text to ModLen - length of the path Returns: TRUE if the text line will wrap on this path FALSE if not Purpose: Calculates whether the formatter will wrap this line of text around the path It calculates this by checking the position of the last character (or VTN in the line). If this wraps it is assumed the whole line will wrap ********************************************************************************************/ BOOL TextLine::WillLineWrapOnPath(FIXED16 xscale, MILLIPOINT PLength) { TextStory* pTextStory = FindParentStory(); ERROR2IF(pTextStory==NULL,FALSE,"TextLine::IsLineWrappedOnPath() - pTextStory==NULL"); BOOL found = FALSE; Node* pNode=FindLastChild(); while (pNode && !found) { if (pNode->IsAVisibleTextNode()) found = TRUE; else pNode=pNode->FindPrevious(); } if (pNode) { VisibleTextNode* pVTN=(VisibleTextNode*)pNode; MILLIPOINT Dist=XLONG(pVTN->GetPosInLine()) * xscale; if (Dist>PLength) return TRUE; } return FALSE; } /******************************************************************************************** > BOOL TextLine::WritePreChildrenWeb(BaseCamelotFilter *pFilter) BOOL TextLine::WritePreChildrenNative(BaseCamelotFilter *pFilter) Author: Andy_Hayward (Xara Group Ltd) Created: 10/07/96 Inputs: pFilter - filter to save text line node to Returns: TRUE if successful, FALSE otherwise Purpose: Saves a text line record to the new file format filter ********************************************************************************************/ BOOL TextLine::WritePreChildrenWeb(BaseCamelotFilter *pFilter) { #ifdef DO_EXPORT return CXaraFileTxtLine::WritePreChildrenWeb(pFilter, this); #else return FALSE; #endif } BOOL TextLine::WritePreChildrenNative(BaseCamelotFilter *pFilter) { #ifdef DO_EXPORT return CXaraFileTxtLine::WritePreChildrenNative(pFilter, this); #else return FALSE; #endif } //-------------------------------- BOOL TextLine::WriteBeginChildRecordsWeb(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtLine::WriteBeginChildRecordsWeb(pFilter, this); #else return FALSE; #endif } BOOL TextLine::WriteBeginChildRecordsNative(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtLine::WriteBeginChildRecordsNative(pFilter, this); #else return FALSE; #endif } BOOL TextLine::WriteEndChildRecordsWeb(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtLine::WriteEndChildRecordsWeb(pFilter, this); #else return FALSE; #endif } BOOL TextLine::WriteEndChildRecordsNative(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return CXaraFileTxtLine::WriteEndChildRecordsNative(pFilter, this); #else return FALSE; #endif } ///////////////////////////////////////////////////////////////////////////////////////////// // FormatRegion /******************************************************************************************** > FormatRegion::FormatRegion() Author: Peter_Arnold (Xara Group Ltd) Created: 17/03/95 Purpose: Initialise member variables of the RenderRegion to keep it happy ********************************************************************************************/ FormatRegion::FormatRegion() : RenderRegion() { CurrentClipRect = DocRect(0,0,0,0); ScaledPixelWidth = 1; } /******************************************************************************************** > FormatRegion::~FormatRegion() Author: Peter_Arnold (Xara Group Ltd) Created: 17/03/95 Purpose: Destructor for FormatRegion ********************************************************************************************/ FormatRegion::~FormatRegion() { } /******************************************************************************************** > BOOL FormatRegion::Init(NodeRenderableInk* pFirstNode) Author: Peter_Arnold (Xara Group Ltd) Created: 17/03/95 Inputs: pFirstNode - pointer to a node whose applied attributes are used to initialise the FormatRegion's attribute stack Returns: FALSE if fails Purpose: Initialise a FormatRegion setting the attribute stack to those attributes applied to the specified node ********************************************************************************************/ BOOL FormatRegion::Init(NodeRenderableInk* pFirstNode) { m_pFormatDC = std::auto_ptr( new wxMemoryDC ); if( NULL == m_pFormatDC.get() ) ERROR2(FALSE,"FormatRegion::Init() - CreateCompatibleDC() failed"); RenderView = DocView::GetSelected(); // If there is not a selected doc view (e.g. because it is a clipboard document), // try the current view item. // This should only be an issue when formatting text that's been imported into the clipboard, usually // as part of an OLE operation if (RenderView == NULL) { RenderView = View::GetCurrent(); if (RenderView == NULL) { ERROR3("No view!! Can't format text without a view"); return FALSE; } // ERROR3IF(RenderView->GetDoc()->IsNotAClipboard(),"No selected doc view yet doc attached to current view is not a clipboard doc. Is this right?"); } // Initialise the render region StartRender(); // Get the attributes if (pFirstNode==NULL) return TRUE; CCAttrMap *pAttribMap = new CCAttrMap(30); if (pAttribMap == NULL) return FALSE; if (!pFirstNode->FindAppliedAttributes(pAttribMap)) { delete pAttribMap; return FALSE; } CCAttrMap::iterator pos = pAttribMap->GetStartPosition(); CCAttrMap::iterator end = pAttribMap->GetEndPosition(); while( pos != end ) { CCRuntimeClass *pKey; void *pVal; pAttribMap->GetNextAssoc(pos, pKey, pVal); ((NodeAttribute*) pVal)->Render(this); } delete pAttribMap; return TRUE; } /******************************************************************************************** > BOOL FormatRegion::GetCharMetrics(CharMetrics* pCharMetrics, WCHAR ch) Author: Ed_Cornes (Xara Group Ltd) Created: 14/1/96 Inputs: ch - the character (kern codes should ask for a FONTEMCHAR) Outputs: pCharMetrics - metrics (CharWidth, FontAscent, FontDescent, FontEmWidth) Returns: FALSE if fails Purpose: Get metrics for a given char using the current attributes of the format region ********************************************************************************************/ BOOL FormatRegion::GetCharMetrics(CharMetrics* pCharMetrics, WCHAR ch) { ERROR2IF(pCharMetrics==NULL, FALSE, "FormatRegion::GetCharMetrics() - pCharMetrics==NULL"); // get the char metrics for the specified character wxDC *pDC = m_pFormatDC.get(); CharDescription FontDesc( FONTEMCHAR, RR_TXTFONTTYPEFACE(), RR_TXTBOLD(), RR_TXTITALIC() ); if( FONTMANAGER->GetCharMetrics( pDC, ch, FontDesc, pCharMetrics ) == FALSE ) return FALSE; // get x/y scale factors and apply then to the default metrics found // Note: y scale factor does not include script ratio so does not affect ascent/descent // ie a line of subscript text has the same line ascents/descents as a non-subscript line TxtScriptAttribute* pScriptAttr=RR_TXTSCRIPT(); ERROR2IF(pScriptAttr==NULL,FALSE,"RenderRegion::GetCharAttributeMatrix() - pScriptAttr==NULL"); double ScaleY = (double)RR_TXTFONTSIZE() / TextManager::GetDefaultHeight(); double ScaleX = ScaleY * RR_TXTASPECTRATIO().MakeDouble() * pScriptAttr->Size.MakeDouble(); pCharMetrics->Scale(ScaleX,ScaleY); return TRUE; } /******************************************************************************************** > MILLIPOINT FormatRegion::GetCharsKerning(WCHAR chLeft, WCHAR chRight) Author: Jonathan_Payne (Xara Group Ltd) Created: 15/10/2000 Inputs: chLeft - the left char of a kern pair chRight - the right char of a kern pair Returns: Kern distance between chars or 0 on error or no kern Purpose: Finds the kerning distance between two chars ********************************************************************************************/ MILLIPOINT FormatRegion::GetCharsKerning(WCHAR chLeft, WCHAR chRight) { // get the char metrics for the specified character wxDC *pDC = m_pFormatDC.get(); CharDescription FontDesc(FONTEMCHAR, RR_TXTFONTTYPEFACE(), RR_TXTBOLD(), RR_TXTITALIC()); MILLIPOINT kern = FONTMANAGER->GetCharsKerning( pDC, chLeft, chRight, FontDesc ); // get x/y scale factors and apply then to the default metrics found // Note: y scale factor does not include script ratio so does not affect ascent/descent // ie a line of subscript text has the same line ascents/descents as a non-subscript line TxtScriptAttribute* pScriptAttr=RR_TXTSCRIPT(); ERROR2IF(pScriptAttr==NULL,FALSE,"RenderRegion::GetCharAttributeMatrix() - pScriptAttr==NULL"); double ScaleY = (double)RR_TXTFONTSIZE() / TextManager::GetDefaultHeight(); double ScaleX = ScaleY * RR_TXTASPECTRATIO().MakeDouble() * pScriptAttr->Size.MakeDouble(); if (kern) kern = (MILLIPOINT)(kern * ScaleX + 0.5); return kern; }