nodetxtl.cpp

Go to the documentation of this file.
00001 // $Id: nodetxtl.cpp 1535 2006-07-25 16:50:32Z alex $
00002 /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE
00003 ================================XARAHEADERSTART===========================
00004  
00005                Xara LX, a vector drawing and manipulation program.
00006                     Copyright (C) 1993-2006 Xara Group Ltd.
00007        Copyright on certain contributions may be held in joint with their
00008               respective authors. See AUTHORS file for details.
00009 
00010 LICENSE TO USE AND MODIFY SOFTWARE
00011 ----------------------------------
00012 
00013 This file is part of Xara LX.
00014 
00015 Xara LX is free software; you can redistribute it and/or modify it
00016 under the terms of the GNU General Public License version 2 as published
00017 by the Free Software Foundation.
00018 
00019 Xara LX and its component source files are distributed in the hope
00020 that it will be useful, but WITHOUT ANY WARRANTY; without even the
00021 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00022 See the GNU General Public License for more details.
00023 
00024 You should have received a copy of the GNU General Public License along
00025 with Xara LX (see the file GPL in the root directory of the
00026 distribution); if not, write to the Free Software Foundation, Inc., 51
00027 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
00028 
00029 
00030 ADDITIONAL RIGHTS
00031 -----------------
00032 
00033 Conditional upon your continuing compliance with the GNU General Public
00034 License described above, Xara Group Ltd grants to you certain additional
00035 rights. 
00036 
00037 The additional rights are to use, modify, and distribute the software
00038 together with the wxWidgets library, the wxXtra library, and the "CDraw"
00039 library and any other such library that any version of Xara LX relased
00040 by Xara Group Ltd requires in order to compile and execute, including
00041 the static linking of that library to XaraLX. In the case of the
00042 "CDraw" library, you may satisfy obligation under the GNU General Public
00043 License to provide source code by providing a binary copy of the library
00044 concerned and a copy of the license accompanying it.
00045 
00046 Nothing in this section restricts any of the rights you have under
00047 the GNU General Public License.
00048 
00049 
00050 SCOPE OF LICENSE
00051 ----------------
00052 
00053 This license applies to this program (XaraLX) and its constituent source
00054 files only, and does not necessarily apply to other Xara products which may
00055 in part share the same code base, and are subject to their own licensing
00056 terms.
00057 
00058 This license does not apply to files in the wxXtra directory, which
00059 are built into a separate library, and are subject to the wxWindows
00060 license contained within that directory in the file "WXXTRA-LICENSE".
00061 
00062 This license does not apply to the binary libraries (if any) within
00063 the "libs" directory, which are subject to a separate license contained
00064 within that directory in the file "LIBS-LICENSE".
00065 
00066 
00067 ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS
00068 ----------------------------------------------
00069 
00070 Subject to the terms of the GNU Public License (see above), you are
00071 free to do whatever you like with your modifications. However, you may
00072 (at your option) wish contribute them to Xara's source tree. You can
00073 find details of how to do this at:
00074   http://www.xaraxtreme.org/developers/
00075 
00076 Prior to contributing your modifications, you will need to complete our
00077 contributor agreement. This can be found at:
00078   http://www.xaraxtreme.org/developers/contribute/
00079 
00080 Please note that Xara will not accept modifications which modify any of
00081 the text between the start and end of this header (marked
00082 XARAHEADERSTART and XARAHEADEREND).
00083 
00084 
00085 MARKS
00086 -----
00087 
00088 Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara
00089 designs are registered or unregistered trademarks, design-marks, and/or
00090 service marks of Xara Group Ltd. All rights in these marks are reserved.
00091 
00092 
00093       Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK.
00094                         http://www.xara.com/
00095 
00096 =================================XARAHEADEREND============================
00097  */
00098 
00099 /*
00100 */
00101 
00102 #include "camtypes.h"
00103 #include "nodetxtl.h"
00104 
00105 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00106 #include "blobs.h"
00107 //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00108 //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00109 //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00110 //#include "mario.h"
00111 //#include "nodeattr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00112 #include "nodepath.h"
00113 #include "nodetext.h"
00114 #include "nodetxts.h"
00115 //#include "simon.h"
00116 //#include "textinfo.h"
00117 //#include "matrix.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00118 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00119 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00120 //#include "cameleps.h"
00121 #include "pathproc.h"
00122 #include "fontman.h"
00123 #include "attrmap.h"
00124 #include "cxftext.h"
00125 
00126 DECLARE_SOURCE("$Revision: 1535 $")              
00127 
00128 CC_IMPLEMENT_MEMDUMP(TextLineInfo,      CC_CLASS_MEMDUMP)
00129 CC_IMPLEMENT_DYNAMIC(TextLine, BaseTextClass)
00130 CC_IMPLEMENT_DYNAMIC(FormatRegion, RenderRegion)
00131 
00132 // Declare smart memory handling in Debug builds
00133 #define new CAM_DEBUG_NEW
00134 
00136 // TextLineInfo Methods
00137 
00138 /********************************************************************************************
00139 >   TextLineInfo::TextLineInfo()
00140 
00141     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00142     Created:    4/2/96
00143     Purpose:    default constructor
00144 ********************************************************************************************/
00145 
00146 TextLineInfo::TextLineInfo()
00147 {
00148     SumCharAdvances = 0;
00149     justification   = JLEFT;
00150     LeftMargin      = 0;
00151     RightMargin     = 0;
00152     ParaLeftMargin = 0;
00153     ParaRightMargin = 0;
00154     Ruler = NULL;
00155     WordWrapping    = FALSE;
00156     NumChars        = 0;
00157     NumSpaces       = 0;
00158 }
00159 
00160 
00162 // TextLine Methods
00163 
00164 /********************************************************************************************
00165 >   void TextLine::Init()
00166 
00167     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00168     Created:    3/2/96
00169     Purpose:    common init function for constructors
00170 ********************************************************************************************/
00171 
00172 void TextLine::Init()
00173 {
00174     mLineAscent  = 0;
00175     mLineDescent = 0;
00176     mLineSize    = 0;
00177 
00178     mJustification  = JLEFT;
00179     mLineSpacing    = 0;
00180     mLineSpaceRatio = 1;
00181     mpRuler = new TxtRuler;
00182 
00183     mPosInStory  = 0;
00184 }
00185 
00186 
00187 /********************************************************************************************
00188 >   TextLine::TextLine()
00189 
00190     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00191     Created:    21/12/94
00192     Purpose:    Simple TextLine constructor, it is required so that SimpleCopy will work.
00193 
00194 ********************************************************************************************/
00195 
00196 TextLine::TextLine(): BaseTextClass()   // Call the base class
00197 {
00198     Init();
00199 }
00200 
00201 /********************************************************************************************
00202 >   TextLine::~TextLine()
00203 
00204     Author:     Martin Wuerthner <xara@mw-software.com>
00205     Created:    20/07/06
00206     Purpose:    Destructor
00207 
00208 ********************************************************************************************/
00209 
00210 TextLine::~TextLine()
00211 {
00212     delete mpRuler;
00213 }
00214  
00215 /********************************************************************************************
00216 >   TextLine::TextLine(Node* ContextNode, AttachNodeDirection Direction)
00217 
00218     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00219     Created:    21/12/94
00220     Inputs:     ContextNode: Pointer to a node which this node is to be attached to.     
00221         
00222                 Direction: 
00223             
00224                 Specifies the direction in which the node is to be attached to the 
00225                 ContextNode. The values this variable can take are as follows: 
00226                                   
00227                 PREV      : Attach node as a previous sibling of the context node
00228                 NEXT      : Attach node as a next sibling of the context node
00229                 FIRSTCHILD: Attach node as the first child of the context node
00230                 LASTCHILD : Attach node as a last child of the context node                  
00231 
00232     Purpose:    The main TextLine constructor
00233 ********************************************************************************************/
00234 
00235 TextLine::TextLine(Node* ContextNode,  
00236                      AttachNodeDirection Direction):BaseTextClass(ContextNode, Direction)
00237 {
00238     Init();
00239 }
00240 
00241 
00242 /********************************************************************************************
00243 >   virtual CopyType TextLine::GetCopyType()
00244 
00245     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
00246     Created:    3/5/95
00247     Returns:    A copy type describing how to copy this object
00248     Purpose:    This function returns a type describing how this object is to be copied.
00249                 The fuction is called from the low level copy operation CopyObjects.
00250                 There are two options at present, these being SIMPLECOPY and COMPLEXCOPY.
00251                 SIMPLECOPY indicates that the node can be copied by a call to its virtual
00252                 function SimpleCopy(). 
00253                 COMPLEXCOPY however indicates that the node needs to do its own thing when
00254                 copying and must be called via the ComplexCopy() virtual function. This
00255                 virtual will likely return a tree of copied objects rather than just a
00256                 copy of itself.
00257 ********************************************************************************************/
00258 
00259 CopyType TextLine::GetCopyType()
00260 {
00261     return COMPLEXCOPY;
00262 }
00263 
00264 
00265 /********************************************************************************************
00266 >   Node* TextLine::SimpleCopy()
00267 
00268     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00269     Created:    21/12/94
00270     Returns:    A copy of the node, or NULL if we are out of memory 
00271     Purpose:    This method returns a shallow copy of the node with all Node pointers NULL. 
00272                 The function is virtual, and must be defined for all derived classes of Node  
00273     Errors:     If memory runs out when trying to copy, then ERROR is called with an out of memory
00274                 error and the function returns NULL. 
00275 ********************************************************************************************/
00276 
00277 Node* TextLine::SimpleCopy()
00278 {
00279     // Make a new TextLine and then copy things into it
00280     TextLine* NodeCopy = new TextLine();
00281 
00282     ERROR1IF(NodeCopy == NULL, NULL, _R(IDE_NOMORE_MEMORY)); 
00283 
00284     if (NodeCopy)
00285         CopyNodeContents(NodeCopy);
00286     
00287     return NodeCopy;
00288 }
00289 
00290 
00291 /********************************************************************************************
00292 >   virtual INT32 TextLine::ComplexCopy(CopyStage Stage, Range& RangeToCopy, Node** pOutput)
00293 
00294     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
00295     Created:    3/5/95
00296     Inputs:     Stage       - COPYOBJECT   if we should make a copy
00297                             - COPYFINISHED once the entire copy operation has completed
00298                 RangeToCopy - Describes the range which is currently being copied.
00299                 pOutput     - a pointer to a pointer to a node. Set this pointer to point
00300                               at the tree copy
00301     Returns:    -1  = The routine failed to make a copy.
00302                  0  = No copy has been made.
00303                 +1  = pOutput points to the copy.
00304     Purpose:    If the copystage is COPYOBJECT,
00305                 The node has been called to copy itself and do what ever it needs to to
00306                 make a sensible copy of other items such as attributes. The caller
00307                 (CopyObjects) will not deep copy this node (as this is a complex copy and
00308                 it expects the handler to know what its doing). In this case the TextLine
00309                 object cannot exist on its own. It needs a TextStory as a parent and
00310                 however many text characters that live inside it. Hence this is what is
00311                 returned.
00312     SeeAlso     Node::ComplexCopy(), CopyObjects()
00313 ********************************************************************************************/
00314 
00315 INT32 TextLine::ComplexCopy(CopyStage Stage, Range& RangeToCopy, Node** pOutput)
00316 {
00317     TextStory* pTextStory = FindParentStory();
00318     ERROR2IF(pTextStory==NULL,FALSE,"TextLine::ComplexCopy() - pTextStory==NULL");
00319     return pTextStory->BaseComplexCopy(Stage, RangeToCopy, pOutput);
00320 }
00321 
00322 
00323 /********************************************************************************************
00324 >   virtual INT32 TextLine::ComplexHide(UndoableOperation* pOp, Node* pNextNode)
00325 
00326     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
00327     Created:    3/5/95
00328     Inputs:     pOp         - a pointer to an undoable operation
00329                 pNextNode   - a pointer to the next node in range.
00330     Returns:    -1  = The routine failed to hide this node.
00331                  0  = Ignored, this object does not support complex hide operations, so
00332                       carry on and hide the node as normal.
00333                 +1  = The node and possibly various others have been hidden correctly.
00334     Purpose: Override the node level virtual function ComplexHide. This gives us a chance to
00335              get in and hide the various selected members of a text story sensibly. We
00336              hide all necessary nodes when the last member of the text story is called to
00337              complex hide itself, otherwise we may corrupt the range being scanned
00338 ********************************************************************************************/
00339 
00340 INT32 TextLine::ComplexHide(UndoableOperation* pOp, Node* pNextNode)
00341 {
00342     // if there is no next node in the range then we need to hide all nodes
00343     BOOL CallComplexHide = TRUE;
00344     TextStory* pThisStory = FindParentStory();
00345     ERROR2IF(pThisStory==NULL,FALSE,"VisibleTextNode::ComplexHide() - pThisStory==NULL");
00346 
00347     if (pNextNode)
00348     {
00349         if ( (pNextNode->IsAVisibleTextNode()) ||
00350              (IS_A(pNextNode,TextLine))
00351            )
00352         {
00353             TextStory* pTextStory = FindParentStory();
00354             ERROR2IF(pTextStory==NULL,FALSE,"VisibleTextNode::ComplexHide() - pTextStory==NULL");
00355 
00356             if (pThisStory==pTextStory)
00357                 CallComplexHide=FALSE;
00358         }
00359     }
00360 
00361     if (CallComplexHide)
00362         return pThisStory->BaseComplexHide(pOp);
00363     else
00364         return 1;
00365 }
00366 
00367 
00368 /***********************************************************************************************
00369 >   void TextLine::CopyNodeContents(TextLine* NodeCopy)
00370 
00371     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00372     Created:    28/4/93
00373     Outputs:    A copy of this node
00374     Purpose:    This method copies the node's contents to the node pointed to by NodeCopy.
00375     Errors:     An assertion failure will occur if NodeCopy is NULL
00376     Scope:      protected
00377 ***********************************************************************************************/
00378 
00379 void TextLine::CopyNodeContents(TextLine* NodeCopy)
00380 {
00381     // Ask the base class to do its bit
00382     NodeRenderableBounded::CopyNodeContents(NodeCopy);
00383 
00384     // Copy specifics
00385     NodeCopy->mLineAscent  = mLineAscent;
00386     NodeCopy->mLineDescent = mLineDescent;
00387     NodeCopy->mLineSize    = mLineSize;
00388 
00389     NodeCopy->mJustification  = mJustification;
00390     NodeCopy->mLineSpacing    = mLineSpacing;
00391     NodeCopy->mLineSpaceRatio = mLineSpaceRatio;
00392     NodeCopy->mLeftMargin = mLeftMargin;
00393     NodeCopy->mFirstIndent = mFirstIndent;
00394     NodeCopy->mRightMargin = mRightMargin;
00395     *NodeCopy->mpRuler = *mpRuler;
00396 
00397     NodeCopy->mPosInStory  = mPosInStory;
00398 }
00399 
00400 
00401 /***********************************************************************************************
00402 >   void TextLine::PolyCopyNodeContents(NodeRenderable* pNodeCopy)
00403 
00404     Author:     Phil_Martin (Xara Group Ltd) <camelotdev@xara.com>
00405     Created:    18/12/2003
00406     Outputs:    -
00407     Purpose:    Polymorphically copies the contents of this node to another
00408     Errors:     An assertion failure will occur if NodeCopy is NULL
00409     Scope:      protected
00410                                      
00411 ***********************************************************************************************/
00412 
00413 void TextLine::PolyCopyNodeContents(NodeRenderable* pNodeCopy)
00414 {
00415     ENSURE(pNodeCopy, "Trying to copy a node's contents into a NULL node");
00416     ENSURE(IS_A(pNodeCopy, TextLine), "PolyCopyNodeContents given wrong dest node type");
00417 
00418     if (IS_A(pNodeCopy, TextLine))
00419         CopyNodeContents((TextLine*)pNodeCopy);
00420 }
00421 
00422 
00423 
00424 /********************************************************************************************
00425 >   BOOL TextLine::CreateNodeGroup(NodeGroup** ppNodeGroup,
00426                                     FormatRegion* pFormatRegion, BecomeA* pBecomeA)
00427 
00428     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00429     Created:    30/3/95
00430     Inputs:     pFormatRegion - format region with current attr stack
00431                 pBecomeA      - ptr to class containing info needed to become a new type of node
00432     Outputs:    ppNodeGroup   - pointer to new node group (if not passing back)
00433     Returns:    FALSE if fails
00434     Purpose:    Creates a NodeGroup containing all chars converted to NodePaths
00435 ********************************************************************************************/
00436 
00437 BOOL TextLine::CreateNodeGroup(NodeGroup** ppNodeGroup, FormatRegion* pFormatRegion, BecomeA* pBecomeA)
00438 {
00439 #ifndef DISABLE_TEXT_RENDERING
00440     ERROR2IF(pFormatRegion==NULL,FALSE,"TextChar::CreateNodeGroup() - pFormatRegion==NULL");
00441     ERROR2IF(     pBecomeA==NULL,FALSE,"TextChar::CreateNodeGroup() - pBecomeA==NULL");
00442     ERROR2IF(  ppNodeGroup==NULL,FALSE,"TextChar::CreateNodeGroup() - ppNodeGroup==NULL");
00443 
00444     pFormatRegion->SaveContext();
00445 
00446     // here to overcome scope problem
00447     Node* pNode=NULL;
00448     TextChar* pTextChar=NULL;
00449 
00450     // if not passing back, create a NodeGroup to encompass the line, and copy non-text line attributes
00451     // BODGE - should use ALLOC_WITH_FAIL
00452     NodeGroup* pLineNodeGroup=NULL;
00453     if (pBecomeA->GetReason()!=BECOMEA_PASSBACK)
00454     {
00455         pLineNodeGroup=new NodeGroup;
00456         if (pLineNodeGroup==NULL)
00457             goto Fail;
00458     }
00459 
00460     pNode=FindFirstChild();
00461     while (pNode!=NULL)
00462     {
00463         if (pNode->IsAnAttribute())
00464         {
00465             pNode->Render(pFormatRegion);   // render attributes
00466             if (pBecomeA->GetReason()!=BECOMEA_PASSBACK)
00467                 if (!pNode->IsKindOfTextAttribute())
00468                     if (pNode->CopyNode(pLineNodeGroup, LASTCHILD)==FALSE)
00469                         goto Fail;
00470         }
00471         pNode=pNode->FindNext();
00472     }
00473 
00474     // For each child TextChar convert it to a NodePath 
00475     // then EITHER pass it back OR attach it to the line group with non-text char attributes
00476     pTextChar=(TextChar*)FindFirstChild(CC_RUNTIME_CLASS(TextChar));
00477     while (pTextChar)
00478     {
00479         pFormatRegion->SaveContext();
00480         pTextChar->RenderChildAttrs(pFormatRegion);
00481         NodePath* pCharNodePath=NULL;
00482         if (pTextChar->CreateNodePath(&pCharNodePath,pFormatRegion)==FALSE)
00483             goto Fail;
00484         if (pCharNodePath)
00485         {
00486             if (pBecomeA->GetReason()==BECOMEA_PASSBACK)
00487             {
00488                 if (pBecomeA->PassBack(pCharNodePath,pTextChar)==FALSE)
00489                     goto Fail;
00490             }
00491             else
00492             {
00493                 pCharNodePath->AttachNode(pLineNodeGroup,LASTCHILD);
00494                 Node* pNode=pTextChar->FindFirstChild();
00495                 while (pNode)
00496                 {
00497                     if (pNode->IsAnAttribute() && !pNode->IsKindOfTextAttribute())
00498                         if (pNode->CopyNode(pCharNodePath, LASTCHILD)==FALSE)
00499                             goto Fail;
00500                     pNode=pNode->FindNext();
00501                 }
00502 
00503                 pBecomeA->PassBack(pCharNodePath, pTextChar);
00504             }
00505         }
00506         pFormatRegion->RestoreContext();
00507         pTextChar=(TextChar*)(pTextChar->FindNext(CC_RUNTIME_CLASS(TextChar)));
00508     }
00509 
00510     *ppNodeGroup=pLineNodeGroup;
00511     pFormatRegion->RestoreContext();
00512     return TRUE;
00513 
00514 Fail:
00515     if (pLineNodeGroup)
00516     {
00517         pLineNodeGroup->CascadeDelete();
00518         delete pLineNodeGroup;
00519     }
00520     pFormatRegion->RestoreContext();
00521 #endif
00522     return FALSE;
00523 }
00524 
00525 
00526 /********************************************************************************************
00527 >   static TextLine* TextLine::CreateEmptyTextLine(Node* pContextNode=NULL,
00528                                                    AttachNodeDirection Direction=FIRSTCHILD)
00529 
00530     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00531     Created:    28/5/95
00532     Inputs:     pContextNode - node to attach it to (or NULL if not to be attached)
00533                 Direction    - direction in which to attach
00534     Returns:    pointer to new TextLine (or NULL if fails)
00535     Purpose:    Create a TextLine with EOL and attach it to another node if required
00536 ********************************************************************************************/
00537 
00538 TextLine* TextLine::CreateEmptyTextLine(Node* pContextNode, AttachNodeDirection Direction)
00539 {
00540     TextLine* pTextLine = new TextLine();
00541     if (pTextLine==NULL)
00542         return NULL;
00543 
00544     EOLNode* pEOL = new EOLNode(pTextLine,FIRSTCHILD);
00545     if (pEOL==NULL)
00546     {
00547         pTextLine->CascadeDelete();
00548         delete pTextLine;
00549         return NULL;
00550     }
00551 
00552     if (pContextNode!=NULL)
00553         pTextLine->AttachNode(pContextNode,Direction);
00554 
00555     return pTextLine;
00556 }
00557 
00558 
00559 /********************************************************************************************
00560 >   String TextLine::Describe(BOOL Plural, BOOL Verbose = TRUE)            
00561 
00562     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00563     Created:    21/12/94
00564     Inputs:     Plural: Singular or plural description
00565     Returns:    A string describing the node
00566     Purpose:    Gives a description of the TextLine node for the status line etc
00567 ********************************************************************************************/
00568 
00569 String TextLine::Describe(BOOL Plural, BOOL Verbose)           
00570 {
00571     if (Plural)
00572         return(String(_R(IDS_DESCRIBE_TEXTLINEP)));  
00573     else
00574         return(String(_R(IDS_DESCRIBE_TEXTLINES))); 
00575 }
00576 
00577 
00578 /*******************************************************************************************
00579     void TextLine::GetBlobBoundingRect()
00580 
00581     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00582     Created:    10/4/95
00583     Returns:    The rectangle of the document affected by the blobs on this TextLine
00584     Purpose:    This function returns the blob bounding rectangle of this TextLine.  There 
00585                 is a tiny blob on the top left of the first character on the line.
00586 ********************************************************************************************/
00587 
00588 DocRect TextLine::GetBlobBoundingRect()
00589 {
00590 #if !defined(EXCLUDE_FROM_RALPH)
00591     DocCoord BlobPos=GetTinyBlobPos();
00592     DocRect BlobBounds;
00593     BlobManager* pBlobMgr=GetApplication()->GetBlobManager();
00594     if (pBlobMgr)
00595         pBlobMgr->GetBlobRect(BlobPos,&BlobBounds);
00596     else
00597         ERROR3("TextLine::GetBlobBoundingRect() - Couldn't find the BlobManager");
00598 
00599     IncludeChildrensBoundingRects(&BlobBounds);
00600 
00601     return BlobBounds;
00602 #else
00603     return DocRect(0,0,0,0);
00604 #endif
00605 }
00606 
00607 
00608 /*******************************************************************************************
00609     DocCoord TextLine::GetTinyBlobPos()
00610 
00611     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00612     Created:    10/4/95
00613     Returns:    The position of the tiny bob on this TextLine
00614     Purpose:    This function returns the location of the tiny blob on this TextLine.  It is
00615                 either on the top-left of the first character on the line, or if there isn't
00616                 a char, on the top-left of the line bounding rect.
00617 ********************************************************************************************/
00618 
00619 DocCoord TextLine::GetTinyBlobPos()
00620 {
00621 #if !defined(EXCLUDE_FROM_RALPH)
00622     // set a fall back position - the top left of the line bounds
00623     DocRect TempRect=GetBoundingRect();
00624     DocCoord BlobPos=DocCoord(TempRect.lo.x,TempRect.hi.y);
00625 
00626     // but really we want the top left of the first char's metrics rect
00627     AbstractTextChar* pFirstChar=(AbstractTextChar*)FindFirstChild(CC_RUNTIME_CLASS(AbstractTextChar));
00628     if (pFirstChar)
00629     {
00630         if (pFirstChar->GetMetricsRect(&TempRect))
00631         {
00632             BlobPos=DocCoord(TempRect.lo.x,TempRect.hi.y);
00633             Matrix matrix;
00634             if (pFirstChar->GetStoryAndCharMatrix(&matrix))
00635                 matrix.transform(&BlobPos);
00636             else    
00637                 ERROR3("TextLine::GetTinyBlobPos() - GetStoryAndCharMatrix() failed");
00638         }
00639         else
00640             ERROR3("TextLine::GetTinyBlobPos() - GetMetricsRect() failed");
00641     }
00642 
00643     return BlobPos;
00644 #else
00645     return DocCoord(0,0);
00646 #endif
00647 }
00648 
00649 
00650 /*******************************************************************************************
00651     void TextLine::RenderTinyBlobs(RenderRegion* pRRegion)
00652 
00653     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00654     Created:    10/4/95
00655     Inputs:     pRegion - points to the RenderRegion to render into
00656     Purpose:    Renders the TextLines tiny blobs
00657 ********************************************************************************************/
00658 
00659 void TextLine::RenderTinyBlobs(RenderRegion* pRRegion)
00660 {
00661 #if !defined(EXCLUDE_FROM_RALPH)
00662     pRRegion->SetLineColour(COLOUR_NONE);
00663     pRRegion->SetFillColour(COLOUR_UNSELECTEDBLOB);
00664     pRRegion->DrawBlob(GetTinyBlobPos(), BT_UNSELECTED);
00665 #endif
00666 }
00667 
00668 
00669 /*******************************************************************************************
00670     void TextLine::RenderObjectBlobs(RenderRegion* pRRegion)
00671 
00672     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00673     Created:    10/4/95
00674     Inputs:     pRegion - points to the RenderRegion to render into
00675     Purpose:    Renders the TextLines object blobs
00676 ********************************************************************************************/
00677 
00678 void TextLine::RenderObjectBlobs(RenderRegion* pRRegion)
00679 {
00680 #if !defined(EXCLUDE_FROM_RALPH)
00681     RenderTinyBlobs(pRRegion);
00682 #endif
00683 }
00684 
00685 
00686 /********************************************************************************************
00687 >   UINT32 TextLine::GetNodeSize() const 
00688 
00689     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00690     Created:    21/12/94
00691     Returns:    The size of the node in bytes
00692     Purpose:    For finding the size of the node 
00693     SeeAlso:    Node::GetSubtreeSize
00694 ********************************************************************************************/
00695 
00696 UINT32 TextLine::GetNodeSize() const 
00697 {
00698     return (sizeof(TextLine)); 
00699 }
00700 
00701 
00702 /********************************************************************************************
00703 >   void TextLine::GetDebugDetails(StringBase* Str) 
00704 
00705     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00706     Created:    1/2/95
00707     Outputs:    Str: String giving debug info about the node
00708     Purpose:    For obtaining debug information about the Node. 
00709                 This fn can be deleted before we ship 
00710 ********************************************************************************************/
00711 
00712 void TextLine::GetDebugDetails(StringBase* Str)
00713 {
00714     NodeRenderableBounded::GetDebugDetails(Str); 
00715 }
00716 
00717 
00718 /********************************************************************************************
00719 >   virtual BOOL TextLine::ReCacheMetrics(FormatRegion* pFormatRegion)
00720 
00721     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00722     Created:    2/2/96
00723     Inputs:     pFormatRegion - 
00724     Returns:    FALSE if fails
00725     Purpose:    Recache metrics in text line
00726 ********************************************************************************************/
00727 
00728 BOOL TextLine::ReCacheMetrics(FormatRegion* pFormatRegion)
00729 {
00730     TRACEUSER("wuerthne", _T("TextLine::ReCacheMetrics"));
00731     SetJustification( pFormatRegion->GetJustification());
00732     SetLineSpacing(   pFormatRegion->GetLineSpacing());
00733     SetLineSpaceRatio(pFormatRegion->GetLineSpaceRatio());
00734 
00735     SetParaLeftMargin(pFormatRegion->GetLeftMargin());
00736     SetParaFirstIndent(pFormatRegion->GetFirstIndent());
00737     SetParaRightMargin(pFormatRegion->GetRightMargin());
00738     SetRuler(pFormatRegion->GetRuler());
00739 #if defined(_DEBUG) && 0
00740     String_256 TempStr;
00741     String Str(_T(" "));
00742     for (TxtTabStopIterator It = mpRuler->begin(); It != mpRuler->end(); ++It)
00743     {
00744         switch((*It).GetType())
00745         {
00746             case LeftTab:
00747                 TempStr._MakeMsg( TEXT("L(#1%ld)"), (*It).GetPosition());
00748                 Str += TempStr;
00749                 break;
00750             case RightTab:
00751                 TempStr._MakeMsg( TEXT("R(#1%ld)"), (*It).GetPosition());
00752                 Str += TempStr;
00753                 break;
00754             case CentreTab:
00755                 TempStr._MakeMsg( TEXT("C(#1%ld)"), (*It).GetPosition());
00756                 Str += TempStr;
00757                 break;
00758             case DecimalTab:
00759                 TempStr._MakeMsg( TEXT("D(#1%ld)"), (*It).GetPosition());
00760                 Str += TempStr;
00761                 break;
00762         }
00763     }
00764     TRACEUSER("wuerthne", _T("ruler at %08x:%s"), mpRuler, (TCHAR*)Str);
00765 #endif
00766     return TRUE;
00767 }
00768 
00769 /********************************************************************************************
00770 >   MILLIPOINT TextLine::GetEffectiveLeftMargin()
00771 
00772     Author:     Martin Wuerthner <xara@mw-software.com>
00773     Created:    18/07/06
00774     Inputs:     -
00775     Returns:    the left margin value to use
00776     Purpose:    Select the left margin or first indent value depending on whether this is
00777                 the first line in the paragraph
00778 ********************************************************************************************/
00779 
00780 MILLIPOINT TextLine::GetEffectiveLeftMargin()
00781 {
00782     // we need to find out whether this is the first line in the paragraph, so we know
00783     // whether we need to use FirstLineIndent or LeftMargin
00784 
00785     TextLine* pPrevLine = FindPrevLine();
00786     if (pPrevLine==NULL || pPrevLine->FindEOLNode() != NULL)
00787     {
00788         // first line in paragraph
00789         return mFirstIndent;
00790     }
00791     else
00792     {
00793         return mLeftMargin;
00794     }
00795 }
00796 
00797 /********************************************************************************************
00798 >   BOOL TextLine::Format(TextStoryInfo* pStoryInfo)
00799 
00800     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00801     Created:    24/3/95
00802     Inputs:     pStoryInfo - current story level info
00803     Returns:    FALSE if fails
00804     Purpose:    perform line level formatting
00805     Note:       on entry pStoryInfo->DescentLine hold the DescentLine of the last line
00806                 (pStoryInfo->DescentLineValid indicating if there was none (eg first line)
00807                 and on exit, it holds the DescentLine for this line and DescentLineValid is TRUE
00808 ********************************************************************************************/
00809 
00810 BOOL TextLine::Format(TextStoryInfo* pStoryInfo)
00811 {
00812     ERROR2IF(pStoryInfo==NULL,FALSE,"TextLine::Format() - pStoryInfo == NULL");
00813 
00814     TRACEUSER("wuerthne", _T("TextLine::Format"));
00815     MILLIPOINT PhysicalLeftMargin   = pStoryInfo->LeftPathIndent;   // 0 if not on path
00816     MILLIPOINT PhysicalRightMargin  = pStoryInfo->StoryWidth - pStoryInfo->RightPathIndent;
00817     BOOL       WordWrapping = pStoryInfo->WordWrapping;
00818     MILLIPOINT RightMargin = PhysicalRightMargin - mRightMargin;
00819     MILLIPOINT LeftIndent = GetEffectiveLeftMargin();
00820 
00821     // if word wrapping, and not text at a point, and undoably 'do'ing op, word wrap the line
00822     MILLIPOINT WrapWidth = 0;
00823     if (WordWrapping)
00824         WrapWidth = RightMargin - PhysicalLeftMargin;   // will be 0 if text at a point
00825     if (WrapWidth!=0 && pStoryInfo->WordWrap)
00826     {
00827         if (!this->Wrap(pStoryInfo->pUndoOp, WrapWidth, LeftIndent))
00828             return FALSE;
00829     }
00830     else if (WrapWidth != 0)
00831     {
00832         // when called during undo (i.e., WordWrap = FALSE) and the story is word wrapping,
00833         // we do not want to wrap, but we still need to make sure that each tab gets its width
00834         // set correctly, otherwise PositionCharsInLine will not do the right thing
00835         FindBreakChar(WrapWidth, FALSE, LeftIndent);
00836     }
00837 
00838     // if line affected in any way, reposition chars in line
00839     if (NodeOrDescendantAffected())
00840     {
00841         TextLineInfo LineInfo;
00842         if (ReCalcLineInfo(&LineInfo)==FALSE)
00843             return FALSE;
00844 
00845         LineInfo.LeftMargin    = PhysicalLeftMargin;
00846         LineInfo.RightMargin   = PhysicalRightMargin;
00847         LineInfo.WordWrapping  = WordWrapping;
00848         if (PositionCharsInLine(&LineInfo)==FALSE)
00849             return FALSE;
00850     }
00851 
00852     // calculate this line's BaseLine, and keep the DescentLine up to date
00853     MILLIPOINT BaseLine    = 0;
00854     MILLIPOINT DescentLine = 0;
00855     if (CalcBaseAndDescentLine(&BaseLine,&DescentLine,pStoryInfo->DescentLine,pStoryInfo->DescentLineValid)==FALSE)
00856         return FALSE;
00857     pStoryInfo->DescentLine      = DescentLine;
00858     pStoryInfo->DescentLineValid = TRUE;
00859 
00860     // if the line has moved, set its' new position and flag it is 'affected'
00861     if (BaseLine!=GetPosInStory())
00862     {
00863         SetPosInStory(BaseLine);
00864         FlagNodeAndDescendantsAffectedAndParentsHaveDescendantAffected();
00865     }
00866 
00867     // if line or descendent 'Affected' recalc matrices for affected children
00868     BOOL ok=TRUE;
00869     if (NodeOrDescendantAffected())
00870     {
00871         if (pStoryInfo->pPath==NULL)
00872             ok = SetCharMatrices(GetPosInStory());
00873         else
00874             ok = FitTextToPath(pStoryInfo,GetPosInStory());
00875     }
00876 
00877     return ok;
00878 }
00879 
00880 
00881 /********************************************************************************************
00882 >   BOOL TextLine::EnsureNextLineOfParagraphHasSameLineLevelAttrs(UndoableOperation* pUndoOp)
00883 
00884     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00885     Created:    16/7/96
00886     Inputs:     pUndoOp - 
00887     Returns:    FALSE if fails
00888     Purpose:    Ensure the next line of a paragraph has the same line level attibutes
00889 ********************************************************************************************/
00890 
00891 BOOL TextLine::EnsureNextLineOfParagraphHasSameLineLevelAttrs(UndoableOperation* pUndoOp)
00892 {
00893     TRACEUSER("wuerthne", _T("EnsureNextLineOfParagraphHasSameLineLevelAttrs"));
00894 
00895     ERROR2IF(FindEOLNode()!=NULL,FALSE,"TextLine::EnsureNextOfParagraphLineHasSameLineLevelAttrs() - there is no next line in the paragraph!");
00896     TextStory* pStory = this->FindParentStory();
00897     ERROR2IF(pStory==NULL,FALSE,"TextLine::EnsureNextOfParagraphLineHasSameLineLevelAttrs() - line has no parent story!");
00898     TextLine* pNextLine = this->FindNextLine();
00899     ERROR2IF(pNextLine==NULL,FALSE,"TextLine::EnsureNextOfParagraphLineHasSameLineLevelAttrs() - last line of story has no EOL!");
00900 
00901     // flag story not localised, and get the set of line level attrs to localise
00902     BOOL StoryLocalised = FALSE;
00903     AttrTypeSet LLASet;
00904     BOOL    ok = this->AddChildLineLevelAttrsToSet(&LLASet);
00905     if (ok) ok = pNextLine->AddChildLineLevelAttrsToSet(&LLASet);
00906     if (!ok) return FALSE;
00907 
00908     // hide any Line Level Attributes found on the next line
00909     // which are different to those on this line or do not exist on this line
00910     Node* pNextLineNode = pNextLine->FindFirstChild();
00911     while (pNextLineNode!=NULL)
00912     {
00913         if (pNextLineNode->IsAnAttribute() && ((NodeAttribute*)pNextLineNode)->IsALineLevelAttrib())
00914         {
00915             NodeAttribute* pNextLineLLA = (NodeAttribute*)pNextLineNode;
00916             NodeAttribute* pThisLineLLA = (NodeAttribute*)(this->FindFirstChild(pNextLineLLA->GetAttributeType()));
00917             if (pThisLineLLA==NULL || !((*pNextLineLLA)==(*pThisLineLLA)) )
00918             {
00919                 if (!StoryLocalised)
00920                     if (!pStory->DoLocaliseCommonAttributes(pUndoOp,FALSE,TRUE,&LLASet))
00921                         return FALSE;
00922                 StoryLocalised = TRUE;
00923                 if (pUndoOp!=NULL)
00924                 {
00925                     if (!pUndoOp->DoHideNode(pNextLineLLA,FALSE))
00926                         return FALSE;
00927                 }
00928                 else
00929                 {
00930                     pNextLineLLA->CascadeDelete();
00931                     delete pNextLineLLA;
00932                 }
00933             }
00934         }
00935         pNextLineNode = pNextLineNode->FindNext();
00936     }
00937 
00938     // hide any Line Level Attributes on next line which are different to those found on this line
00939     // then copy any Line Level Attributes from this line to the next
00940     // which don't exist (or have just been hiden) on the next line
00941     Node* pThisLineNode = this->FindFirstChild();
00942     while (pThisLineNode!=NULL)
00943     {
00944         if (pThisLineNode->IsAnAttribute() && ((NodeAttribute*)pThisLineNode)->IsALineLevelAttrib())
00945         {
00946             NodeAttribute* pThisLineLLA = (NodeAttribute*)pThisLineNode;
00947             NodeAttribute* pNextLineLLA = (NodeAttribute*)(pNextLine->FindFirstChild(pThisLineLLA->GetAttributeType()));
00948 
00949             if (pNextLineLLA!=NULL && !((*pNextLineLLA)==(*pThisLineLLA)) )
00950             {
00951                 if (!StoryLocalised)
00952                     if (!pStory->DoLocaliseCommonAttributes(pUndoOp,FALSE,TRUE,&LLASet))
00953                         return FALSE;
00954                 StoryLocalised = TRUE;
00955                 if (pUndoOp!=NULL)
00956                 {
00957                     if (!pUndoOp->DoHideNode(pNextLineLLA,FALSE))
00958                         return FALSE;
00959                 }
00960                 else
00961                 {
00962                     pNextLineLLA->CascadeDelete();
00963                     delete pNextLineLLA;
00964                 }
00965                 pNextLineLLA = NULL;    // flag its been hidden
00966             }
00967 
00968             if (pNextLineLLA==NULL)
00969             {
00970                 if (!StoryLocalised)
00971                     if (!pStory->DoLocaliseCommonAttributes(pUndoOp,FALSE,TRUE,&LLASet))
00972                         return FALSE;
00973                 StoryLocalised = TRUE;
00974                 TRACEUSER("wuerthne", _T("calling SimpleCopy for att %d %d"), IS_A(pThisLineLLA, AttrTxtLineSpace), IS_A(pThisLineLLA, AttrTxtRuler));
00975                 NodeAttribute* pNewLLA = (NodeAttribute*)pThisLineLLA->SimpleCopy();
00976                 TRACEUSER("wuerthne", _T("node copied %d %d"), IS_A(pNewLLA, AttrTxtLineSpace), IS_A(pNewLLA, AttrTxtRuler));
00977                 if (pNewLLA==NULL)
00978                     return FALSE;
00979                 pNewLLA->AttachNode(pNextLine,FIRSTCHILD,TRUE,FALSE); 
00980                 if (pUndoOp!=NULL)
00981                 {
00982                     Action* UndoHideAttrAction;     
00983                     if (HideNodeAction::Init(pUndoOp,pUndoOp->GetUndoActionList(),pNewLLA,TRUE,&UndoHideAttrAction)==AC_FAIL)
00984                         return FALSE; 
00985                 }
00986             }
00987         }
00988         pThisLineNode = pThisLineNode->FindNext();
00989     }
00990 
00991     // if attrs localised on next line, factor out (globally) and flag line affected
00992     if (StoryLocalised)
00993     {
00994         if (!pStory->DoFactorOutCommonChildAttributes(pUndoOp,TRUE,&LLASet))
00995             return FALSE;
00996         pNextLine->FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp();
00997     }
00998 
00999     return TRUE;
01000 }
01001 
01002 
01003 /********************************************************************************************
01004 >   TextLine* TextLine::FindFirstLineOfParagraph()
01005 
01006     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01007     Created:    17/7/96
01008     Returns:    ptr to first line of paragraph in which this line is to be found
01009                 (or NULL if error)
01010 ********************************************************************************************/
01011 
01012 TextLine* TextLine::FindFirstLineOfParagraph()
01013 {
01014     TextLine* pLine = this;
01015     while (1)
01016     {
01017         TextLine* pPrevLine = pLine->FindPrevLine();
01018         if (pPrevLine==NULL || pPrevLine->FindEOLNode()!=NULL)
01019             break;
01020         pLine = pPrevLine;
01021     }
01022     return pLine;
01023 }
01024 
01025 
01026 /********************************************************************************************
01027 >   static BOOL TextLine::IsAttrTypeLineLevel(CCRuntimeClass* pAttrType)
01028 
01029     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01030     Created:    17/7/96
01031     Inputs:     pAttrType - 
01032     Returns:    TRUE if attr type is a line level attribute
01033 ********************************************************************************************/
01034 
01035 BOOL TextLine::IsAttrTypeLineLevel(CCRuntimeClass* pAttrType)
01036 {
01037     return pAttrType==CC_RUNTIME_CLASS(AttrTxtJustification)
01038         || pAttrType==CC_RUNTIME_CLASS(AttrTxtLineSpace)
01039         || pAttrType==CC_RUNTIME_CLASS(AttrTxtLeftMargin)
01040         || pAttrType==CC_RUNTIME_CLASS(AttrTxtRightMargin)
01041         || pAttrType==CC_RUNTIME_CLASS(AttrTxtFirstIndent)
01042         || pAttrType==CC_RUNTIME_CLASS(AttrTxtRuler);
01043 }
01044 
01045 
01046 /********************************************************************************************
01047 >   BOOL TextLine::AddChildLineLevelAttrsToSet(AttrTypeSet* pAttrTypeSet)
01048 
01049     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01050     Created:    18/7/96
01051     Inputs:     pAttrTypeSet - 
01052     Outputs:    pAttrTypeSet - 
01053     Returns:    FALSE if fails
01054 ********************************************************************************************/
01055 
01056 BOOL TextLine::AddChildLineLevelAttrsToSet(AttrTypeSet* pAttrTypeSet)
01057 {
01058     Node* pNode = FindFirstChild();
01059     while (pNode!=NULL)
01060     {
01061         if (pNode->IsAnAttribute() && ((NodeAttribute*)pNode)->IsALineLevelAttrib())
01062             if (!pAttrTypeSet->AddToSet(((NodeAttribute*)pNode)->GetAttributeType()))
01063                 return FALSE;
01064         pNode = pNode->FindNext();
01065     }
01066     return TRUE;
01067 }
01068 
01069 /********************************************************************************************
01070 >   BOOL FormatState::FinishTabSection(VisibleTextNode* pLastFormattedNode, BOOL IsLastTabSection)
01071 
01072     Author:     Martin Wuerthner <xara@mw-software.com>         
01073     Created:    23/06/06
01074     Inputs:     pLastFormattedNode - the most recently fitted node
01075                 IsLastTabSection - TRUE if this section has been terminated by EOL or a
01076                 line break (i.e., not by a Tab), which means we should add
01077                 ExtraSpaceOnChars/Space here)
01078     Returns:    ptr to char to break at (or NULL if ERROR)
01079     Purpose:    This routine is called when a tab or the end of the line is encountered,
01080                 so the text after the previous tab (or the start of the line) can be
01081                 finally formatted.
01082                 The Width/Advance of the Tab is always set and if SetCharPositions is TRUE,
01083                 then all the character positions are set.
01084 ********************************************************************************************/
01085 
01086 BOOL FormatState::FinishTabSection(VisibleTextNode* pLastFormattedNode, BOOL IsLastTabSection)
01087 {
01088     // first, set the Tab character's width - this is only necessary for non-Left tabs
01089     // (for left tabs we have already set it when encountering the tab)
01090     if (ActiveTabType != LeftTab)
01091     {
01092         // pLastTabVTN can only be undefined for ActiveTabType == LeftTab
01093         ((HorizontalTab*)pLastTabVTN)->SetCharWidth(RemainingSpace);
01094         ((HorizontalTab*)pLastTabVTN)->SetCharAdvance(RemainingSpace);
01095     }
01096     if (!SetCharPositions) return TRUE;
01097 
01098     // full formatting desired
01099     VisibleTextNode* pVTN = pLastTabVTN ? pLastTabVTN : pFirstVTN;
01100     MILLIPOINT Pos = AnchorPos;
01101     do
01102     {
01103         ERROR2IF(pVTN == NULL, FALSE, "FinishTabSection - unexpected end of line");
01104         if (Pos + CharPosOffset != pVTN->GetPosInLine())
01105         {
01106             pVTN->SetPosInLine(Pos + CharPosOffset);
01107             pVTN->FlagNodeAndDescendantsAffectedAndParentsHaveDescendantAffected(); 
01108         }
01109         Pos += pVTN->GetCharAdvance();
01110         
01111         if (IsLastTabSection && IS_A(pVTN,TextChar))
01112             Pos += ExtraOnChars;
01113         if (IsLastTabSection && pVTN->IsASpace())
01114             Pos += ExtraOnSpaces;
01115         if (pVTN == pLastFormattedNode) break;
01116         pVTN = pVTN->FindNextVTNInLine();
01117     } while(1);
01118     return TRUE;
01119 }
01120 
01121 /********************************************************************************************
01122 >   void FormatState::AdvanceBy(MILLIPOINT CharWidth)
01123 
01124     Author:     Martin Wuerthner <xara@mw-software.com>         
01125     Created:    23/06/06
01126     Inputs:     CharWidth - the amount to advance by
01127                 IsADecimalPoint - TRUE if the currently active tab is a decimal tab and the
01128                                   character to be formatted is its decimal point character
01129     Purpose:    Account for a char with width CharWidth to be fitted into the current line.
01130     See also:   TextLine::FindBreakChar for a description of the formatting algorithm used
01131 ********************************************************************************************/
01132 
01133 void FormatState::AdvanceBy(MILLIPOINT CharWidth, BOOL IsADecimalPoint)
01134 {
01135     // Note: This routine may only update Width and RemainingSpace, otherwise
01136     // the backtracking in IsAvailable() does not work as expected.
01137     if (ActiveTabType == CentreTab && RemainingSpace > 0)
01138     {
01139         MILLIPOINT SpaceToLeftUsed = 0;
01140         // half the width is distributed into RemainingSpace, if it is big enough
01141         if (RemainingSpace >= CharWidth / 2)
01142         {
01143             // half the width fully fits into RemainingSpace
01144             SpaceToLeftUsed = CharWidth / 2;
01145         }
01146         else
01147         {
01148             // does not fit, so use up all of RemainingSpace
01149             SpaceToLeftUsed = RemainingSpace;
01150         }
01151         // distribute some of the character to the left
01152         RemainingSpace -= SpaceToLeftUsed;
01153         // and the remainder to the right
01154         Width += CharWidth - SpaceToLeftUsed;
01155     }
01156     else if ((ActiveTabType == RightTab || (ActiveTabType == DecimalTab && !DecimalPointFound
01157                                             && !IsADecimalPoint))
01158              && RemainingSpace > 0)
01159     {
01160         if (RemainingSpace >= CharWidth) RemainingSpace -= CharWidth;
01161         else
01162         {
01163             Width += CharWidth - RemainingSpace;
01164             RemainingSpace = 0;
01165         }
01166     }
01167     else
01168     {
01169         // We end up here if:
01170         // - the last tab was left tab, or
01171         // - the last tab was a decimal tab and we have already found the decimal point, or
01172         // - the last tab was a decimal tab and this is a decimal point character (NB. - the
01173         //   decimal point is always formatted to the right of the tab position), or, finally,
01174         // - this is any kind of tab and we have no more remaining space to the left of it
01175         // In all these cases, we simply format the character to the right of the current position,
01176         // which is the easiest thing to do anyway
01177         Width += CharWidth;
01178     }
01179 }
01180 
01181 /********************************************************************************************
01182 >   BOOL FormatState::IsAvailable(MILLIPOINT CharWidth, BOOL IsATab)
01183 
01184     Author:     Martin Wuerthner <xara@mw-software.com>         
01185     Created:    23/06/06
01186     Inputs:     CharWidth - the amount to advance by
01187                 IsATab - TRUE if the character to be fitted is a tab
01188                 IsADecimalPoint - TRUE if the currently active tab is a decimal tab and the
01189                                   character to be formatted is its decimal point character
01190     Purpose:    Test whether a char with width CharWidth can be fitted into the current line.
01191 ********************************************************************************************/
01192 
01193 BOOL FormatState::IsAvailable(MILLIPOINT CharWidth, BOOL IsATab, BOOL IsADecimalPoint)
01194 {
01195     // If the full CharWidth still fits, then we can return TRUE straight away
01196     // without having to look at the active tab type
01197     if (Width + CharWidth < FitWidth) return TRUE;
01198     // If the above did not succeed there is no hope in the standard case (last
01199     // tab was left tab or the beginning of the line) - the same is true for a
01200     // tab character because it will start a new tab section, so none of its width
01201     // can go to the left of the current formatting position
01202     if (ActiveTabType == LeftTab || IsATab) return FALSE;
01203 
01204     // For other tab types, things are a bit more complicated because half or
01205     // all of the character can go to the left of the tab stop. Rather than
01206     // duplicating all the AdvanceBy code, we simply advance and backtrack.
01207     MILLIPOINT OldWidth = Width;
01208     MILLIPOINT OldRemainingSpace = RemainingSpace;
01209     AdvanceBy(CharWidth, IsADecimalPoint);
01210     MILLIPOINT NewWidth = Width;
01211     Width = OldWidth;
01212     RemainingSpace = OldRemainingSpace;
01213     return NewWidth < FitWidth;
01214 }
01215 
01216 /********************************************************************************************
01217 >   VisibleTextNode* TextLine::FindBreakChar(MILLIPOINT FitWidth, BOOL SetCharPositions,
01218                                          MILLIPOINT Indent, MILLIPOINT CharPosOffset,
01219                                          MILLIPOINT ExtraOnChars, MILLIPOINT ExtraOnSpaces)
01220 
01221     Author:     Martin Wuerthner <xara@mw-software.com>
01222                 (based on routine by Ed_Cornes <camelotdev@xara.com> created 15/7/96)
01223     Created:    23/06/06
01224     Inputs:     FitWidth - the overall width available
01225                 SetCharPositions - TRUE if character positions should be set
01226                 Indent = starting position of first character (logical, counts as far as tab
01227                          stop positions are concerned)
01228                 CharPosOffset = positional offset to be added to each character, transparent
01229                          as far as tab stop positions are concerned
01230                 ExtraOnChars, ExtraOnSpaces - see PositionCharsInLine
01231                 The last four are only used if SetCharPositions = TRUE
01232     Returns:    ptr to char to break at (or NULL if ERROR)
01233     Purpose:    Find words to fit given width, absorbing spaces, optionally formats line
01234 ********************************************************************************************/
01235 
01236 VisibleTextNode* TextLine::FindBreakChar(MILLIPOINT FitWidth, BOOL SetCharPositions,
01237                                          MILLIPOINT Indent, MILLIPOINT CharPosOffset,
01238                                          MILLIPOINT ExtraOnChars, MILLIPOINT ExtraOnSpaces)
01239 {
01240     // This routine does not just find the break char (SetCharPositions = FALSE), it
01241     // also formats lines by setting the character positions (SetCharPositions = TRUE).
01242     // With the introduction of Tabs, formatting has become more complex, so it makes
01243     // a lot of sense to have 
01244     // depending on whether 
01245     // Finding the break char is straightforward when no tabs are involved.
01246     // You just add up the widths and remember the last space character and
01247     // return that when the available width has been exceeded.
01248     //
01249     // With tabs, things become a bit more complex. Left tabs are easy: A left tab
01250     // is treated like a character with the width of the remaining space up to the
01251     // tab stop. When dealing with centre or right tabs we need to remember how much
01252     // space we had left to the left of the tab stop. Half (for centre tabs) or all
01253     // (for right tabs) of the width of all subsequent text goes to the left until
01254     // that space is filled up, all remaining space goes to the right only.
01255     // Decimal tabs behave like centre tabs except that distributing half the width
01256     // to the left stops as soon as the decimal point has been seen.
01257     //
01258     // The formatting algorithm below divides the line into sections delimited by
01259     // tabs or end of line. When a section is finished (i.e., when seeing a tab or
01260     // end of line), then the complete section is formatted (using FinishTabSection()).
01261 
01262     TRACEUSER("wuerthne", _T("FindBreakChar, SetCharPositions = %d"), SetCharPositions);
01263     VisibleTextNode* pBreakChar = NULL;
01264     BOOL             CharOnLine = FALSE;
01265     VisibleTextNode* pPrevVTN   = NULL;
01266     VisibleTextNode* pVTN       = this->FindFirstVTN();
01267 
01268     // create a FormatState object that takes care of the formatting
01269     FormatState State(FitWidth, SetCharPositions, pVTN, Indent, CharPosOffset, ExtraOnChars, ExtraOnSpaces);
01270 
01271     while(pVTN!=NULL)
01272     {
01273         BOOL IsATab = IS_A(pVTN, HorizontalTab);
01274         BOOL IsADecimalPoint = FALSE;
01275         if (State.ActiveTabType == DecimalTab && pVTN->IsATextChar())
01276         {
01277             // if the currently active tab is a decimal tab check whether we have hit a decimal point
01278             // NB. - the decimal point character is stored in the tab stop
01279             IsADecimalPoint = (((TextChar*)pVTN)->GetUnicodeValue() == State.DecimalPointChar);
01280         }
01281         if (IsATab)
01282         {
01283             TxtTabType ThisTabType;
01284             MILLIPOINT ThisTabPos;
01285             MILLIPOINT TabWidth;
01286 
01287             // finish the previous tab
01288             State.FinishTabSection(pVTN, FALSE);
01289 
01290             // find the next tab stop (always returns usable values - if there are no more
01291             // tab stops on the ruler the routine assumes left tabs at equidistant positions)
01292             WCHAR DecimalPointChar;
01293             TxtRulerAttribute::FindTabStopInRuler(mpRuler, State.Width, &ThisTabType, &ThisTabPos,
01294                                                   &DecimalPointChar);
01295             TabWidth = ThisTabPos - State.Width;
01296             ((HorizontalTab*)pVTN)->SetCharAdvance(TabWidth);
01297             ((HorizontalTab*)pVTN)->SetCharWidth(TabWidth);
01298             State.AnchorPos = State.Width;
01299             if (ThisTabType != LeftTab)
01300             {
01301                 // Space between the tab character and the stop it aligns to
01302                 State.RemainingSpace = TabWidth;
01303             }
01304             if (ThisTabType == DecimalTab)
01305             {
01306                 State.DecimalPointChar = DecimalPointChar;
01307                 State.DecimalPointFound = FALSE;
01308             }
01309             State.pLastTabVTN = pVTN;
01310             State.ActiveTabType = ThisTabType;
01311             State.ActiveTabPos  = ThisTabPos;
01312             // we allow line breaks at tabs
01313             pBreakChar  = pPrevVTN;
01314         }
01315         if (!pVTN->IsACaret())
01316         {
01317             if (pVTN->IsAnEOLNode())
01318             {
01319                 State.FinishTabSection(pVTN, TRUE);
01320                 return pVTN;
01321             }
01322             else if (pVTN->IsASpace())
01323             {
01324                 State.AdvanceBy(pVTN->GetCharAdvance(), IsADecimalPoint);
01325                 pBreakChar  = pVTN;
01326             }
01327             else if (!CharOnLine || State.IsAvailable(pVTN->GetCharWidth(), IsATab, IsADecimalPoint))
01328             {
01329                 if (IsATab) State.Width += pVTN->GetCharAdvance();
01330                 else State.AdvanceBy(pVTN->GetCharAdvance(), IsADecimalPoint);
01331                 if (pVTN->IsAHyphen())
01332                     pBreakChar = pVTN;
01333                 if (State.ActiveTabType == DecimalTab && !State.DecimalPointFound && IsADecimalPoint)
01334                     State.DecimalPointFound = TRUE;
01335             }
01336             else
01337             {
01338                 // did not fit
01339                 if (pBreakChar==NULL)
01340                     pBreakChar = pPrevVTN;
01341                 if (pBreakChar) State.FinishTabSection(pBreakChar, TRUE);
01342                 return pBreakChar;
01343             }
01344             CharOnLine = TRUE;
01345         }
01346         pPrevVTN = pVTN;
01347         if (SetCharPositions)
01348         {
01349             // formatting run - we stay within our line and finish when we run past the end
01350             pVTN = pVTN->FindNextVTNInLine();
01351             if (!pVTN)
01352             {
01353                 State.FinishTabSection(pPrevVTN, TRUE);
01354                 return NULL;
01355             }
01356         }
01357         else
01358         {
01359             pVTN = pVTN->FindNextVTNInStory();
01360         }
01361     }
01362     ERROR2(FALSE,"FindBreakChar() - story has no final EOL!");
01363 }
01364 
01365 
01366 /********************************************************************************************
01367 >   BOOL TextLine::Wrap(UndoableOperation* pUndoOp, MILLIPOINT WrapWidth)
01368 
01369     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01370     Created:    15/7/96
01371     Inputs:     pUndoOp   - 
01372                 WrapWidth - width into which to format
01373                 LeftIndent - the left indent
01374     Returns:    FALSE if fails
01375     Purpose:    Word wrap the line
01376 ********************************************************************************************/
01377 
01378 BOOL TextLine::Wrap(UndoableOperation* pUndoOp, MILLIPOINT WrapWidth, MILLIPOINT Indent)
01379 {
01380     VisibleTextNode* pBreakChar = this->FindBreakChar(WrapWidth, FALSE, Indent);
01381     if (pBreakChar==NULL)
01382         return FALSE;
01383 
01384     // if break char on this line, wrap the rest of the line forward
01385     // else wrap all lines and chars upto the break char back on this line
01386     TextLine* pBreakLine = pBreakChar->FindParentLine();
01387     ERROR2IF(pBreakLine==NULL,FALSE,"TextLine::Wrap() - break char has no parent line");
01388     if (pBreakLine==this)
01389     {
01390         VisibleTextNode* pAfterBreak = pBreakChar->FindNextVTNInLine();
01391         if (pAfterBreak!=NULL)
01392             if (!pAfterBreak->WrapRestOfLineForward(pUndoOp))
01393                 return FALSE;
01394     }
01395     else
01396     {
01397         TextLine* pNextLine = this->FindNextLine();
01398         while (pNextLine!=pBreakLine)
01399         {
01400             ERROR2IF(pNextLine==NULL,FALSE,"TextLine::Wrap() - did not find line with break char!");
01401             VisibleTextNode* pLastVTN = pNextLine->FindLastVTN();
01402             ERROR2IF(pLastVTN==NULL,FALSE,"TextLine::Wrap() - no VTN on line!");
01403             if (!pLastVTN->WrapFromStartOfLineBack(pUndoOp))
01404                 return FALSE;
01405             pNextLine = this->FindNextLine();
01406         }
01407         if (!pBreakChar->WrapFromStartOfLineBack(pUndoOp))
01408             return FALSE;
01409     }
01410 
01411     return TRUE;
01412 }
01413 
01414 
01415 /********************************************************************************************
01416 >   BOOL TextLine::ReCalcLineInfo(TextLineInfo* pLineInfo)
01417 
01418     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01419     Created:    4/2/96
01420     Outputs:    pLineInfo - line level info
01421     Returns:    FALSE if fails
01422     Purpose:    Recalculate line level info (absorbing space at end of line)
01423     Note:       Also updates line level info in node itself
01424 ********************************************************************************************/
01425 
01426 BOOL TextLine::ReCalcLineInfo(TextLineInfo* pLineInfo)
01427 {
01428     ERROR2IF(pLineInfo==NULL,FALSE,"TextLine::ReCalcLineInfo() - pLineInfo==NULL");
01429 
01430     TRACEUSER("wuerthne", _T("RecalcLineInfo"));
01431     // reset info in node to be calculated
01432     SetLineAscent(0);
01433     SetLineDescent(0);
01434     SetLineSize(0);
01435 
01436     // init local info to be calculated
01437     MILLIPOINT SumCharAdvances = 0;
01438     INT32       NumChars        = 0;
01439     INT32       NumSpaces       = 0;
01440     MILLIPOINT SumCharAdvancesToLastNonSpace = 0;
01441     INT32       NumCharsToLastNonSpace        = 0;
01442     INT32       NumSpacesToLastNonSpace       = 0;
01443 
01444     // for each AbstractTextChar on line ...
01445     BOOL AbstractTextCharFound = FALSE;
01446     Node* pNode = FindFirstChild();
01447     ERROR3IF(pNode==NULL,"TextLine::ReCalcLineInfo() - line has no children!");
01448     while (pNode!=NULL)
01449     {
01450         if (pNode->IsAnAbstractTextChar())
01451         {
01452             AbstractTextChar* pATC= (AbstractTextChar*)pNode;
01453 
01454             // update info in TextLine (but only use EOL if no chars on line)
01455             if (!pATC->IsAnEOLNode() || !AbstractTextCharFound)
01456             {
01457                 UpdateLineAscent( pATC->GetFontAscent());
01458                 UpdateLineDescent(pATC->GetFontDescent());
01459                 UpdateLineSize(   pATC->GetFontSize());
01460             }
01461 
01462             // update local info
01463             if (IS_A(pATC,TextChar))
01464                 NumChars  += 1;
01465             if (IS_A(pATC, HorizontalTab))
01466             {
01467                 // only spaces/chars after the last tab can be stretched for full justification
01468                 TRACEUSER("wuerthne", _T("tab at %08x width=%d"), pATC, pATC->GetCharAdvance());
01469                 NumSpaces = 0;
01470                 NumChars = 0;
01471             }
01472             if (pATC->IsASpace())
01473                 NumSpaces += 1;
01474             else if (!pATC->IsAnEOLNode())
01475             {
01476                 NumCharsToLastNonSpace        = NumChars;
01477                 NumSpacesToLastNonSpace       = NumSpaces;
01478                 SumCharAdvancesToLastNonSpace = SumCharAdvances + pATC->GetCharWidth(); // exc tracking of this char
01479             }
01480             SumCharAdvances += pATC->GetCharAdvance();
01481 
01482             AbstractTextCharFound = TRUE;
01483         }
01484         pNode = pNode->FindNext();
01485     }
01486     ERROR2IF(!AbstractTextCharFound,FALSE,"TextLine::ReCalcLineInfo() - line has no AbstractTextChars");
01487 
01488     // set outputs
01489     pLineInfo->SumCharAdvances = SumCharAdvancesToLastNonSpace;
01490     pLineInfo->NumChars        = NumCharsToLastNonSpace;
01491     pLineInfo->NumSpaces       = NumSpacesToLastNonSpace;
01492     pLineInfo->justification   = GetJustification();
01493     pLineInfo->ParaLeftMargin  = GetEffectiveLeftMargin();
01494     pLineInfo->ParaRightMargin = GetParaRightMargin();
01495     pLineInfo->Ruler           = GetRuler();
01496     return TRUE;
01497 }
01498 
01499 
01500 /********************************************************************************************
01501 >   BOOL TextLine::PositionCharsInLine(TextLineInfo* pLineInfo)
01502 
01503     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01504     Created:    18/4/95
01505     Inputs:     pLineInfo - required info about the line
01506     Returns:    FALSE if fails
01507     Purpose:    set the position of each char on the line accounting for justification
01508                 ALSO, flag any chars that have moved as 'affected'
01509 ********************************************************************************************/
01510 
01511 BOOL TextLine::PositionCharsInLine(TextLineInfo* pLineInfo)
01512 {
01513     ERROR2IF(pLineInfo==NULL,FALSE,"TextLine::PositionCharsInLine() - pLineInfo==NULL");
01514     // we have two different left/right margins: margin properties of the complete story
01515     // (only for text on path) and margins applied as attributes to paragraphs
01516     MILLIPOINT PhysicalLeftMargin = pLineInfo->LeftMargin;        // story property
01517     MILLIPOINT ParagraphLeftMargin = pLineInfo->ParaLeftMargin;   // paragraph attribute
01518     MILLIPOINT LeftMargin      = PhysicalLeftMargin + ParagraphLeftMargin;
01519 
01520     MILLIPOINT PhysicalRightMargin = pLineInfo->RightMargin;
01521     MILLIPOINT ParagraphRightMargin = pLineInfo->ParaRightMargin;
01522     MILLIPOINT RightMargin = PhysicalRightMargin - ParagraphRightMargin;
01523 
01524     MILLIPOINT SumCharAdvances = pLineInfo->SumCharAdvances;
01525     BOOL       WordWrapping    = pLineInfo->WordWrapping;
01526     MILLIPOINT JustifyWidth    = RightMargin - LeftMargin;
01527 
01528     TRACEUSER("wuerthne", _T("PositionCharsInLine, Sum=%d, JWidth=%d"), SumCharAdvances, JustifyWidth);
01529 
01530     // if word wrapping, fully justified, text does not fill line and end of paragraph
01531     // left justified
01532     Justification justification = pLineInfo->justification;
01533     if (WordWrapping && justification==JFULL && SumCharAdvances < JustifyWidth && FindEOLNode()!=NULL)
01534         justification = JLEFT;
01535 
01536     // calc amount by which chars need to be adjusted (default to left justify)
01537     MILLIPOINT CharPosOffset = PhysicalLeftMargin;
01538     MILLIPOINT ExtraOnSpaces = 0;
01539     MILLIPOINT ExtraOnChars  = 0;
01540     switch (justification)
01541     {
01542         // for both right and centre justification we subtract ParagraphLeftMargin because this
01543         // will be added in again later
01544         case JRIGHT:
01545             CharPosOffset = RightMargin - SumCharAdvances - ParagraphLeftMargin;
01546             break;
01547         case JCENTRE:
01548             CharPosOffset = (LeftMargin + RightMargin - SumCharAdvances)/2 - ParagraphLeftMargin;
01549             break;
01550         case JFULL:
01551             if (JustifyWidth!=0)
01552             {
01553                 // if text does not fill line, expand spaces so it does,
01554                 // but if no spaces, and word wrapping, increase inter-char spacing so word fills line
01555                 // if text overflows line, reduce inter-char spacing so words fit line
01556                 INT32 gap       = JustifyWidth - SumCharAdvances;
01557                 INT32 NumChars  = pLineInfo->NumChars;
01558                 INT32 NumSpaces = pLineInfo->NumSpaces;
01559                 if (gap>0)
01560                 {
01561                     if (NumSpaces>0)
01562                         ExtraOnSpaces = gap/NumSpaces;
01563                     else
01564                         if (WordWrapping && NumChars>1)
01565                             ExtraOnChars = gap/(NumChars-1);
01566                 }
01567                 else
01568                     if (pLineInfo->NumChars>1)
01569                         ExtraOnChars = gap/(NumChars-1);
01570             }
01571             break;
01572         default:
01573             break;
01574     }
01575 
01576     // run through line setting all char's 'PosInLine' (flagging those which moved)
01577     TRACEUSER("wuerthne", _T("calling FindBreakChar, Indent=%d, Offset=%d, ExtraC=%d, ExtraS=%d"),
01578               ParagraphLeftMargin, CharPosOffset, ExtraOnChars, ExtraOnSpaces);
01579     FindBreakChar(INT32_MAX, TRUE, ParagraphLeftMargin, CharPosOffset, ExtraOnChars, ExtraOnSpaces);
01580     return TRUE;
01581 }
01582 
01583 
01584 /********************************************************************************************
01585 >   BOOL TextLine::CalcBaseAndDescentLine(MILLIPOINT* pBaseLine, MILLIPOINT* pDescentLine,
01586                                         MILLIPOINT LastDescentLine, BOOL LastDescentLineValid)
01587 
01588     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01589     Created:    4/2/96
01590     Inputs:     LastDescentLine      - DescentLine of last line
01591                 LastDescentLineValid - FALSE if first line in story
01592     Outputs:    pBaseLine    - base line of this line
01593                 pDescentLine - base line of this line
01594     Returns:    FALSE if fails
01595     Purpose:    Calculate BaseLine and DescentLine for this line given the last line's DescentLine
01596                 if this is the first line in the story,
01597                 the baseline is 0 and the DescentLine has to be back calculated
01598 ********************************************************************************************/
01599 
01600 BOOL TextLine::CalcBaseAndDescentLine(MILLIPOINT* pBaseLine, MILLIPOINT* pDescentLine,
01601                                     MILLIPOINT LastDescentLine, BOOL LastDescentLineValid)
01602 {
01603     // get some info about the line ...
01604     MILLIPOINT LineAscent  = GetLineAscent();
01605     MILLIPOINT LineDescent = GetLineDescent();
01606     MILLIPOINT LineHeight  = LineAscent-LineDescent;
01607     MILLIPOINT LineSpacing = GetLineSpacing();
01608 
01609     MILLIPOINT BaseLine    = 0;
01610     MILLIPOINT DescentLine = 0;
01611     if (LineSpacing!=0)
01612     {
01613         MILLIPOINT OffsetFromDescentLineToBaseLine = MulDiv(LineSpacing, -LineDescent, LineHeight);
01614         if (LastDescentLineValid)
01615         {
01616             DescentLine = LastDescentLine - LineSpacing;
01617             BaseLine    = DescentLine + OffsetFromDescentLineToBaseLine;
01618         }
01619         else
01620             DescentLine = BaseLine - OffsetFromDescentLineToBaseLine;
01621     }
01622     else
01623     {
01624         FIXED16    LineSpaceRatio     = GetLineSpaceRatio();
01625         MILLIPOINT AbsolutLineSpacing = MILLIPOINT( XLONG(LineHeight) * LineSpaceRatio );
01626         MILLIPOINT OffsetFromLastDescentLineToBaseLine = LineAscent;
01627         if (LineSpaceRatio<1)
01628             OffsetFromLastDescentLineToBaseLine = XLONG(LineAscent) * LineSpaceRatio;
01629 
01630         if (LastDescentLineValid)
01631         {
01632             BaseLine    = LastDescentLine - OffsetFromLastDescentLineToBaseLine;
01633             DescentLine = LastDescentLine - AbsolutLineSpacing;
01634         }
01635         else
01636             DescentLine = BaseLine + OffsetFromLastDescentLineToBaseLine - AbsolutLineSpacing;
01637     }
01638 
01639     // set outputs
01640     *pDescentLine = DescentLine;
01641     *pBaseLine    = BaseLine;
01642 
01643     return TRUE;
01644 }
01645 
01646 
01647 /********************************************************************************************
01648 >   BOOL TextLine::SetCharMatrices(MILLIPOINT LinePos)
01649 
01650     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01651     Created:    18/4/95
01652     Inputs:     LinePos - pos of line in story
01653     Returns:    FALSE if fails
01654     Purpose:    set matrices in all VTN on a line
01655 ********************************************************************************************/
01656 
01657 BOOL TextLine::SetCharMatrices(MILLIPOINT LinePos)
01658 {
01659     Node* pNode=FindFirstChild();
01660     while (pNode!=NULL)
01661     {
01662         if (pNode->IsAVisibleTextNode())
01663         {
01664             VisibleTextNode* pVTN=(VisibleTextNode*)pNode;
01665             if (pVTN->Affected())
01666                 pVTN->SetMatrix( Matrix( pVTN->GetPosInLine(), LinePos+pVTN->GetBaseLineShift() ) );
01667         }
01668         pNode=pNode->FindNext();
01669     }
01670     return TRUE;
01671 }
01672 
01673 
01674 /********************************************************************************************
01675 >   BOOL TextLine::FitTextToPath(TextStoryInfo* pStoryInfo, MILLIPOINT LinePos)
01676 
01677     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01678     Created:    18/4/95
01679     Inputs:     pStoryInfo - cached info associated with path
01680                 LinePos   - pos of line in story
01681     Returns:    FALSE if fails
01682     Purpose:    set matrices in all VTN on a line so as to fit text to the path, accounting
01683                 for any transform which was applied before the text was fitted to the path
01684 ********************************************************************************************/
01685 
01686 BOOL TextLine::FitTextToPath(TextStoryInfo* pStoryInfo, MILLIPOINT LinePos)
01687 {
01688     ERROR2IF(pStoryInfo==NULL,FALSE,"TextLine::FitTextToPath() - pStoryInfo==NULL");
01689     TextStory* pTextStory = FindParentStory();
01690     ERROR2IF(pTextStory==NULL,FALSE,"TextLine::FitTextToPath() - pTextStory==NULL");
01691 
01692     // get values required to account for transforms applied to text before it was fitted to the path
01693     FIXED16 scale    = pTextStory->GetCharsScale();
01694     FIXED16 xscale   = (scale<0 ? -scale : scale) * pTextStory->GetCharsAspect();
01695     FIXED16 shear    = scale * tan(pTextStory->GetCharsShear().MakeDouble());
01696 //  double  rotation = pTextStory->GetCharsRotation().MakeDouble();
01697 
01698     // scale the line width offset
01699     LinePos = XLONG(LinePos) * scale;
01700     MILLIPOINT LinePosX = (MILLIPOINT)(LinePos * pStoryInfo->UnitDirectionVectorX + 0.5);
01701     MILLIPOINT LinePosY = (MILLIPOINT)(LinePos * pStoryInfo->UnitDirectionVectorY + 0.5);
01702 
01703     // if chars tangential, get a pointer to a double to read the angle into
01704     // else set the angle to 0 and make the pointer NULL (ie don't actually ask for the angle)
01705     double  TangentAtDist=0;
01706     double* pTangentAtDist=NULL;
01707     if (pTextStory->IsTextOnPathTangential())
01708         pTangentAtDist=&TangentAtDist;
01709 
01710     // some vars used repeatedly while scanning line ...
01711     BOOL                Found=FALSE;
01712     DocCoord            CoordAtDist(0,0);
01713     Matrix              VTNMatrix(0,0);
01714     ProcessPathDistance PathDistanceProcess(64);
01715 
01716     Node* pNode=FindFirstChild();
01717     while (pNode)
01718     {
01719         if (pNode->IsAVisibleTextNode())
01720         {
01721             VisibleTextNode* pVTN=(VisibleTextNode*)pNode;
01722             if (pVTN->Affected())
01723             {
01724                 // get distance to centre of char accounting for pre-transform
01725                 MILLIPOINT CharWidthBy2 = XLONG(pVTN->GetCharWidth()/2) * xscale;
01726                 MILLIPOINT Dist=XLONG(pVTN->GetPosInLine()) * xscale + CharWidthBy2;
01727 
01728                 if (pStoryInfo->PathClosed)
01729                 {
01730                     Dist = Dist % pStoryInfo->PathLength;
01731                     if (Dist<0)
01732                         Dist += pStoryInfo->PathLength;
01733                 }
01734 
01735                 // and thence the coord (and tangent) this distance along the path
01736                 if (PathDistanceProcess.GetCoordAndTangent(&CoordAtDist,pTangentAtDist,&Found,Dist,pStoryInfo->pPath)==FALSE)
01737                     return FALSE;
01738 
01739                 // set the char matrix to account for pre-transform and baseline shift,
01740                 VTNMatrix=Matrix( xscale, 0, shear, scale, 0, XLONG(pVTN->GetBaseLineShift()) * scale );
01741                 // then rotate appropriately,
01742 //              VTNMatrix*=Matrix(DocCoord(CharWidthBy2,0),(TangentAtDist+rotation)/PI*180);
01743                 VTNMatrix*=Matrix(DocCoord(CharWidthBy2,0),TangentAtDist/PI*180);
01744                 // then translate to the correct point on the path and account for line spacing
01745                 VTNMatrix.translate(CoordAtDist.x-CharWidthBy2+LinePosX,CoordAtDist.y+LinePosY);
01746                 pVTN->SetMatrix(VTNMatrix);
01747             }
01748         }
01749         pNode=pNode->FindNext();
01750     }
01751 
01752     return TRUE;
01753 }
01754 
01755 
01756 /*******************************************************************************************
01757 >   VisibleTextNode* TextLine::FindCharAtDistAlongLine(MILLIPOINT Distance, BOOL* LeftHandSide)
01758 
01759     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01760     Created:    12/5/95
01761     Inputs:     Distance - distance along the line to look for
01762                 LeftHandSide - pointer to a BOOL
01763     Outputs:    LeftHandSide - TRUE if the point supplied is in the left half of the char
01764                                FALSE if the point supplied is in the right half of the char
01765     Returns:    A pointer to the character found (NULL if error)
01766     Purpose:    Call this function to get the character at a certian distance along this 
01767                 TextLine.  If the distance is off the right end of the line then a pointer to
01768                 the EOLNode is returned.
01769 ********************************************************************************************/
01770 
01771 VisibleTextNode* TextLine::FindCharAtDistAlongLine(MILLIPOINT Distance, BOOL* LeftHandSide)
01772 {
01773     ERROR2IF(LeftHandSide==NULL,NULL,"TextLine::FindCharAtDistAlongLine() - NULL side pointer");
01774 
01775     VisibleTextNode* pVTN = FindFirstVTN();
01776     ERROR2IF(pVTN==NULL,NULL,"TextLine::FindCharAtDistAlongLine() - has no VTN");
01777     while (1)
01778     {
01779         const MILLIPOINT Left   = pVTN->GetPosInLine();
01780         const MILLIPOINT Centre = Left+pVTN->GetCharWidth()/2;
01781         const MILLIPOINT Next   = Left+pVTN->GetCharAdvance();
01782 
01783         // if to left of centre of this char (and to right of previous if any)
01784         // or at end of line, return char and 'to left'
01785         if (Distance<=Centre || pVTN->IsAnEOLNode())
01786         {
01787             *LeftHandSide = TRUE;
01788             return pVTN;
01789         }
01790 
01791         // if to left of next char (and right of centre of this char),
01792         // return char and 'to right'
01793         if (Distance<=Next)
01794         {
01795             *LeftHandSide = FALSE;
01796             return pVTN;
01797         }
01798 
01799         // if no next VTN, return this char and 'to right'
01800         VisibleTextNode* pNextVTN = pVTN->FindNextVTNInLine();
01801         if (pNextVTN==NULL)
01802         {
01803             *LeftHandSide = FALSE;
01804             return pVTN;
01805         }
01806         pVTN = pNextVTN;
01807     }
01808 }
01809 
01810 
01811 /********************************************************************************************
01812 >   BOOL TextLine::LineContainsCaret(CaretNode** pCaret)
01813 
01814     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01815     Created:    9/7/96
01816     Returns:    ptr to caret if line contains caret else NULL
01817 ********************************************************************************************/
01818 CaretNode* TextLine::FindCaret() const
01819 {
01820     return (CaretNode*)FindFirstChild(CC_RUNTIME_CLASS(CaretNode));
01821 }
01822 
01823 
01824 /********************************************************************************************
01825 >   BOOL TextLine::WholeLineSelected()
01826 
01827     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01828     Created:    4/5/95
01829     Returns:    TRUE if the line itself or all the chars on the line are selected
01830                 (including hard EOL but excluding soft EOL and caret)
01831 ********************************************************************************************/
01832 
01833 BOOL TextLine::WholeLineSelected()
01834 {
01835     if (IsSelected()==FALSE)
01836     {
01837         VisibleTextNode* pVTN = FindFirstVTN();
01838         ERROR2IF(pVTN==NULL,FALSE,"TextLine::WholeLineSelected() - line has no VTN!");
01839         while (pVTN!=NULL)
01840         {
01841             if (!pVTN->IsSelected() && !pVTN->IsACaret())
01842                 return FALSE;
01843             pVTN = pVTN->FindNextVTNInLine();
01844         }
01845     }
01846     return TRUE;
01847 }
01848 
01849 
01850 
01851 /********************************************************************************************
01852 >   TextLine* TextLine::FindNextLine()
01853 
01854     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01855     Created:    24/7/96
01856     Returns:    pointer to next TextLine
01857 ********************************************************************************************/
01858 TextLine* TextLine::FindNextLine()  const
01859 {
01860     return (TextLine*)FindNext(CC_RUNTIME_CLASS(TextLine));
01861 }
01862 
01863 
01864 /********************************************************************************************
01865 >   TextLine* TextLine::FindPrevLine()
01866 
01867     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01868     Created:    24/7/96
01869     Returns:    pointer to previous TextLine
01870 ********************************************************************************************/
01871 TextLine* TextLine::FindPrevLine() const
01872 {
01873     return (TextLine*)FindPrevious(CC_RUNTIME_CLASS(TextLine));
01874 }
01875 
01876 
01877 /********************************************************************************************
01878 >   VisibleTextNode* TextLine::FindFirstVTN()
01879 
01880     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01881     Created:    22/5/95
01882     Returns:    A pointer to the first VTN in this line; NULL if there isn't one
01883     Purpose:    For getting a pointer to the first Visible Text Node in this TextLine
01884 ********************************************************************************************/
01885 VisibleTextNode* TextLine::FindFirstVTN() const
01886 {
01887     return (VisibleTextNode*)FindFirstChild(CC_RUNTIME_CLASS(VisibleTextNode));
01888 }
01889 
01890 
01891 /********************************************************************************************
01892 >   VisibleTextNode* TextLine::FindLastVTN()
01893 
01894     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01895     Created:    22/5/95
01896     Returns:    A pointer to the last VTN in this line; NULL if there isn't one
01897     Purpose:    For getting a pointer to the last Visible Text Node in this TextLine
01898 ********************************************************************************************/
01899 VisibleTextNode* TextLine::FindLastVTN() const
01900 {
01901     return (VisibleTextNode*)FindLastChild(CC_RUNTIME_CLASS(VisibleTextNode));
01902 }
01903 
01904 
01905 /********************************************************************************************
01906 >   EOLNode* TextLine::FindEOLNode()
01907 
01908     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01909     Created:    8/2/96
01910     Returns:    ptr to EOL (or NULL if none - error set)
01911 ********************************************************************************************/
01912 EOLNode* TextLine::FindEOLNode() const
01913 {
01914     EOLNode* pEOL = (EOLNode*)FindFirstChild(CC_RUNTIME_CLASS(EOLNode));
01915     ERROR3IF(pEOL!=NULL && pEOL->FindNext(CC_RUNTIME_CLASS(EOLNode))!=NULL,
01916         "TextLine::FindEOLNode() - more than one EOLNode on a line!");
01917     return pEOL;
01918 }
01919 
01920 
01921 /********************************************************************************************
01922 >   MILLIPOINT TextLine::GetLastCharTracking()
01923 
01924     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01925     Created:    11/1/95
01926     Returns:    Returns size of tracking on last char on line
01927 ********************************************************************************************/
01928 
01929 MILLIPOINT TextLine::GetLastCharTracking()
01930 {
01931     AbstractTextChar* pATC = (AbstractTextChar*)FindFirstChild(CC_RUNTIME_CLASS(AbstractTextChar));
01932     if (pATC->IsAnEOLNode())
01933         pATC = (AbstractTextChar*)(pATC->FindPrevious(CC_RUNTIME_CLASS(AbstractTextChar)));
01934 
01935     if (pATC==NULL)
01936         return 0;
01937 
01938     return pATC->GetCharAdvance() - pATC->GetCharWidth();
01939 }
01940 
01941 
01942 /********************************************************************************************
01943 >   BOOL TextLine::WillLineWrapOnPath(FIXED16 xscale, MILLIPOINT Length)
01944 
01945     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
01946     Created:    11/6/95
01947     Inputs:     pPath   - path to fit text to
01948                 ModLen  - length of the path
01949     Returns:    TRUE if the text line will wrap on this path
01950                 FALSE if not
01951     Purpose:    Calculates whether the formatter will wrap this line of text around the path
01952                 It calculates this by checking the position of the last character (or VTN in
01953                 the line). If this wraps it is assumed the whole line will wrap
01954 ********************************************************************************************/
01955 
01956 BOOL TextLine::WillLineWrapOnPath(FIXED16 xscale, MILLIPOINT PLength)
01957 {
01958     TextStory* pTextStory = FindParentStory();
01959     ERROR2IF(pTextStory==NULL,FALSE,"TextLine::IsLineWrappedOnPath() - pTextStory==NULL");
01960 
01961     BOOL found = FALSE;
01962     Node* pNode=FindLastChild();
01963     while (pNode && !found)
01964     {
01965         if (pNode->IsAVisibleTextNode())
01966             found = TRUE;
01967         else
01968             pNode=pNode->FindPrevious();
01969     }
01970 
01971     if (pNode)
01972     {
01973         VisibleTextNode* pVTN=(VisibleTextNode*)pNode;
01974         MILLIPOINT Dist=XLONG(pVTN->GetPosInLine()) * xscale;
01975         if (Dist>PLength)
01976             return TRUE;
01977     }
01978     return FALSE;
01979 }
01980 
01981 /********************************************************************************************
01982 
01983 >   BOOL TextLine::WritePreChildrenWeb(BaseCamelotFilter *pFilter)
01984     BOOL TextLine::WritePreChildrenNative(BaseCamelotFilter *pFilter)
01985 
01986     Author:     Andy_Hayward (Xara Group Ltd) <camelotdev@xara.com>
01987     Created:    10/07/96
01988     Inputs:     pFilter - filter to save text line node to
01989     Returns:    TRUE if successful, FALSE otherwise
01990     Purpose:    Saves a text line record to the new file format filter
01991 
01992 ********************************************************************************************/
01993 
01994 BOOL TextLine::WritePreChildrenWeb(BaseCamelotFilter *pFilter)
01995 {
01996 #ifdef DO_EXPORT
01997     return CXaraFileTxtLine::WritePreChildrenWeb(pFilter, this);
01998 #else
01999     return FALSE;
02000 #endif
02001 }
02002 
02003 BOOL TextLine::WritePreChildrenNative(BaseCamelotFilter *pFilter)
02004 {
02005 #ifdef DO_EXPORT
02006     return CXaraFileTxtLine::WritePreChildrenNative(pFilter, this);
02007 #else
02008     return FALSE;
02009 #endif
02010 }
02011 
02012 //--------------------------------
02013 
02014 BOOL TextLine::WriteBeginChildRecordsWeb(BaseCamelotFilter* pFilter)
02015 {
02016 #ifdef DO_EXPORT
02017     return CXaraFileTxtLine::WriteBeginChildRecordsWeb(pFilter, this);
02018 #else
02019     return FALSE;
02020 #endif
02021 }
02022 
02023 BOOL TextLine::WriteBeginChildRecordsNative(BaseCamelotFilter* pFilter)
02024 {
02025 #ifdef DO_EXPORT
02026     return CXaraFileTxtLine::WriteBeginChildRecordsNative(pFilter, this);
02027 #else
02028     return FALSE;
02029 #endif
02030 }
02031 
02032 BOOL TextLine::WriteEndChildRecordsWeb(BaseCamelotFilter* pFilter)
02033 {
02034 #ifdef DO_EXPORT
02035     return CXaraFileTxtLine::WriteEndChildRecordsWeb(pFilter, this);
02036 #else
02037     return FALSE;
02038 #endif
02039 }
02040 
02041 BOOL TextLine::WriteEndChildRecordsNative(BaseCamelotFilter* pFilter)
02042 {
02043 #ifdef DO_EXPORT
02044     return CXaraFileTxtLine::WriteEndChildRecordsNative(pFilter, this);
02045 #else
02046     return FALSE;
02047 #endif
02048 }
02049 
02051 // FormatRegion
02052 
02053 
02054 /********************************************************************************************
02055 >   FormatRegion::FormatRegion()
02056 
02057     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02058     Created:    17/03/95
02059     Purpose:    Initialise member variables of the RenderRegion to keep it happy
02060 ********************************************************************************************/
02061 
02062 FormatRegion::FormatRegion() : RenderRegion()
02063 {
02064     CurrentClipRect = DocRect(0,0,0,0);
02065     ScaledPixelWidth = 1;
02066 }
02067 
02068 
02069 /********************************************************************************************
02070 >   FormatRegion::~FormatRegion()
02071 
02072     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02073     Created:    17/03/95
02074     Purpose:    Destructor for FormatRegion
02075 ********************************************************************************************/
02076 
02077 FormatRegion::~FormatRegion()
02078 {
02079 }
02080 
02081 
02082 /********************************************************************************************
02083 >   BOOL FormatRegion::Init(NodeRenderableInk* pFirstNode)
02084 
02085     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02086     Created:    17/03/95
02087     Inputs:     pFirstNode - pointer to a node whose applied attributes are used to initialise 
02088                              the FormatRegion's attribute stack
02089     Returns:    FALSE if fails
02090     Purpose:    Initialise a FormatRegion
02091                 setting the attribute stack to those attributes applied to the specified node
02092 ********************************************************************************************/
02093 
02094 BOOL FormatRegion::Init(NodeRenderableInk* pFirstNode)
02095 {
02096     m_pFormatDC = std::auto_ptr<wxDC>( new wxMemoryDC );
02097     if( NULL == m_pFormatDC.get() )
02098         ERROR2(FALSE,"FormatRegion::Init() - CreateCompatibleDC() failed");
02099 
02100     RenderView = DocView::GetSelected();
02101 
02102     // If there is not a selected doc view (e.g. because it is a clipboard document),
02103     // try the current view item.
02104     // This should only be an issue when formatting text that's been imported into the clipboard, usually
02105     // as part of an OLE operation
02106     if (RenderView == NULL)
02107     {
02108         RenderView = View::GetCurrent();
02109         if (RenderView == NULL)
02110         {
02111             ERROR3("No view!! Can't format text without a view");
02112             return FALSE;
02113         }
02114 
02115     //  ERROR3IF(RenderView->GetDoc()->IsNotAClipboard(),"No selected doc view yet doc attached to current view is not a clipboard doc.  Is this right?");
02116     }
02117 
02118     // Initialise the render region
02119     StartRender();
02120 
02121     // Get the attributes
02122     if (pFirstNode==NULL)
02123         return TRUE;
02124 
02125     CCAttrMap          *pAttribMap = new CCAttrMap(30);
02126     if (pAttribMap == NULL) return FALSE;
02127 
02128     if (!pFirstNode->FindAppliedAttributes(pAttribMap))
02129     {
02130         delete pAttribMap;
02131         return FALSE;
02132     }
02133 
02134     CCAttrMap::iterator pos = pAttribMap->GetStartPosition();
02135     CCAttrMap::iterator end = pAttribMap->GetEndPosition();
02136     while( pos != end )
02137     {
02138         CCRuntimeClass *pKey;
02139         void           *pVal;
02140         pAttribMap->GetNextAssoc(pos, pKey, pVal);
02141 
02142         ((NodeAttribute*) pVal)->Render(this);
02143     }
02144 
02145     delete pAttribMap;
02146 
02147     return TRUE;
02148 }
02149 
02150 
02151 /********************************************************************************************
02152 >   BOOL FormatRegion::GetCharMetrics(CharMetrics* pCharMetrics, WCHAR ch)
02153 
02154     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02155     Created:    14/1/96
02156     Inputs:     ch           - the character (kern codes should ask for a FONTEMCHAR)
02157     Outputs:    pCharMetrics - metrics (CharWidth, FontAscent, FontDescent, FontEmWidth)
02158     Returns:    FALSE if fails
02159     Purpose:    Get metrics for a given char using the current attributes of the format region
02160 ********************************************************************************************/
02161 
02162 BOOL FormatRegion::GetCharMetrics(CharMetrics* pCharMetrics, WCHAR ch)
02163 {
02164     ERROR2IF(pCharMetrics==NULL, FALSE, "FormatRegion::GetCharMetrics() - pCharMetrics==NULL");
02165 
02166     // get the char metrics for the specified character
02167     wxDC               *pDC = m_pFormatDC.get();
02168     CharDescription     FontDesc( FONTEMCHAR, RR_TXTFONTTYPEFACE(), RR_TXTBOLD(), RR_TXTITALIC() );
02169     if( FONTMANAGER->GetCharMetrics( pDC, ch, FontDesc, pCharMetrics ) == FALSE )
02170         return FALSE;
02171 
02172     // get x/y scale factors and apply then to the default metrics found
02173     // Note: y scale factor does not include script ratio so does not affect ascent/descent
02174     //       ie a line of subscript text has the same line ascents/descents as a non-subscript line
02175     TxtScriptAttribute* pScriptAttr=RR_TXTSCRIPT();
02176     ERROR2IF(pScriptAttr==NULL,FALSE,"RenderRegion::GetCharAttributeMatrix() - pScriptAttr==NULL");
02177     double ScaleY = (double)RR_TXTFONTSIZE() / TextManager::GetDefaultHeight();
02178     double ScaleX = ScaleY * RR_TXTASPECTRATIO().MakeDouble() * pScriptAttr->Size.MakeDouble();
02179     pCharMetrics->Scale(ScaleX,ScaleY);
02180 
02181     return TRUE;
02182 }
02183 
02184 
02185 /********************************************************************************************
02186 >   MILLIPOINT FormatRegion::GetCharsKerning(WCHAR chLeft, WCHAR chRight)
02187 
02188     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
02189     Created:    15/10/2000
02190     Inputs:     chLeft      - the left char of a kern pair
02191                 chRight     - the right char of a kern pair
02192     Returns:    Kern distance between chars or 0 on error or no kern
02193     Purpose:    Finds the kerning distance between two chars
02194 ********************************************************************************************/
02195 
02196 MILLIPOINT FormatRegion::GetCharsKerning(WCHAR chLeft, WCHAR chRight)
02197 {
02198     // get the char metrics for the specified character
02199     wxDC               *pDC = m_pFormatDC.get();
02200     CharDescription     FontDesc(FONTEMCHAR, RR_TXTFONTTYPEFACE(), RR_TXTBOLD(), RR_TXTITALIC());
02201     MILLIPOINT          kern = FONTMANAGER->GetCharsKerning( pDC, chLeft, chRight, FontDesc );
02202 
02203     // get x/y scale factors and apply then to the default metrics found
02204     // Note: y scale factor does not include script ratio so does not affect ascent/descent
02205     //       ie a line of subscript text has the same line ascents/descents as a non-subscript line
02206     TxtScriptAttribute* pScriptAttr=RR_TXTSCRIPT();
02207     ERROR2IF(pScriptAttr==NULL,FALSE,"RenderRegion::GetCharAttributeMatrix() - pScriptAttr==NULL");
02208     double ScaleY = (double)RR_TXTFONTSIZE() / TextManager::GetDefaultHeight();
02209     double ScaleX = ScaleY * RR_TXTASPECTRATIO().MakeDouble() * pScriptAttr->Size.MakeDouble();
02210 
02211     if (kern)
02212         kern = (MILLIPOINT)(kern * ScaleX + 0.5);
02213 
02214     return kern;
02215 }

Generated on Sat Nov 10 03:46:10 2007 for Camelot by  doxygen 1.4.4