/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include "tmpdevice.hxx" #include #include #include "mathtype.hxx" #include // SmDefaultingVisitor void SmDefaultingVisitor::Visit( SmTableNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmBraceNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmBracebodyNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmOperNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmAlignNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmAttributNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmFontNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmUnHorNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmBinHorNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmBinVerNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmBinDiagonalNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmSubSupNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmMatrixNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmPlaceNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmTextNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmSpecialNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmGlyphSpecialNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmMathSymbolNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmBlankNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmErrorNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmLineNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmExpressionNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmPolyLineNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmRootNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmRootSymbolNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmRectangleNode* pNode ) { DefaultVisit( pNode ); } void SmDefaultingVisitor::Visit( SmVerticalBraceNode* pNode ) { DefaultVisit( pNode ); } // SmCaretDrawingVisitor SmCaretDrawingVisitor::SmCaretDrawingVisitor( OutputDevice& rDevice, SmCaretPos position, Point offset, bool caretVisible ) : mrDev( rDevice ) , maPos( position ) , maOffset( offset ) , mbCaretVisible( caretVisible ) { SAL_WARN_IF( !position.IsValid(), "starmath", "Cannot draw invalid position!" ); if( !position.IsValid( ) ) return; //Save device state mrDev.Push( PushFlags::FONT | PushFlags::MAPMODE | PushFlags::LINECOLOR | PushFlags::FILLCOLOR | PushFlags::TEXTCOLOR ); maPos.pSelectedNode->Accept( this ); //Restore device state mrDev.Pop( ); } void SmCaretDrawingVisitor::Visit( SmTextNode* pNode ) { tools::Long i = maPos.nIndex; mrDev.SetFont( pNode->GetFont( ) ); //Find the line SmNode* pLine = SmCursor::FindTopMostNodeInLine( pNode ); //Find coordinates tools::Long left = pNode->GetLeft( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, i ) + maOffset.X( ); tools::Long top = pLine->GetTop( ) + maOffset.Y( ); tools::Long height = pLine->GetHeight( ); tools::Long left_line = pLine->GetLeft( ) + maOffset.X( ); tools::Long right_line = pLine->GetRight( ) + maOffset.X( ); //Set color mrDev.SetLineColor( COL_BLACK ); if ( mbCaretVisible ) { //Draw vertical line Point p1( left, top ); Point p2( left, top + height ); mrDev.DrawLine( p1, p2 ); } //Underline the line Point aLeft( left_line, top + height ); Point aRight( right_line, top + height ); mrDev.DrawLine( aLeft, aRight ); } void SmCaretDrawingVisitor::DefaultVisit( SmNode* pNode ) { //Find the line SmNode* pLine = SmCursor::FindTopMostNodeInLine( pNode ); //Find coordinates tools::Long left = pNode->GetLeft( ) + maOffset.X( ) + ( maPos.nIndex == 1 ? pNode->GetWidth( ) : 0 ); tools::Long top = pLine->GetTop( ) + maOffset.Y( ); tools::Long height = pLine->GetHeight( ); tools::Long left_line = pLine->GetLeft( ) + maOffset.X( ); tools::Long right_line = pLine->GetRight( ) + maOffset.X( ); //Set color mrDev.SetLineColor( COL_BLACK ); if ( mbCaretVisible ) { //Draw vertical line Point p1( left, top ); Point p2( left, top + height ); mrDev.DrawLine( p1, p2 ); } //Underline the line Point aLeft( left_line, top + height ); Point aRight( right_line, top + height ); mrDev.DrawLine( aLeft, aRight ); } // SmCaretPos2LineVisitor void SmCaretPos2LineVisitor::Visit( SmTextNode* pNode ) { //Save device state mpDev->Push( PushFlags::FONT | PushFlags::TEXTCOLOR ); tools::Long i = maPos.nIndex; mpDev->SetFont( pNode->GetFont( ) ); //Find coordinates tools::Long left = pNode->GetLeft( ) + mpDev->GetTextWidth( pNode->GetText( ), 0, i ); tools::Long top = pNode->GetTop( ); tools::Long height = pNode->GetHeight( ); maLine = SmCaretLine( left, top, height ); //Restore device state mpDev->Pop( ); } void SmCaretPos2LineVisitor::DefaultVisit( SmNode* pNode ) { //Vertical line ( code from SmCaretDrawingVisitor ) Point p1 = pNode->GetTopLeft( ); if( maPos.nIndex == 1 ) p1.Move( pNode->GetWidth( ), 0 ); maLine = SmCaretLine( p1.X( ), p1.Y( ), pNode->GetHeight( ) ); } // SmDrawingVisitor void SmDrawingVisitor::Visit( SmTableNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmBraceNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmBracebodyNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmOperNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmAlignNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmAttributNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmFontNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmUnHorNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmBinHorNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmBinVerNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmBinDiagonalNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmSubSupNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmMatrixNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmPlaceNode* pNode ) { DrawSpecialNode( pNode ); } void SmDrawingVisitor::Visit( SmTextNode* pNode ) { DrawTextNode( pNode ); } void SmDrawingVisitor::Visit( SmSpecialNode* pNode ) { DrawSpecialNode( pNode ); } void SmDrawingVisitor::Visit( SmGlyphSpecialNode* pNode ) { DrawSpecialNode( pNode ); } void SmDrawingVisitor::Visit( SmMathSymbolNode* pNode ) { DrawSpecialNode( pNode ); } void SmDrawingVisitor::Visit( SmBlankNode* ) { } void SmDrawingVisitor::Visit( SmErrorNode* pNode ) { DrawSpecialNode( pNode ); } void SmDrawingVisitor::Visit( SmLineNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmExpressionNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmRootNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmVerticalBraceNode* pNode ) { DrawChildren( pNode ); } void SmDrawingVisitor::Visit( SmRootSymbolNode* pNode ) { if ( pNode->IsPhantom( ) ) return; // draw root-sign itself DrawSpecialNode( pNode ); SmTmpDevice aTmpDev( mrDev, true ); aTmpDev.SetFillColor( pNode->GetFont( ).GetColor( ) ); mrDev.SetLineColor( ); aTmpDev.SetFont( pNode->GetFont( ) ); // since the width is always unscaled it corresponds to the _original_ // _unscaled_ font height to be used, we use that to calculate the // bar height. Thus it is independent of the arguments height. // ( see display of sqrt QQQ versus sqrt stack{Q#Q#Q#Q} ) tools::Long nBarHeight = pNode->GetWidth( ) * 7 / 100; tools::Long nBarWidth = pNode->GetBodyWidth( ) + pNode->GetBorderWidth( ); Point aBarOffset( pNode->GetWidth( ), +pNode->GetBorderWidth( ) ); Point aBarPos( maPosition + aBarOffset ); tools::Rectangle aBar( aBarPos, Size( nBarWidth, nBarHeight ) ); //! avoid GROWING AND SHRINKING of drawn rectangle when constantly //! increasing zoomfactor. // This is done by shifting its output-position to a point that // corresponds exactly to a pixel on the output device. Point aDrawPos( mrDev.PixelToLogic( mrDev.LogicToPixel( aBar.TopLeft( ) ) ) ); aBar.SetPos( aDrawPos ); mrDev.DrawRect( aBar ); } void SmDrawingVisitor::Visit( SmPolyLineNode* pNode ) { if ( pNode->IsPhantom( ) ) return; tools::Long nBorderwidth = pNode->GetFont( ).GetBorderWidth( ); LineInfo aInfo; aInfo.SetWidth( pNode->GetWidth( ) - 2 * nBorderwidth ); Point aOffset ( Point( ) - pNode->GetPolygon( ).GetBoundRect( ).TopLeft( ) + Point( nBorderwidth, nBorderwidth ) ), aPos ( maPosition + aOffset ); pNode->GetPolygon( ).Move( aPos.X( ), aPos.Y( ) ); //Works because Polygon wraps a pointer SmTmpDevice aTmpDev ( mrDev, false ); aTmpDev.SetLineColor( pNode->GetFont( ).GetColor( ) ); mrDev.DrawPolyLine( pNode->GetPolygon( ), aInfo ); } void SmDrawingVisitor::Visit( SmRectangleNode* pNode ) { if ( pNode->IsPhantom( ) ) return; SmTmpDevice aTmpDev ( mrDev, false ); aTmpDev.SetFillColor( pNode->GetFont( ).GetColor( ) ); mrDev.SetLineColor( ); aTmpDev.SetFont( pNode->GetFont( ) ); sal_uLong nTmpBorderWidth = pNode->GetFont( ).GetBorderWidth( ); // get rectangle and remove borderspace tools::Rectangle aTmp ( pNode->AsRectangle( ) + maPosition - pNode->GetTopLeft( ) ); aTmp.AdjustLeft(nTmpBorderWidth ); aTmp.AdjustRight( -sal_Int32(nTmpBorderWidth) ); aTmp.AdjustTop(nTmpBorderWidth ); aTmp.AdjustBottom( -sal_Int32(nTmpBorderWidth) ); SAL_WARN_IF( aTmp.IsEmpty(), "starmath", "Empty rectangle" ); //! avoid GROWING AND SHRINKING of drawn rectangle when constantly //! increasing zoomfactor. // This is done by shifting its output-position to a point that // corresponds exactly to a pixel on the output device. Point aPos ( mrDev.PixelToLogic( mrDev.LogicToPixel( aTmp.TopLeft( ) ) ) ); aTmp.SetPos( aPos ); mrDev.DrawRect( aTmp ); } void SmDrawingVisitor::DrawTextNode( SmTextNode* pNode ) { if ( pNode->IsPhantom() || pNode->GetText().isEmpty() || pNode->GetText()[0] == '\0' ) return; SmTmpDevice aTmpDev ( mrDev, false ); aTmpDev.SetFont( pNode->GetFont( ) ); Point aPos ( maPosition ); aPos.AdjustY(pNode->GetBaselineOffset( ) ); // round to pixel coordinate aPos = mrDev.PixelToLogic( mrDev.LogicToPixel( aPos ) ); mrDev.DrawStretchText( aPos, pNode->GetWidth( ), pNode->GetText( ) ); } void SmDrawingVisitor::DrawSpecialNode( SmSpecialNode* pNode ) { //! since this chars might come from any font, that we may not have //! set to ALIGN_BASELINE yet, we do it now. pNode->GetFont( ).SetAlignment( ALIGN_BASELINE ); DrawTextNode( pNode ); } void SmDrawingVisitor::DrawChildren( SmStructureNode* pNode ) { if ( pNode->IsPhantom( ) ) return; Point rPosition = maPosition; for( auto pChild : *pNode ) { if(!pChild) continue; Point aOffset ( pChild->GetTopLeft( ) - pNode->GetTopLeft( ) ); maPosition = rPosition + aOffset; pChild->Accept( this ); } } // SmSetSelectionVisitor SmSetSelectionVisitor::SmSetSelectionVisitor( SmCaretPos startPos, SmCaretPos endPos, SmNode* pTree) : maStartPos(startPos) , maEndPos(endPos) , mbSelecting(false) { //Assume that pTree is a SmTableNode SAL_WARN_IF(pTree->GetType() != SmNodeType::Table, "starmath", "pTree should be a SmTableNode!"); //Visit root node, this is special as this node cannot be selected, but its children can! if(pTree->GetType() == SmNodeType::Table){ //Change state if maStartPos is in front of this node if( maStartPos.pSelectedNode == pTree && maStartPos.nIndex == 0 ) mbSelecting = !mbSelecting; //Change state if maEndPos is in front of this node if( maEndPos.pSelectedNode == pTree && maEndPos.nIndex == 0 ) mbSelecting = !mbSelecting; SAL_WARN_IF(mbSelecting, "starmath", "Caret positions needed to set mbSelecting about, shouldn't be possible!"); //Visit lines for( auto pChild : *static_cast(pTree) ) { if(!pChild) continue; pChild->Accept( this ); //If we started a selection in this line and it haven't ended, we do that now! if(mbSelecting) { mbSelecting = false; SetSelectedOnAll(pChild); //Set maStartPos and maEndPos to invalid positions, this ensures that an unused //start or end (because we forced end above), doesn't start a new selection. maStartPos = maEndPos = SmCaretPos(); } } //Check if pTree isn't selected SAL_WARN_IF(pTree->IsSelected(), "starmath", "pTree should never be selected!"); //Discard the selection if there's a bug (it's better than crashing) if(pTree->IsSelected()) SetSelectedOnAll(pTree, false); }else //This shouldn't happen, but I don't see any reason to die if it does pTree->Accept(this); } void SmSetSelectionVisitor::SetSelectedOnAll( SmNode* pSubTree, bool IsSelected ) { pSubTree->SetSelected( IsSelected ); if(pSubTree->GetNumSubNodes() == 0) return; //Quick BFS to set all selections for( auto pChild : *static_cast(pSubTree) ) { if(!pChild) continue; SetSelectedOnAll( pChild, IsSelected ); } } void SmSetSelectionVisitor::DefaultVisit( SmNode* pNode ) { //Change state if maStartPos is in front of this node if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 0 ) mbSelecting = !mbSelecting; //Change state if maEndPos is in front of this node if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 0 ) mbSelecting = !mbSelecting; //Cache current state bool WasSelecting = mbSelecting; bool ChangedState = false; //Set selected pNode->SetSelected( mbSelecting ); //Visit children if(pNode->GetNumSubNodes() > 0) { for( auto pChild : *static_cast(pNode) ) { if(!pChild) continue; pChild->Accept( this ); ChangedState = ( WasSelecting != mbSelecting ) || ChangedState; } } //If state changed if( ChangedState ) { //Select this node and all of its children //(Make exception for SmBracebodyNode) if( pNode->GetType() != SmNodeType::Bracebody || !pNode->GetParent() || pNode->GetParent()->GetType() != SmNodeType::Brace ) SetSelectedOnAll( pNode ); else SetSelectedOnAll( pNode->GetParent() ); /* If the equation is: sqrt{2 + 4} + 5 * And the selection is: sqrt{2 + [4} +] 5 * Where [ denotes maStartPos and ] denotes maEndPos * Then the sqrt node should be selected, so that the * effective selection is: [sqrt{2 + 4} +] 5 * The same is the case if we swap maStartPos and maEndPos. */ } //Change state if maStartPos is after this node if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 1 ) { mbSelecting = !mbSelecting; } //Change state if maEndPos is after of this node if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 1 ) { mbSelecting = !mbSelecting; } } void SmSetSelectionVisitor::VisitCompositionNode( SmStructureNode* pNode ) { //Change state if maStartPos is in front of this node if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 0 ) mbSelecting = !mbSelecting; //Change state if maEndPos is in front of this node if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 0 ) mbSelecting = !mbSelecting; //Cache current state bool WasSelecting = mbSelecting; //Visit children for( auto pChild : *pNode ) { if(!pChild) continue; pChild->Accept( this ); } //Set selected, if everything was selected pNode->SetSelected( WasSelecting && mbSelecting ); //Change state if maStartPos is after this node if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 1 ) mbSelecting = !mbSelecting; //Change state if maEndPos is after of this node if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 1 ) mbSelecting = !mbSelecting; } void SmSetSelectionVisitor::Visit( SmTextNode* pNode ) { tools::Long i1 = -1, i2 = -1; if( maStartPos.pSelectedNode == pNode ) i1 = maStartPos.nIndex; if( maEndPos.pSelectedNode == pNode ) i2 = maEndPos.nIndex; tools::Long start, end; pNode->SetSelected(true); if( i1 != -1 && i2 != -1 ) { start = std::min(i1, i2); end = std::max(i1, i2); } else if( mbSelecting && i1 != -1 ) { start = 0; end = i1; mbSelecting = false; } else if( mbSelecting && i2 != -1 ) { start = 0; end = i2; mbSelecting = false; } else if( !mbSelecting && i1 != -1 ) { start = i1; end = pNode->GetText().getLength(); mbSelecting = true; } else if( !mbSelecting && i2 != -1 ) { start = i2; end = pNode->GetText().getLength(); mbSelecting = true; } else if( mbSelecting ) { start = 0; end = pNode->GetText().getLength(); } else { pNode->SetSelected( false ); start = 0; end = 0; } pNode->SetSelected( start != end ); pNode->SetSelectionStart( start ); pNode->SetSelectionEnd( end ); } void SmSetSelectionVisitor::Visit( SmExpressionNode* pNode ) { VisitCompositionNode( pNode ); } void SmSetSelectionVisitor::Visit( SmLineNode* pNode ) { VisitCompositionNode( pNode ); } void SmSetSelectionVisitor::Visit( SmAlignNode* pNode ) { VisitCompositionNode( pNode ); } void SmSetSelectionVisitor::Visit( SmBinHorNode* pNode ) { VisitCompositionNode( pNode ); } void SmSetSelectionVisitor::Visit( SmUnHorNode* pNode ) { VisitCompositionNode( pNode ); } void SmSetSelectionVisitor::Visit( SmFontNode* pNode ) { VisitCompositionNode( pNode ); } // SmCaretPosGraphBuildingVisitor SmCaretPosGraphBuildingVisitor::SmCaretPosGraphBuildingVisitor( SmNode* pRootNode ) : mpRightMost(nullptr) , mpGraph(new SmCaretPosGraph) { //pRootNode should always be a table SAL_WARN_IF( pRootNode->GetType( ) != SmNodeType::Table, "starmath", "pRootNode must be a table node"); //Handle the special case where SmNodeType::Table is used a rootnode if( pRootNode->GetType( ) == SmNodeType::Table ){ //Children are SmLineNodes //Or so I thought... Apparently, the children can be instances of SmExpression //especially if there's an error in the formula... So here we go, a simple work around. for( auto pChild : *static_cast(pRootNode) ) { if(!pChild) continue; mpRightMost = mpGraph->Add( SmCaretPos( pChild, 0 ) ); pChild->Accept( this ); } }else pRootNode->Accept(this); } SmCaretPosGraphBuildingVisitor::~SmCaretPosGraphBuildingVisitor() { } void SmCaretPosGraphBuildingVisitor::Visit( SmLineNode* pNode ){ for( auto pChild : *pNode ) { if(!pChild) continue; pChild->Accept( this ); } } /** Build SmCaretPosGraph for SmTableNode * This method covers cases where SmTableNode is used in a binom or stack, * the special case where it is used as root node for the entire formula is * handled in the constructor. */ void SmCaretPosGraphBuildingVisitor::Visit( SmTableNode* pNode ){ SmCaretPosGraphEntry *left = mpRightMost, *right = mpGraph->Add( SmCaretPos( pNode, 1) ); bool bIsFirst = true; for( auto pChild : *pNode ) { if(!pChild) continue; mpRightMost = mpGraph->Add( SmCaretPos( pChild, 0 ), left); if(bIsFirst) left->SetRight(mpRightMost); pChild->Accept( this ); mpRightMost->SetRight(right); if(bIsFirst) right->SetLeft(mpRightMost); bIsFirst = false; } mpRightMost = right; } /** Build SmCaretPosGraph for SmSubSupNode * * The child positions in a SubSupNode, where H is the body: * \code * CSUP * * LSUP H H RSUP * H H * HHHH * H H * LSUB H H RSUB * * CSUB * \endcode * * Graph over these, where "left" is before the SmSubSupNode and "right" is after: * \dot * digraph Graph{ * left -> H; * H -> right; * LSUP -> H; * LSUB -> H; * CSUP -> right; * CSUB -> right; * RSUP -> right; * RSUB -> right; * }; * \enddot * */ void SmCaretPosGraphBuildingVisitor::Visit( SmSubSupNode* pNode ) { SmCaretPosGraphEntry *left, *right, *bodyLeft, *bodyRight; assert(mpRightMost); left = mpRightMost; //Create bodyLeft SAL_WARN_IF( !pNode->GetBody(), "starmath", "SmSubSupNode Doesn't have a body!" ); bodyLeft = mpGraph->Add( SmCaretPos( pNode->GetBody( ), 0 ), left ); left->SetRight( bodyLeft ); //TODO: Don't make this if LSUP or LSUB are NULL ( not sure??? ) //Create right right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); //Visit the body, to get bodyRight mpRightMost = bodyLeft; pNode->GetBody( )->Accept( this ); bodyRight = mpRightMost; bodyRight->SetRight( right ); right->SetLeft( bodyRight ); //If there's an LSUP SmNode* pChild = pNode->GetSubSup( LSUP ); if( pChild ){ SmCaretPosGraphEntry *cLeft; //Child left cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); mpRightMost = cLeft; pChild->Accept( this ); mpRightMost->SetRight( bodyLeft ); } //If there's an LSUB pChild = pNode->GetSubSup( LSUB ); if( pChild ){ SmCaretPosGraphEntry *cLeft; //Child left cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); mpRightMost = cLeft; pChild->Accept( this ); mpRightMost->SetRight( bodyLeft ); } //If there's a CSUP pChild = pNode->GetSubSup( CSUP ); if( pChild ){ SmCaretPosGraphEntry *cLeft; //Child left cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); mpRightMost = cLeft; pChild->Accept( this ); mpRightMost->SetRight( right ); } //If there's a CSUB pChild = pNode->GetSubSup( CSUB ); if( pChild ){ SmCaretPosGraphEntry *cLeft; //Child left cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); mpRightMost = cLeft; pChild->Accept( this ); mpRightMost->SetRight( right ); } //If there's an RSUP pChild = pNode->GetSubSup( RSUP ); if( pChild ){ SmCaretPosGraphEntry *cLeft; //Child left cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), bodyRight ); mpRightMost = cLeft; pChild->Accept( this ); mpRightMost->SetRight( right ); } //If there's an RSUB pChild = pNode->GetSubSup( RSUB ); if( pChild ){ SmCaretPosGraphEntry *cLeft; //Child left cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), bodyRight ); mpRightMost = cLeft; pChild->Accept( this ); mpRightMost->SetRight( right ); } //Set return parameters mpRightMost = right; } /** Build caret position for SmOperNode * * If first child is an SmSubSupNode we will ignore its * body, as this body is a SmMathSymbol, for SUM, INT or similar * that shouldn't be subject to modification. * If first child is not a SmSubSupNode, ignore it completely * as it is a SmMathSymbol. * * The child positions in a SmOperNode, where H is symbol, e.g. int, sum or similar: * \code * TO * * LSUP H H RSUP BBB BB BBB B B * H H B B B B B B B B * HHHH BBB B B B B B * H H B B B B B B B * LSUB H H RSUB BBB BB BBB B * * FROM * \endcode * Notice, CSUP, etc. are actually grandchildren, but inorder to ignore H, these are visited * from here. If they are present, that is if pOper is an instance of SmSubSupNode. * * Graph over these, where "left" is before the SmOperNode and "right" is after: * \dot * digraph Graph{ * left -> BODY; * BODY -> right; * LSUP -> BODY; * LSUB -> BODY; * TO -> BODY; * FROM -> BODY; * RSUP -> BODY; * RSUB -> BODY; * }; * \enddot */ void SmCaretPosGraphBuildingVisitor::Visit( SmOperNode* pNode ) { SmNode *pOper = pNode->GetSubNode( 0 ), *pBody = pNode->GetSubNode( 1 ); SmCaretPosGraphEntry *left = mpRightMost, *bodyLeft, *bodyRight, *right; //Create body left bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); left->SetRight( bodyLeft ); //Visit body, get bodyRight mpRightMost = bodyLeft; pBody->Accept( this ); bodyRight = mpRightMost; //Create right right = mpGraph->Add( SmCaretPos( pNode, 1 ), bodyRight ); bodyRight->SetRight( right ); //Get subsup pNode if any SmSubSupNode* pSubSup = pOper->GetType( ) == SmNodeType::SubSup ? static_cast(pOper) : nullptr; if( pSubSup ) { SmNode* pChild = pSubSup->GetSubSup( LSUP ); if( pChild ) { //Create position in front of pChild SmCaretPosGraphEntry *childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); //Visit pChild mpRightMost = childLeft; pChild->Accept( this ); //Set right on mpRightMost from pChild mpRightMost->SetRight( bodyLeft ); } pChild = pSubSup->GetSubSup( LSUB ); if( pChild ) { //Create position in front of pChild SmCaretPosGraphEntry *childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); //Visit pChild mpRightMost = childLeft; pChild->Accept( this ); //Set right on mpRightMost from pChild mpRightMost->SetRight( bodyLeft ); } pChild = pSubSup->GetSubSup( CSUP ); if ( pChild ) {//TO //Create position in front of pChild SmCaretPosGraphEntry *childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); //Visit pChild mpRightMost = childLeft; pChild->Accept( this ); //Set right on mpRightMost from pChild mpRightMost->SetRight( bodyLeft ); } pChild = pSubSup->GetSubSup( CSUB ); if( pChild ) { //FROM //Create position in front of pChild SmCaretPosGraphEntry *childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); //Visit pChild mpRightMost = childLeft; pChild->Accept( this ); //Set right on mpRightMost from pChild mpRightMost->SetRight( bodyLeft ); } pChild = pSubSup->GetSubSup( RSUP ); if ( pChild ) { //Create position in front of pChild SmCaretPosGraphEntry *childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); //Visit pChild mpRightMost = childLeft; pChild->Accept( this ); //Set right on mpRightMost from pChild mpRightMost->SetRight( bodyLeft ); } pChild = pSubSup->GetSubSup( RSUB ); if ( pChild ) { //Create position in front of pChild SmCaretPosGraphEntry *childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); //Visit pChild mpRightMost = childLeft; pChild->Accept( this ); //Set right on mpRightMost from pChild mpRightMost->SetRight( bodyLeft ); } } //Return right mpRightMost = right; } void SmCaretPosGraphBuildingVisitor::Visit( SmMatrixNode* pNode ) { SmCaretPosGraphEntry *left = mpRightMost, *right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); for (size_t i = 0; i < pNode->GetNumRows(); ++i) { SmCaretPosGraphEntry* r = left; for (size_t j = 0; j < pNode->GetNumCols(); ++j) { SmNode* pSubNode = pNode->GetSubNode( i * pNode->GetNumCols( ) + j ); mpRightMost = mpGraph->Add( SmCaretPos( pSubNode, 0 ), r ); if( j != 0 || ( pNode->GetNumRows() - 1U ) / 2 == i ) r->SetRight( mpRightMost ); pSubNode->Accept( this ); r = mpRightMost; } mpRightMost->SetRight( right ); if( ( pNode->GetNumRows() - 1U ) / 2 == i ) right->SetLeft( mpRightMost ); } mpRightMost = right; } /** Build SmCaretPosGraph for SmTextNode * * Lines in an SmTextNode: * \code * A B C * \endcode * Where A B and C are characters in the text. * * Graph over these, where "left" is before the SmTextNode and "right" is after: * \dot * digraph Graph{ * left -> A; * A -> B * B -> right; * }; * \enddot * Notice that C and right is the same position here. */ void SmCaretPosGraphBuildingVisitor::Visit( SmTextNode* pNode ) { SAL_WARN_IF( pNode->GetText().isEmpty(), "starmath", "Empty SmTextNode is bad" ); int size = pNode->GetText().getLength(); for( int i = 1; i <= size; i++ ){ SmCaretPosGraphEntry* pRight = mpRightMost; mpRightMost = mpGraph->Add( SmCaretPos( pNode, i ), pRight ); pRight->SetRight( mpRightMost ); } } /** Build SmCaretPosGraph for SmBinVerNode * * Lines in an SmBinVerNode: * \code * A * ----- * B * \endcode * * Graph over these, where "left" is before the SmBinVerNode and "right" is after: * \dot * digraph Graph{ * left -> A; * A -> right; * B -> right; * }; * \enddot */ void SmCaretPosGraphBuildingVisitor::Visit( SmBinVerNode* pNode ) { //None if these children can be NULL, see SmBinVerNode::Arrange SmNode *pNum = pNode->GetSubNode( 0 ), *pDenom = pNode->GetSubNode( 2 ); SmCaretPosGraphEntry *left, *right, *numLeft, *denomLeft; assert(mpRightMost); //Set left left = mpRightMost; //Create right right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); //Create numLeft numLeft = mpGraph->Add( SmCaretPos( pNum, 0 ), left ); left->SetRight( numLeft ); //Visit pNum mpRightMost = numLeft; pNum->Accept( this ); mpRightMost->SetRight( right ); right->SetLeft( mpRightMost ); //Create denomLeft denomLeft = mpGraph->Add( SmCaretPos( pDenom, 0 ), left ); //Visit pDenom mpRightMost = denomLeft; pDenom->Accept( this ); mpRightMost->SetRight( right ); //Set return parameter mpRightMost = right; } /** Build SmCaretPosGraph for SmVerticalBraceNode * * Lines in an SmVerticalBraceNode: * \code * pScript * ________ * / \ * pBody * \endcode * */ void SmCaretPosGraphBuildingVisitor::Visit( SmVerticalBraceNode* pNode ) { SmNode *pBody = pNode->Body(), *pScript = pNode->Script(); //None of these children can be NULL SmCaretPosGraphEntry *left, *bodyLeft, *scriptLeft, *right; left = mpRightMost; //Create right right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); //Create bodyLeft bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); left->SetRight( bodyLeft ); mpRightMost = bodyLeft; pBody->Accept( this ); mpRightMost->SetRight( right ); right->SetLeft( mpRightMost ); //Create script scriptLeft = mpGraph->Add( SmCaretPos( pScript, 0 ), left ); mpRightMost = scriptLeft; pScript->Accept( this ); mpRightMost->SetRight( right ); //Set return value mpRightMost = right; } /** Build SmCaretPosGraph for SmBinDiagonalNode * * Lines in an SmBinDiagonalNode: * \code * A / * / * / B * \endcode * Where A and B are lines. * * Used in formulas such as "A wideslash B" */ void SmCaretPosGraphBuildingVisitor::Visit( SmBinDiagonalNode* pNode ) { SmNode *A = pNode->GetSubNode( 0 ), *B = pNode->GetSubNode( 1 ); SmCaretPosGraphEntry *left, *leftA, *rightA, *leftB, *right; left = mpRightMost; //Create right right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); //Create left A leftA = mpGraph->Add( SmCaretPos( A, 0 ), left ); left->SetRight( leftA ); //Visit A mpRightMost = leftA; A->Accept( this ); rightA = mpRightMost; //Create left B leftB = mpGraph->Add( SmCaretPos( B, 0 ), rightA ); rightA->SetRight( leftB ); //Visit B mpRightMost = leftB; B->Accept( this ); mpRightMost->SetRight( right ); right->SetLeft( mpRightMost ); //Set return value mpRightMost = right; } //Straight forward ( I think ) void SmCaretPosGraphBuildingVisitor::Visit( SmBinHorNode* pNode ) { for( auto pChild : *pNode ) { if(!pChild) continue; pChild->Accept( this ); } } void SmCaretPosGraphBuildingVisitor::Visit( SmUnHorNode* pNode ) { // Unary operator node for( auto pChild : *pNode ) { if(!pChild) continue; pChild->Accept( this ); } } void SmCaretPosGraphBuildingVisitor::Visit( SmExpressionNode* pNode ) { for( auto pChild : *pNode ) { if(!pChild) continue; pChild->Accept( this ); } } void SmCaretPosGraphBuildingVisitor::Visit( SmFontNode* pNode ) { //Has only got one child, should act as an expression if possible for( auto pChild : *pNode ) { if(!pChild) continue; pChild->Accept( this ); } } /** Build SmCaretPosGraph for SmBracebodyNode * Acts as an SmExpressionNode * * Below is an example of a formula tree that has multiple children for SmBracebodyNode * \dot * digraph { * labelloc = "t"; * label= "Equation: \"lbrace i mline i in setZ rbrace\""; * n0 [label="SmTableNode"]; * n0 -> n1 [label="0"]; * n1 [label="SmLineNode"]; * n1 -> n2 [label="0"]; * n2 [label="SmExpressionNode"]; * n2 -> n3 [label="0"]; * n3 [label="SmBraceNode"]; * n3 -> n4 [label="0"]; * n4 [label="SmMathSymbolNode: {"]; * n3 -> n5 [label="1"]; * n5 [label="SmBracebodyNode"]; * n5 -> n6 [label="0"]; * n6 [label="SmExpressionNode"]; * n6 -> n7 [label="0"]; * n7 [label="SmTextNode: i"]; * n5 -> n8 [label="1"]; * n8 [label="SmMathSymbolNode: |"]; // Unicode "VERTICAL LINE" * n5 -> n9 [label="2"]; * n9 [label="SmExpressionNode"]; * n9 -> n10 [label="0"]; * n10 [label="SmBinHorNode"]; * n10 -> n11 [label="0"]; * n11 [label="SmTextNode: i"]; * n10 -> n12 [label="1"]; * n12 [label="SmMathSymbolNode: ∈"]; // Unicode "ELEMENT OF" * n10 -> n13 [label="2"]; * n13 [label="SmMathSymbolNode: ℤ"]; // Unicode "DOUBLE-STRUCK CAPITAL Z" * n3 -> n14 [label="2"]; * n14 [label="SmMathSymbolNode: }"]; * } * \enddot */ void SmCaretPosGraphBuildingVisitor::Visit( SmBracebodyNode* pNode ) { for( auto pChild : *pNode ) { if(!pChild) continue; SmCaretPosGraphEntry* pStart = mpGraph->Add( SmCaretPos( pChild, 0), mpRightMost ); mpRightMost->SetRight( pStart ); mpRightMost = pStart; pChild->Accept( this ); } } /** Build SmCaretPosGraph for SmAlignNode * Acts as an SmExpressionNode, as it only has one child this okay */ void SmCaretPosGraphBuildingVisitor::Visit( SmAlignNode* pNode ) { for( auto pChild : *pNode ) { if(!pChild) continue; pChild->Accept( this ); } } /** Build SmCaretPosGraph for SmRootNode * * Lines in an SmRootNode: * \code * _________ * A/ * \/ B * * \endcode * A: pExtra ( optional, can be NULL ), * B: pBody * * Graph over these, where "left" is before the SmRootNode and "right" is after: * \dot * digraph Graph{ * left -> B; * B -> right; * A -> B; * } * \enddot */ void SmCaretPosGraphBuildingVisitor::Visit( SmRootNode* pNode ) { SmNode *pExtra = pNode->GetSubNode( 0 ), //Argument, NULL for sqrt, and SmTextNode if cubicroot *pBody = pNode->GetSubNode( 2 ); //Body of the root assert(pBody); SmCaretPosGraphEntry *left, *right, *bodyLeft, *bodyRight; //Get left and save it assert(mpRightMost); left = mpRightMost; //Create body left bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); left->SetRight( bodyLeft ); //Create right right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); //Visit body mpRightMost = bodyLeft; pBody->Accept( this ); bodyRight = mpRightMost; bodyRight->SetRight( right ); right->SetLeft( bodyRight ); //Visit pExtra if( pExtra ){ mpRightMost = mpGraph->Add( SmCaretPos( pExtra, 0 ), left ); pExtra->Accept( this ); mpRightMost->SetRight( bodyLeft ); } mpRightMost = right; } /** Build SmCaretPosGraph for SmPlaceNode * Consider this a single character. */ void SmCaretPosGraphBuildingVisitor::Visit( SmPlaceNode* pNode ) { SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); mpRightMost->SetRight( right ); mpRightMost = right; } /** SmErrorNode is context dependent metadata, it can't be selected * * @remarks There's no point in deleting, copying and/or moving an instance * of SmErrorNode as it may not exist in another context! Thus there are no * positions to select an SmErrorNode. */ void SmCaretPosGraphBuildingVisitor::Visit( SmErrorNode* ) { } /** Build SmCaretPosGraph for SmBlankNode * Consider this a single character, as it is only a blank space */ void SmCaretPosGraphBuildingVisitor::Visit( SmBlankNode* pNode ) { SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); mpRightMost->SetRight( right ); mpRightMost = right; } /** Build SmCaretPosGraph for SmBraceNode * * Lines in an SmBraceNode: * \code * | | * | B | * | | * \endcode * B: Body * * Graph over these, where "left" is before the SmBraceNode and "right" is after: * \dot * digraph Graph{ * left -> B; * B -> right; * } * \enddot */ void SmCaretPosGraphBuildingVisitor::Visit( SmBraceNode* pNode ) { SmNode* pBody = pNode->Body(); SmCaretPosGraphEntry *left = mpRightMost, *right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); if( pBody->GetType() != SmNodeType::Bracebody ) { mpRightMost = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); left->SetRight( mpRightMost ); }else mpRightMost = left; pBody->Accept( this ); mpRightMost->SetRight( right ); right->SetLeft( mpRightMost ); mpRightMost = right; } /** Build SmCaretPosGraph for SmAttributNode * * Lines in an SmAttributNode: * \code * Attr * Body * \endcode * * There's a body and an attribute, the construction is used for "widehat A", where "A" is the body * and "^" is the attribute ( note GetScaleMode( ) on SmAttributNode tells how the attribute should be * scaled ). */ void SmCaretPosGraphBuildingVisitor::Visit( SmAttributNode* pNode ) { SmNode *pAttr = pNode->Attribute(), *pBody = pNode->Body(); assert(pAttr); assert(pBody); SmCaretPosGraphEntry *left = mpRightMost, *attrLeft, *bodyLeft, *bodyRight, *right; //Creating bodyleft bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); left->SetRight( bodyLeft ); //Creating right right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); //Visit the body mpRightMost = bodyLeft; pBody->Accept( this ); bodyRight = mpRightMost; bodyRight->SetRight( right ); right->SetLeft( bodyRight ); //Create attrLeft attrLeft = mpGraph->Add( SmCaretPos( pAttr, 0 ), left ); //Visit attribute mpRightMost = attrLeft; pAttr->Accept( this ); mpRightMost->SetRight( right ); //Set return value mpRightMost = right; } //Consider these single symbols void SmCaretPosGraphBuildingVisitor::Visit( SmSpecialNode* pNode ) { SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); mpRightMost->SetRight( right ); mpRightMost = right; } void SmCaretPosGraphBuildingVisitor::Visit( SmGlyphSpecialNode* pNode ) { SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); mpRightMost->SetRight( right ); mpRightMost = right; } void SmCaretPosGraphBuildingVisitor::Visit( SmMathSymbolNode* pNode ) { SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); mpRightMost->SetRight( right ); mpRightMost = right; } void SmCaretPosGraphBuildingVisitor::Visit( SmRootSymbolNode* ) { //Do nothing } void SmCaretPosGraphBuildingVisitor::Visit( SmRectangleNode* ) { //Do nothing } void SmCaretPosGraphBuildingVisitor::Visit( SmPolyLineNode* ) { //Do nothing } // SmCloningVisitor SmNode* SmCloningVisitor::Clone( SmNode* pNode ) { SmNode* pCurrResult = mpResult; pNode->Accept( this ); SmNode* pClone = mpResult; mpResult = pCurrResult; return pClone; } void SmCloningVisitor::CloneNodeAttr( SmNode const * pSource, SmNode* pTarget ) { pTarget->SetScaleMode( pSource->GetScaleMode( ) ); //Other attributes are set when prepare or arrange is executed //and may depend on stuff not being cloned here. } void SmCloningVisitor::CloneKids( SmStructureNode* pSource, SmStructureNode* pTarget ) { //Cache current result SmNode* pCurrResult = mpResult; //Create array for holding clones size_t nSize = pSource->GetNumSubNodes( ); SmNodeArray aNodes( nSize ); //Clone children for (size_t i = 0; i < nSize; ++i) { SmNode* pKid; if( nullptr != ( pKid = pSource->GetSubNode( i ) ) ) pKid->Accept( this ); else mpResult = nullptr; aNodes[i] = mpResult; } //Set subnodes of pTarget pTarget->SetSubNodes( std::move(aNodes) ); //Restore result as where prior to call mpResult = pCurrResult; } void SmCloningVisitor::Visit( SmTableNode* pNode ) { SmTableNode* pClone = new SmTableNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmBraceNode* pNode ) { SmBraceNode* pClone = new SmBraceNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmBracebodyNode* pNode ) { SmBracebodyNode* pClone = new SmBracebodyNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmOperNode* pNode ) { SmOperNode* pClone = new SmOperNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmAlignNode* pNode ) { SmAlignNode* pClone = new SmAlignNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmAttributNode* pNode ) { SmAttributNode* pClone = new SmAttributNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmFontNode* pNode ) { SmFontNode* pClone = new SmFontNode( pNode->GetToken( ) ); pClone->SetSizeParameter( pNode->GetSizeParameter( ), pNode->GetSizeType( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmUnHorNode* pNode ) { SmUnHorNode* pClone = new SmUnHorNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmBinHorNode* pNode ) { SmBinHorNode* pClone = new SmBinHorNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmBinVerNode* pNode ) { SmBinVerNode* pClone = new SmBinVerNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmBinDiagonalNode* pNode ) { SmBinDiagonalNode *pClone = new SmBinDiagonalNode( pNode->GetToken( ) ); pClone->SetAscending( pNode->IsAscending( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmSubSupNode* pNode ) { SmSubSupNode *pClone = new SmSubSupNode( pNode->GetToken( ) ); pClone->SetUseLimits( pNode->IsUseLimits( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmMatrixNode* pNode ) { SmMatrixNode *pClone = new SmMatrixNode( pNode->GetToken( ) ); pClone->SetRowCol( pNode->GetNumRows( ), pNode->GetNumCols( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmPlaceNode* pNode ) { mpResult = new SmPlaceNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmTextNode* pNode ) { SmTextNode* pClone = new SmTextNode( pNode->GetToken( ), pNode->GetFontDesc( ) ); pClone->ChangeText( pNode->GetText( ) ); CloneNodeAttr( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmSpecialNode* pNode ) { mpResult = new SmSpecialNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmGlyphSpecialNode* pNode ) { mpResult = new SmGlyphSpecialNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmMathSymbolNode* pNode ) { mpResult = new SmMathSymbolNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmBlankNode* pNode ) { SmBlankNode* pClone = new SmBlankNode( pNode->GetToken( ) ); pClone->SetBlankNum( pNode->GetBlankNum( ) ); mpResult = pClone; CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmErrorNode* pNode ) { mpResult = new SmErrorNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmLineNode* pNode ) { SmLineNode* pClone = new SmLineNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmExpressionNode* pNode ) { SmExpressionNode* pClone = new SmExpressionNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmPolyLineNode* pNode ) { mpResult = new SmPolyLineNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmRootNode* pNode ) { SmRootNode* pClone = new SmRootNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } void SmCloningVisitor::Visit( SmRootSymbolNode* pNode ) { mpResult = new SmRootSymbolNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmRectangleNode* pNode ) { mpResult = new SmRectangleNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, mpResult ); } void SmCloningVisitor::Visit( SmVerticalBraceNode* pNode ) { SmVerticalBraceNode* pClone = new SmVerticalBraceNode( pNode->GetToken( ) ); CloneNodeAttr( pNode, pClone ); CloneKids( pNode, pClone ); mpResult = pClone; } // SmSelectionDrawingVisitor SmSelectionDrawingVisitor::SmSelectionDrawingVisitor( OutputDevice& rDevice, SmNode* pTree, const Point& rOffset ) : mrDev( rDevice ) , mbHasSelectionArea( false ) { //Visit everything SAL_WARN_IF( !pTree, "starmath", "pTree can't be null!" ); if( pTree ) pTree->Accept( this ); //Draw selection if there's any if( !mbHasSelectionArea ) return; maSelectionArea.Move( rOffset.X( ), rOffset.Y( ) ); //Save device state mrDev.Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); //Change colors mrDev.SetLineColor( ); mrDev.SetFillColor( COL_LIGHTGRAY ); //Draw rectangle mrDev.DrawRect( maSelectionArea ); //Restore device state mrDev.Pop( ); } void SmSelectionDrawingVisitor::ExtendSelectionArea(const tools::Rectangle& rArea) { if ( ! mbHasSelectionArea ) { maSelectionArea = rArea; mbHasSelectionArea = true; } else maSelectionArea.Union(rArea); } void SmSelectionDrawingVisitor::DefaultVisit( SmNode* pNode ) { if( pNode->IsSelected( ) ) ExtendSelectionArea( pNode->AsRectangle( ) ); VisitChildren( pNode ); } void SmSelectionDrawingVisitor::VisitChildren( SmNode* pNode ) { if(pNode->GetNumSubNodes() == 0) return; for( auto pChild : *static_cast(pNode) ) { if(!pChild) continue; pChild->Accept( this ); } } void SmSelectionDrawingVisitor::Visit( SmTextNode* pNode ) { if( !pNode->IsSelected()) return; mrDev.Push( PushFlags::TEXTCOLOR | PushFlags::FONT ); mrDev.SetFont( pNode->GetFont( ) ); Point Position = pNode->GetTopLeft( ); tools::Long left = Position.getX( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, pNode->GetSelectionStart( ) ); tools::Long right = Position.getX( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, pNode->GetSelectionEnd( ) ); tools::Long top = Position.getY( ); tools::Long bottom = top + pNode->GetHeight( ); tools::Rectangle rect( left, top, right, bottom ); ExtendSelectionArea( rect ); mrDev.Pop( ); } // SmNodeToTextVisitor SmNodeToTextVisitor::SmNodeToTextVisitor( SmNode* pNode, OUString &rText ) { pNode->Accept( this ); maCmdText.stripEnd(' '); rText = maCmdText.makeStringAndClear(); } void SmNodeToTextVisitor::Visit( SmTableNode* pNode ) { if( pNode->GetToken( ).eType == TBINOM ) { Append( "{ binom" ); LineToText( pNode->GetSubNode( 0 ) ); LineToText( pNode->GetSubNode( 1 ) ); Append("} "); } else if( pNode->GetToken( ).eType == TSTACK ) { Append( "stack{ " ); bool bFirst = true; for( auto pChild : *pNode ) { if(!pChild) continue; if(bFirst) bFirst = false; else { Separate( ); Append( "# " ); } LineToText( pChild ); } Separate( ); Append( "}" ); } else { //Assume it's a toplevel table, containing lines bool bFirst = true; for( auto pChild : *pNode ) { if(!pChild) continue; if(bFirst) bFirst = false; else { Separate( ); Append( "newline" ); } Separate( ); pChild->Accept( this ); } } } void SmNodeToTextVisitor::Visit( SmBraceNode* pNode ) { if ( pNode->GetToken().eType == TEVALUATE ) { SmNode *pBody = pNode->Body(); Append( "evaluate { " ); pBody->Accept( this ); Append("} "); } else{ SmNode *pLeftBrace = pNode->OpeningBrace(), *pBody = pNode->Body(), *pRightBrace = pNode->ClosingBrace(); //Handle special case where it's absolute function if( pNode->GetToken( ).eType == TABS ) { Append( "abs" ); LineToText( pBody ); } else { if( pNode->GetScaleMode( ) == SmScaleMode::Height ) Append( "left " ); pLeftBrace->Accept( this ); Separate( ); pBody->Accept( this ); Separate( ); if( pNode->GetScaleMode( ) == SmScaleMode::Height ) Append( "right " ); pRightBrace->Accept( this ); } } } void SmNodeToTextVisitor::Visit( SmBracebodyNode* pNode ) { for( auto pChild : *pNode ) { if(!pChild) continue; Separate( ); pChild->Accept( this ); } } void SmNodeToTextVisitor::Visit( SmOperNode* pNode ) { Append( pNode->GetToken( ).aText ); Separate( ); if( pNode->GetToken( ).eType == TOPER ){ //There's an SmGlyphSpecialNode if eType == TOPER if( pNode->GetSubNode( 0 )->GetType( ) == SmNodeType::SubSup ) Append( pNode->GetSubNode( 0 )->GetSubNode( 0 )->GetToken( ).aText ); else Append( pNode->GetSubNode( 0 )->GetToken( ).aText ); } if( pNode->GetSubNode( 0 )->GetType( ) == SmNodeType::SubSup ) { SmSubSupNode *pSubSup = static_cast( pNode->GetSubNode( 0 ) ); SmNode* pChild = pSubSup->GetSubSup( LSUP ); if( pChild ) { Separate( ); Append( "lsup { " ); LineToText( pChild ); Append( "} " ); } pChild = pSubSup->GetSubSup( LSUB ); if( pChild ) { Separate( ); Append( "lsub { " ); LineToText( pChild ); Append( "} " ); } pChild = pSubSup->GetSubSup( RSUP ); if( pChild ) { Separate( ); Append( "^ { " ); LineToText( pChild ); Append( "} " ); } pChild = pSubSup->GetSubSup( RSUB ); if( pChild ) { Separate( ); Append( "_ { " ); LineToText( pChild ); Append( "} " ); } pChild = pSubSup->GetSubSup( CSUP ); if( pChild ) { Separate( ); if (pSubSup->IsUseLimits()) Append( "to { " ); else Append( "csup { " ); LineToText( pChild ); Append( "} " ); } pChild = pSubSup->GetSubSup( CSUB ); if( pChild ) { Separate( ); if (pSubSup->IsUseLimits()) Append( "from { " ); else Append( "csub { " ); LineToText( pChild ); Append( "} " ); } } LineToText( pNode->GetSubNode( 1 ) ); } void SmNodeToTextVisitor::Visit( SmAlignNode* pNode ) { Append( pNode->GetToken( ).aText ); LineToText( pNode->GetSubNode( 0 ) ); } void SmNodeToTextVisitor::Visit( SmAttributNode* pNode ) { Append( pNode->GetToken( ).aText ); LineToText( pNode->Body() ); } void SmNodeToTextVisitor::Visit( SmFontNode* pNode ) { sal_uInt32 nc; sal_uInt8 nr, ng, nb; std::unique_ptr aSmColorTokenTableEntry; switch ( pNode->GetToken( ).eType ) { case TBOLD: Append( "bold " ); break; case TNBOLD: Append( "nbold " ); break; case TITALIC: Append( "italic " ); break; case TNITALIC: Append( "nitalic " ); break; case TPHANTOM: Append( "phantom " ); break; case TSIZE: { Append( "size " ); switch ( pNode->GetSizeType( ) ) { case FontSizeType::PLUS: Append( "+" ); break; case FontSizeType::MINUS: Append( "-" ); break; case FontSizeType::MULTIPLY: Append( "*" ); break; case FontSizeType::DIVIDE: Append( "/" ); break; case FontSizeType::ABSOLUT: default: break; } Append( ::rtl::math::doubleToUString( static_cast( pNode->GetSizeParameter( ) ), rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true ) ); Separate( ); } break; case TDVIPSNAMESCOL: Append( "color dvip " ); nc = pNode->GetToken().aText.toUInt32(16); aSmColorTokenTableEntry = starmathdatabase::Identify_Color_Parser( nc ); Append( aSmColorTokenTableEntry->pIdent ); break; case THTMLCOL: case TMATHMLCOL: case TICONICCOL: Append( "color " ); nc = pNode->GetToken().aText.toUInt32(16); aSmColorTokenTableEntry = starmathdatabase::Identify_Color_Parser( nc ); Append( aSmColorTokenTableEntry->pIdent ); break; case TRGB: nc = pNode->GetToken().aText.toUInt32(16); aSmColorTokenTableEntry = starmathdatabase::Identify_Color_Parser( nc ); Append( "color rgb " ); nb = nc % 256; nc /= 256; ng = nc % 256; nc /= 256; nr = nc % 256; Append(OUString::number(nr)); Separate(); Append(OUString::number(ng)); Separate(); Append(OUString::number(nb)); Separate(); break; case TRGBA: nc = pNode->GetToken().aText.toUInt32(16); aSmColorTokenTableEntry = starmathdatabase::Identify_Color_Parser( nc ); Append( "color rgba " ); nc = pNode->GetToken().aText.toUInt32(16); nb = nc % 256; nc /= 256; ng = nc % 256; nc /= 256; nr = nc % 256; nc /= 256; Append(OUString::number(nr)); Separate(); Append(OUString::number(ng)); Separate(); Append(OUString::number(nb)); Separate(); Append(OUString::number(nc)); Separate(); break; case THEX: nc = pNode->GetToken().aText.toUInt32(16); aSmColorTokenTableEntry = starmathdatabase::Identify_Color_Parser( nc ); Append( "color hex " ); nc = pNode->GetToken().aText.toUInt32(16); Append(OUString::number(nc,16)); Separate(); break; case TSANS: Append( "font sans " ); break; case TSERIF: Append( "font serif " ); break; case TFIXED: Append( "font fixed " ); break; default: break; } LineToText( pNode->GetSubNode( 1 ) ); } void SmNodeToTextVisitor::Visit( SmUnHorNode* pNode ) { if(pNode->GetSubNode( 1 )->GetToken( ).eType == TFACT) { // visit children in the reverse order for( auto it = pNode->rbegin(); it != pNode->rend(); ++it ) { auto pChild = *it; if(!pChild) continue; Separate( ); pChild->Accept( this ); } } else { for( auto pChild : *pNode ) { if(!pChild) continue; Separate( ); pChild->Accept( this ); } } } void SmNodeToTextVisitor::Visit( SmBinHorNode* pNode ) { const SmNode *pParent = pNode->GetParent(); bool bBraceNeeded = pParent && pParent->GetType() == SmNodeType::Font; SmNode *pLeft = pNode->LeftOperand(), *pOper = pNode->Symbol(), *pRight = pNode->RightOperand(); Separate( ); if (bBraceNeeded) Append( "{ " ); pLeft->Accept( this ); Separate( ); pOper->Accept( this ); Separate( ); pRight->Accept( this ); Separate( ); if (bBraceNeeded) Append( "} " ); } void SmNodeToTextVisitor::Visit( SmBinVerNode* pNode ) { if( pNode->GetToken().eType == TOVER ){ SmNode *pNum = pNode->GetSubNode( 0 ), *pDenom = pNode->GetSubNode( 2 ); Append( "{ " ); LineToText( pNum ); Append( "over" ); LineToText( pDenom ); Append( "} " ); } else{ SmNode *pNum = pNode->GetSubNode( 0 ), *pDenom = pNode->GetSubNode( 2 ); Append( "frac {" ); LineToText( pNum ); Append( "} {" ); LineToText( pDenom ); Append( "}" ); } } void SmNodeToTextVisitor::Visit( SmBinDiagonalNode* pNode ) { SmNode *pLeftOperand = pNode->GetSubNode( 0 ), *pRightOperand = pNode->GetSubNode( 1 ); Append( "{ " ); LineToText( pLeftOperand ); Separate( ); Append( "wideslash " ); LineToText( pRightOperand ); Append( "} " ); } void SmNodeToTextVisitor::Visit( SmSubSupNode* pNode ) { if( pNode->GetToken().eType == TEVALUATE ) { Append("evaluate { "); pNode->GetSubNode( 0 )->GetSubNode( 1 )->Accept(this); Append("} "); SmNode* pChild = pNode->GetSubSup( RSUP ); if( pChild ) { Separate( ); Append( "to { " ); LineToText( pChild ); Append( "} " ); } pChild = pNode->GetSubSup( RSUB ); if( pChild ) { Separate( ); Append( "from { " ); LineToText( pChild ); Append( "} " ); } } else { LineToText( pNode->GetBody( ) ); SmNode *pChild = pNode->GetSubSup( LSUP ); if( pChild ) { Separate( ); Append( "lsup " ); LineToText( pChild ); } pChild = pNode->GetSubSup( LSUB ); if( pChild ) { Separate( ); Append( "lsub " ); LineToText( pChild ); } pChild = pNode->GetSubSup( RSUP ); if( pChild ) { Separate( ); Append( "^ " ); LineToText( pChild ); } pChild = pNode->GetSubSup( RSUB ); if( pChild ) { Separate( ); Append( "_ " ); LineToText( pChild ); } pChild = pNode->GetSubSup( CSUP ); if( pChild ) { Separate( ); if (pNode->IsUseLimits()) Append( "to " ); else Append( "csup " ); LineToText( pChild ); } pChild = pNode->GetSubSup( CSUB ); if( pChild ) { Separate( ); if (pNode->IsUseLimits()) Append( "from " ); else Append( "csub " ); LineToText( pChild ); } } } void SmNodeToTextVisitor::Visit( SmMatrixNode* pNode ) { Append( "matrix{" ); for (size_t i = 0; i < pNode->GetNumRows(); ++i) { for (size_t j = 0; j < pNode->GetNumCols( ); ++j) { SmNode* pSubNode = pNode->GetSubNode( i * pNode->GetNumCols( ) + j ); Separate( ); if (pSubNode) pSubNode->Accept( this ); Separate( ); if (j != pNode->GetNumCols() - 1U) Append( "#" ); } Separate( ); if (i != pNode->GetNumRows() - 1U) Append( "##" ); } Append( "} " ); } void SmNodeToTextVisitor::Visit( SmPlaceNode* ) { Append( "" ); } void SmNodeToTextVisitor::Visit( SmTextNode* pNode ) { SmTokenType type = pNode->GetToken( ).eType; switch(type){ case TTEXT: Append( "\"" ); Append( pNode->GetToken().aText ); Append( "\"" ); break; case TNUMBER: Append( pNode->GetToken().aText ); break; case TIDENT: Append( pNode->GetToken().aText ); break; case TFUNC: Append("func "); Append( pNode->GetToken().aText ); break; case THEX: Append("hex "); Append( pNode->GetToken().aText ); break; default: Append( pNode->GetToken().aText ); } Separate( ); } void SmNodeToTextVisitor::Visit( SmSpecialNode* pNode ) { SmTokenType type = pNode->GetToken().eType; switch(type){ case TLIMSUP: Append("lim sup "); break; case TLIMINF: Append("lim inf "); break; default: Append( pNode->GetToken().aText ); break; } } void SmNodeToTextVisitor::Visit( SmGlyphSpecialNode* pNode ) { if( pNode->GetToken( ).eType == TBOPER ) Append( "boper " ); else Append( "uoper " ); Append( pNode->GetToken( ).aText ); } //TODO to improve this it is required to improve mathmlimport. void SmNodeToTextVisitor::Visit( SmMathSymbolNode* pNode ) { if ( ( pNode->GetToken().nGroup & TG::LBrace ) || ( pNode->GetToken().nGroup & TG::RBrace ) || ( pNode->GetToken().nGroup & TG::Sum ) || ( pNode->GetToken().nGroup & TG::Product ) || ( pNode->GetToken().nGroup & TG::Relation ) || ( pNode->GetToken().nGroup & TG::UnOper ) || ( pNode->GetToken().nGroup & TG::Oper ) ) { Append( pNode->GetToken().aText ); return; } sal_Unicode cChar = pNode->GetToken().cMathChar; Separate( ); switch(cChar){ case MS_NONE: Append("none"); break; case '{': Append("{"); break; case '}': Append("}"); break; case MS_VERTLINE: Append("mline"); break; case MS_TILDE: Append("\"~\""); break; case MS_RIGHTARROW: if( pNode->GetToken().eType == TTOWARD ) Append("toward"); else Append("rightarrow"); break; case MS_LEFTARROW: Append("leftarrow"); break; case MS_UPARROW: Append("uparrow"); break; case MS_DOWNARROW: Append("downarrow"); break; case MS_LAMBDABAR: Append("lambdabar"); break; case MS_DOTSLOW: Append("dotslow"); break; case MS_SETC: Append("setC"); break; case MS_HBAR: Append("hbar"); break; case MS_IM: Append("Im"); break; case MS_SETN: Append("setN"); break; case MS_WP: Append("wp"); break; case MS_LAPLACE: Append("laplace"); break; case MS_SETQ: Append("setQ"); break; case MS_RE: Append("Re"); break; case MS_SETR: Append("setR"); break; case MS_SETZ: Append("setZ"); break; case MS_ALEPH: Append("aleph"); break; case 0x0362: Append("widevec"); break; case MS_DLARROW: Append("dlarrow"); break; case MS_DRARROW: Append("drarrow"); break; case MS_DLRARROW: Append("dlrarrow"); break; case MS_FORALL: Append("forall"); break; case MS_PARTIAL: Append("partial"); break; case MS_EXISTS: Append("exists"); break; case MS_NOTEXISTS: Append("notexists"); break; case MS_EMPTYSET: Append("emptyset"); break; case MS_NABLA: Append("nabla"); break; case MS_BACKEPSILON: Append("backepsilon"); break; case MS_CIRC: Append("circ"); break; case MS_INFINITY: Append("infinity"); break; case 0x22b2: // NORMAL SUBGROUP OF Append(OUStringChar(cChar)); break; case 0x22b3: // CONTAINS AS NORMAL SUBGROUP Append(OUStringChar(cChar)); break; case MS_ORTHO: Append("ortho"); break; case MS_DOTSVERT: Append("dotsvert"); break; case MS_DOTSAXIS: Append("dotsaxis"); break; case MS_DOTSUP: Append("dotsup"); break; case MS_DOTSDOWN: Append("dotsdown"); break; case '^': Append("^"); break; case 0xe091: Append("widehat"); break; case 0xe096: Append("widetilde"); break; case 0xe098: Append("widevec"); break; case 0xeb01: //no space case 0xeb08: //normal space break; case 0xef04: //tiny space case 0xef05: //tiny space case 0xeb02: //small space case 0xeb04: //medium space Append("`"); break; case 0xeb05: //large space Append("~"); break; case 0x3a9: Append("%OMEGA"); break; default: Append(OUStringChar(cChar)); break; } } void SmNodeToTextVisitor::Visit( SmBlankNode* pNode ) { sal_uInt16 nNum = pNode->GetBlankNum(); if (nNum <= 0) return; sal_uInt16 nWide = nNum / 4; sal_uInt16 nNarrow = nNum % 4; for (sal_uInt16 i = 0; i < nWide; i++) Append( "~" ); for (sal_uInt16 i = 0; i < nNarrow; i++) Append( "`" ); Append( " " ); } void SmNodeToTextVisitor::Visit( SmErrorNode* ) { } void SmNodeToTextVisitor::Visit( SmLineNode* pNode ) { for( auto pChild : *pNode ) { if(!pChild) continue; Separate( ); pChild->Accept( this ); } } void SmNodeToTextVisitor::Visit( SmExpressionNode* pNode ) { bool bracketsNeeded = pNode->GetNumSubNodes() != 1 || pNode->GetSubNode(0)->GetType() == SmNodeType::BinHor; if (!bracketsNeeded) { const SmNode *pParent = pNode->GetParent(); // nested subsups bracketsNeeded = pParent && pParent->GetType() == SmNodeType::SubSup && pNode->GetNumSubNodes() == 1 && pNode->GetSubNode(0)->GetType() == SmNodeType::SubSup; } if (bracketsNeeded) { Append( "{ " ); } for( auto pChild : *pNode ) { if(!pChild) continue; pChild->Accept( this ); Separate( ); } if (bracketsNeeded) { Append( "} " ); } } void SmNodeToTextVisitor::Visit( SmPolyLineNode* ) { } void SmNodeToTextVisitor::Visit( SmRootNode* pNode ) { SmNode *pExtra = pNode->GetSubNode( 0 ), *pBody = pNode->GetSubNode( 2 ); if( pExtra ) { Append( "nroot" ); LineToText( pExtra ); } else Append( "sqrt" ); LineToText( pBody ); } void SmNodeToTextVisitor::Visit( SmRootSymbolNode* ) { } void SmNodeToTextVisitor::Visit( SmRectangleNode* ) { } void SmNodeToTextVisitor::Visit( SmVerticalBraceNode* pNode ) { SmNode *pBody = pNode->Body(), *pScript = pNode->Script(); LineToText( pBody ); Append( pNode->GetToken( ).aText ); LineToText( pScript ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */