1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <rtl/math.hxx>
11 #include <sal/log.hxx>
12 #include <tools/gen.hxx>
13 #include <vcl/lineinfo.hxx>
14 #include <visitors.hxx>
15 #include "tmpdevice.hxx"
18 #include <starmathdatabase.hxx>
20 // SmDefaultingVisitor
22 void SmDefaultingVisitor::Visit( SmTableNode
* pNode
)
24 DefaultVisit( pNode
);
27 void SmDefaultingVisitor::Visit( SmBraceNode
* pNode
)
29 DefaultVisit( pNode
);
32 void SmDefaultingVisitor::Visit( SmBracebodyNode
* pNode
)
34 DefaultVisit( pNode
);
37 void SmDefaultingVisitor::Visit( SmOperNode
* pNode
)
39 DefaultVisit( pNode
);
42 void SmDefaultingVisitor::Visit( SmAlignNode
* pNode
)
44 DefaultVisit( pNode
);
47 void SmDefaultingVisitor::Visit( SmAttributeNode
* pNode
)
49 DefaultVisit( pNode
);
52 void SmDefaultingVisitor::Visit( SmFontNode
* pNode
)
54 DefaultVisit( pNode
);
57 void SmDefaultingVisitor::Visit( SmUnHorNode
* pNode
)
59 DefaultVisit( pNode
);
62 void SmDefaultingVisitor::Visit( SmBinHorNode
* pNode
)
64 DefaultVisit( pNode
);
67 void SmDefaultingVisitor::Visit( SmBinVerNode
* pNode
)
69 DefaultVisit( pNode
);
72 void SmDefaultingVisitor::Visit( SmBinDiagonalNode
* pNode
)
74 DefaultVisit( pNode
);
77 void SmDefaultingVisitor::Visit( SmSubSupNode
* pNode
)
79 DefaultVisit( pNode
);
82 void SmDefaultingVisitor::Visit( SmMatrixNode
* pNode
)
84 DefaultVisit( pNode
);
87 void SmDefaultingVisitor::Visit( SmPlaceNode
* pNode
)
89 DefaultVisit( pNode
);
92 void SmDefaultingVisitor::Visit( SmTextNode
* pNode
)
94 DefaultVisit( pNode
);
97 void SmDefaultingVisitor::Visit( SmSpecialNode
* pNode
)
99 DefaultVisit( pNode
);
102 void SmDefaultingVisitor::Visit( SmGlyphSpecialNode
* pNode
)
104 DefaultVisit( pNode
);
107 void SmDefaultingVisitor::Visit( SmMathSymbolNode
* pNode
)
109 DefaultVisit( pNode
);
112 void SmDefaultingVisitor::Visit( SmBlankNode
* pNode
)
114 DefaultVisit( pNode
);
117 void SmDefaultingVisitor::Visit( SmErrorNode
* pNode
)
119 DefaultVisit( pNode
);
122 void SmDefaultingVisitor::Visit( SmLineNode
* pNode
)
124 DefaultVisit( pNode
);
127 void SmDefaultingVisitor::Visit( SmExpressionNode
* pNode
)
129 DefaultVisit( pNode
);
132 void SmDefaultingVisitor::Visit( SmPolyLineNode
* pNode
)
134 DefaultVisit( pNode
);
137 void SmDefaultingVisitor::Visit( SmRootNode
* pNode
)
139 DefaultVisit( pNode
);
142 void SmDefaultingVisitor::Visit( SmRootSymbolNode
* pNode
)
144 DefaultVisit( pNode
);
147 void SmDefaultingVisitor::Visit( SmRectangleNode
* pNode
)
149 DefaultVisit( pNode
);
152 void SmDefaultingVisitor::Visit( SmVerticalBraceNode
* pNode
)
154 DefaultVisit( pNode
);
157 // SmCaretLinesVisitor
159 SmCaretLinesVisitor::SmCaretLinesVisitor(OutputDevice
& rDevice
, SmCaretPos position
, Point offset
)
166 void SmCaretLinesVisitor::DoIt()
168 SAL_WARN_IF(!maPos
.IsValid(), "starmath", "Cannot draw invalid position!");
169 if (!maPos
.IsValid())
173 mrDev
.Push( vcl::PushFlags::FONT
| vcl::PushFlags::MAPMODE
| vcl::PushFlags::LINECOLOR
| vcl::PushFlags::FILLCOLOR
| vcl::PushFlags::TEXTCOLOR
);
175 maPos
.pSelectedNode
->Accept( this );
176 //Restore device state
180 void SmCaretLinesVisitor::Visit( SmTextNode
* pNode
)
182 tools::Long i
= maPos
.nIndex
;
184 mrDev
.SetFont( pNode
->GetFont( ) );
187 SmNode
* pLine
= SmCursor::FindTopMostNodeInLine( pNode
);
190 tools::Long left
= pNode
->GetLeft( ) + mrDev
.GetTextWidth( pNode
->GetText( ), 0, i
) + maOffset
.X( );
191 tools::Long top
= pLine
->GetTop( ) + maOffset
.Y( );
192 tools::Long height
= pLine
->GetHeight( );
193 tools::Long left_line
= pLine
->GetLeft( ) + maOffset
.X( );
194 tools::Long right_line
= pLine
->GetRight( ) + maOffset
.X( );
197 ProcessCaretLine({ left
, top
}, { left
, top
+ height
});
200 ProcessUnderline({ left_line
, top
+ height
}, { right_line
, top
+ height
});
203 void SmCaretLinesVisitor::DefaultVisit( SmNode
* pNode
)
206 SmNode
* pLine
= SmCursor::FindTopMostNodeInLine( pNode
);
209 tools::Long left
= pNode
->GetLeft( ) + maOffset
.X( ) + ( maPos
.nIndex
== 1 ? pNode
->GetWidth( ) : 0 );
210 tools::Long top
= pLine
->GetTop( ) + maOffset
.Y( );
211 tools::Long height
= pLine
->GetHeight( );
212 tools::Long left_line
= pLine
->GetLeft( ) + maOffset
.X( );
213 tools::Long right_line
= pLine
->GetRight( ) + maOffset
.X( );
216 ProcessCaretLine({ left
, top
}, { left
, top
+ height
});
219 ProcessUnderline({ left_line
, top
+ height
}, { right_line
, top
+ height
});
222 // SmCaretRectanglesVisitor
224 SmCaretRectanglesVisitor::SmCaretRectanglesVisitor(OutputDevice
& rDevice
, SmCaretPos position
)
225 : SmCaretLinesVisitor(rDevice
, position
, {})
230 void SmCaretRectanglesVisitor::ProcessCaretLine(Point from
, Point to
) { maCaret
= { from
, to
}; }
231 void SmCaretRectanglesVisitor::ProcessUnderline(Point
/*from*/, Point
/*to*/) {} // No underline
233 // SmCaretDrawingVisitor
235 SmCaretDrawingVisitor::SmCaretDrawingVisitor( OutputDevice
& rDevice
,
239 : SmCaretLinesVisitor(rDevice
, position
, offset
)
240 , mbCaretVisible( caretVisible
)
245 void SmCaretDrawingVisitor::ProcessCaretLine(Point from
, Point to
)
247 if ( mbCaretVisible
) {
249 getDev().SetLineColor(COL_BLACK
);
251 getDev().DrawLine(from
, to
);
255 void SmCaretDrawingVisitor::ProcessUnderline(Point from
, Point to
)
258 getDev().SetLineColor(COL_BLACK
);
260 getDev().DrawLine(from
, to
);
263 // SmCaretPos2LineVisitor
265 void SmCaretPos2LineVisitor::Visit( SmTextNode
* pNode
)
268 mpDev
->Push( vcl::PushFlags::FONT
| vcl::PushFlags::TEXTCOLOR
);
270 tools::Long i
= maPos
.nIndex
;
272 mpDev
->SetFont( pNode
->GetFont( ) );
275 tools::Long left
= pNode
->GetLeft( ) + mpDev
->GetTextWidth( pNode
->GetText( ), 0, i
);
276 tools::Long top
= pNode
->GetTop( );
277 tools::Long height
= pNode
->GetHeight( );
279 maLine
= SmCaretLine( left
, top
, height
);
281 //Restore device state
285 void SmCaretPos2LineVisitor::DefaultVisit( SmNode
* pNode
)
287 //Vertical line ( code from SmCaretDrawingVisitor )
288 Point p1
= pNode
->GetTopLeft( );
289 if( maPos
.nIndex
== 1 )
290 p1
.Move( pNode
->GetWidth( ), 0 );
292 maLine
= SmCaretLine( p1
.X( ), p1
.Y( ), pNode
->GetHeight( ) );
298 void SmDrawingVisitor::Visit( SmTableNode
* pNode
)
300 DrawChildren( pNode
);
303 void SmDrawingVisitor::Visit( SmBraceNode
* pNode
)
305 DrawChildren( pNode
);
308 void SmDrawingVisitor::Visit( SmBracebodyNode
* pNode
)
310 DrawChildren( pNode
);
313 void SmDrawingVisitor::Visit( SmOperNode
* pNode
)
315 DrawChildren( pNode
);
318 void SmDrawingVisitor::Visit( SmAlignNode
* pNode
)
320 DrawChildren( pNode
);
323 void SmDrawingVisitor::Visit( SmAttributeNode
* pNode
)
325 DrawChildren( pNode
);
328 void SmDrawingVisitor::Visit( SmFontNode
* pNode
)
330 DrawChildren( pNode
);
333 void SmDrawingVisitor::Visit( SmUnHorNode
* pNode
)
335 DrawChildren( pNode
);
338 void SmDrawingVisitor::Visit( SmBinHorNode
* pNode
)
340 DrawChildren( pNode
);
343 void SmDrawingVisitor::Visit( SmBinVerNode
* pNode
)
345 DrawChildren( pNode
);
348 void SmDrawingVisitor::Visit( SmBinDiagonalNode
* pNode
)
350 DrawChildren( pNode
);
353 void SmDrawingVisitor::Visit( SmSubSupNode
* pNode
)
355 DrawChildren( pNode
);
358 void SmDrawingVisitor::Visit( SmMatrixNode
* pNode
)
360 DrawChildren( pNode
);
363 void SmDrawingVisitor::Visit( SmPlaceNode
* pNode
)
365 DrawSpecialNode( pNode
);
368 void SmDrawingVisitor::Visit( SmTextNode
* pNode
)
370 DrawTextNode( pNode
);
373 void SmDrawingVisitor::Visit( SmSpecialNode
* pNode
)
375 DrawSpecialNode( pNode
);
378 void SmDrawingVisitor::Visit( SmGlyphSpecialNode
* pNode
)
380 DrawSpecialNode( pNode
);
383 void SmDrawingVisitor::Visit( SmMathSymbolNode
* pNode
)
385 DrawSpecialNode( pNode
);
388 void SmDrawingVisitor::Visit( SmBlankNode
* )
392 void SmDrawingVisitor::Visit( SmErrorNode
* pNode
)
394 DrawSpecialNode( pNode
);
397 void SmDrawingVisitor::Visit( SmLineNode
* pNode
)
399 DrawChildren( pNode
);
402 void SmDrawingVisitor::Visit( SmExpressionNode
* pNode
)
404 DrawChildren( pNode
);
407 void SmDrawingVisitor::Visit( SmRootNode
* pNode
)
409 DrawChildren( pNode
);
412 void SmDrawingVisitor::Visit( SmVerticalBraceNode
* pNode
)
414 DrawChildren( pNode
);
417 void SmDrawingVisitor::Visit( SmRootSymbolNode
* pNode
)
419 if ( pNode
->IsPhantom( ) )
422 // draw root-sign itself
423 DrawSpecialNode( pNode
);
425 SmTmpDevice
aTmpDev( mrDev
, true );
426 aTmpDev
.SetFillColor( pNode
->GetFont( ).GetColor( ) );
427 mrDev
.SetLineColor( );
428 aTmpDev
.SetFont( pNode
->GetFont( ) );
430 // since the width is always unscaled it corresponds to the _original_
431 // _unscaled_ font height to be used, we use that to calculate the
432 // bar height. Thus it is independent of the arguments height.
433 // ( see display of sqrt QQQ versus sqrt stack{Q#Q#Q#Q} )
434 tools::Long nBarHeight
= pNode
->GetWidth( ) * 7 / 100;
435 tools::Long nBarWidth
= pNode
->GetBodyWidth( ) + pNode
->GetBorderWidth( );
436 Point
aBarOffset( pNode
->GetWidth( ), +pNode
->GetBorderWidth( ) );
437 Point
aBarPos( maPosition
+ aBarOffset
);
439 tools::Rectangle
aBar( aBarPos
, Size( nBarWidth
, nBarHeight
) );
441 if (mrFormat
.IsRightToLeft() && mrDev
.GetOutDevType() != OUTDEV_WINDOW
)
442 mrDev
.ReMirror(aBar
);
444 mrDev
.DrawRect( aBar
);
447 void SmDrawingVisitor::Visit( SmPolyLineNode
* pNode
)
449 if ( pNode
->IsPhantom( ) )
452 tools::Long nBorderwidth
= pNode
->GetFont( ).GetBorderWidth( );
455 aInfo
.SetWidth( pNode
->GetWidth( ) - 2 * nBorderwidth
);
457 Point
aOffset ( Point( ) - pNode
->GetPolygon( ).GetBoundRect( ).TopLeft( )
458 + Point( nBorderwidth
, nBorderwidth
) ),
459 aPos ( maPosition
+ aOffset
);
461 if (mrFormat
.IsRightToLeft() && mrDev
.GetOutDevType() != OUTDEV_WINDOW
)
462 mrDev
.ReMirror(aPos
);
464 pNode
->GetPolygon( ).Move( aPos
.X( ), aPos
.Y( ) ); //Works because Polygon wraps a pointer
466 SmTmpDevice
aTmpDev ( mrDev
, false );
467 aTmpDev
.SetLineColor( pNode
->GetFont( ).GetColor( ) );
469 mrDev
.DrawPolyLine( pNode
->GetPolygon( ), aInfo
);
472 void SmDrawingVisitor::Visit( SmRectangleNode
* pNode
)
474 if ( pNode
->IsPhantom( ) )
477 SmTmpDevice
aTmpDev ( mrDev
, false );
478 aTmpDev
.SetFillColor( pNode
->GetFont( ).GetColor( ) );
479 mrDev
.SetLineColor( );
480 aTmpDev
.SetFont( pNode
->GetFont( ) );
482 sal_uLong nTmpBorderWidth
= pNode
->GetFont( ).GetBorderWidth( );
484 // get rectangle and remove borderspace
485 tools::Rectangle
aTmp ( pNode
->AsRectangle( ) + maPosition
- pNode
->GetTopLeft( ) );
486 aTmp
.AdjustLeft(nTmpBorderWidth
);
487 aTmp
.AdjustRight( -sal_Int32(nTmpBorderWidth
) );
488 aTmp
.AdjustTop(nTmpBorderWidth
);
489 aTmp
.AdjustBottom( -sal_Int32(nTmpBorderWidth
) );
491 SAL_WARN_IF( aTmp
.IsEmpty(), "starmath", "Empty rectangle" );
493 if (mrFormat
.IsRightToLeft() && mrDev
.GetOutDevType() != OUTDEV_WINDOW
)
494 mrDev
.ReMirror(aTmp
);
496 mrDev
.DrawRect( aTmp
);
499 void SmDrawingVisitor::DrawTextNode( SmTextNode
* pNode
)
501 if ( pNode
->IsPhantom() || pNode
->GetText().isEmpty() || pNode
->GetText()[0] == '\0' )
504 SmTmpDevice
aTmpDev ( mrDev
, false );
505 aTmpDev
.SetFont( pNode
->GetFont( ) );
507 Point
aPos ( maPosition
);
508 aPos
.AdjustY(pNode
->GetBaselineOffset( ) );
510 if (mrFormat
.IsRightToLeft() && mrDev
.GetOutDevType() != OUTDEV_WINDOW
)
511 mrDev
.ReMirror(aPos
);
513 mrDev
.DrawStretchText( aPos
, pNode
->GetWidth( ), pNode
->GetText( ) );
516 void SmDrawingVisitor::DrawSpecialNode( SmSpecialNode
* pNode
)
518 //! since this chars might come from any font, that we may not have
519 //! set to ALIGN_BASELINE yet, we do it now.
520 pNode
->GetFont( ).SetAlignment( ALIGN_BASELINE
);
522 DrawTextNode( pNode
);
525 void SmDrawingVisitor::DrawChildren( SmStructureNode
* pNode
)
527 if ( pNode
->IsPhantom( ) )
530 Point rPosition
= maPosition
;
532 for( auto pChild
: *pNode
)
536 Point
aOffset ( pChild
->GetTopLeft( ) - pNode
->GetTopLeft( ) );
537 maPosition
= rPosition
+ aOffset
;
538 pChild
->Accept( this );
542 // SmSetSelectionVisitor
544 SmSetSelectionVisitor::SmSetSelectionVisitor( SmCaretPos startPos
, SmCaretPos endPos
, SmNode
* pTree
)
545 : maStartPos(startPos
)
549 //Assume that pTree is a SmTableNode
550 SAL_WARN_IF(pTree
->GetType() != SmNodeType::Table
, "starmath", "pTree should be a SmTableNode!");
551 //Visit root node, this is special as this node cannot be selected, but its children can!
552 if(pTree
->GetType() == SmNodeType::Table
){
553 //Change state if maStartPos is in front of this node
554 if( maStartPos
.pSelectedNode
== pTree
&& maStartPos
.nIndex
== 0 )
555 mbSelecting
= !mbSelecting
;
556 //Change state if maEndPos is in front of this node
557 if( maEndPos
.pSelectedNode
== pTree
&& maEndPos
.nIndex
== 0 )
558 mbSelecting
= !mbSelecting
;
559 SAL_WARN_IF(mbSelecting
, "starmath", "Caret positions needed to set mbSelecting about, shouldn't be possible!");
562 for( auto pChild
: *static_cast<SmStructureNode
*>(pTree
) )
566 pChild
->Accept( this );
567 //If we started a selection in this line and it haven't ended, we do that now!
570 SetSelectedOnAll(pChild
);
571 //Set maStartPos and maEndPos to invalid positions, this ensures that an unused
572 //start or end (because we forced end above), doesn't start a new selection.
573 maStartPos
= maEndPos
= SmCaretPos();
576 //Check if pTree isn't selected
577 SAL_WARN_IF(pTree
->IsSelected(), "starmath", "pTree should never be selected!");
578 //Discard the selection if there's a bug (it's better than crashing)
579 if(pTree
->IsSelected())
580 SetSelectedOnAll(pTree
, false);
581 }else //This shouldn't happen, but I don't see any reason to die if it does
585 void SmSetSelectionVisitor::SetSelectedOnAll( SmNode
* pSubTree
, bool IsSelected
) {
586 pSubTree
->SetSelected( IsSelected
);
588 if(pSubTree
->GetNumSubNodes() == 0)
590 //Quick BFS to set all selections
591 for( auto pChild
: *static_cast<SmStructureNode
*>(pSubTree
) )
595 SetSelectedOnAll( pChild
, IsSelected
);
599 void SmSetSelectionVisitor::DefaultVisit( SmNode
* pNode
) {
600 //Change state if maStartPos is in front of this node
601 if( maStartPos
.pSelectedNode
== pNode
&& maStartPos
.nIndex
== 0 )
602 mbSelecting
= !mbSelecting
;
603 //Change state if maEndPos is in front of this node
604 if( maEndPos
.pSelectedNode
== pNode
&& maEndPos
.nIndex
== 0 )
605 mbSelecting
= !mbSelecting
;
607 //Cache current state
608 bool WasSelecting
= mbSelecting
;
609 bool ChangedState
= false;
612 pNode
->SetSelected( mbSelecting
);
615 if(pNode
->GetNumSubNodes() > 0)
617 for( auto pChild
: *static_cast<SmStructureNode
*>(pNode
) )
621 pChild
->Accept( this );
622 ChangedState
= ( WasSelecting
!= mbSelecting
) || ChangedState
;
629 //Select this node and all of its children
630 //(Make exception for SmBracebodyNode)
631 if( pNode
->GetType() != SmNodeType::Bracebody
||
632 !pNode
->GetParent() ||
633 pNode
->GetParent()->GetType() != SmNodeType::Brace
)
634 SetSelectedOnAll( pNode
);
636 SetSelectedOnAll( pNode
->GetParent() );
637 /* If the equation is: sqrt{2 + 4} + 5
638 * And the selection is: sqrt{2 + [4} +] 5
639 * Where [ denotes maStartPos and ] denotes maEndPos
640 * Then the sqrt node should be selected, so that the
641 * effective selection is: [sqrt{2 + 4} +] 5
642 * The same is the case if we swap maStartPos and maEndPos.
646 //Change state if maStartPos is after this node
647 if( maStartPos
.pSelectedNode
== pNode
&& maStartPos
.nIndex
== 1 )
649 mbSelecting
= !mbSelecting
;
651 //Change state if maEndPos is after of this node
652 if( maEndPos
.pSelectedNode
== pNode
&& maEndPos
.nIndex
== 1 )
654 mbSelecting
= !mbSelecting
;
658 void SmSetSelectionVisitor::VisitCompositionNode( SmStructureNode
* pNode
)
660 //Change state if maStartPos is in front of this node
661 if( maStartPos
.pSelectedNode
== pNode
&& maStartPos
.nIndex
== 0 )
662 mbSelecting
= !mbSelecting
;
663 //Change state if maEndPos is in front of this node
664 if( maEndPos
.pSelectedNode
== pNode
&& maEndPos
.nIndex
== 0 )
665 mbSelecting
= !mbSelecting
;
667 //Cache current state
668 bool WasSelecting
= mbSelecting
;
671 for( auto pChild
: *pNode
)
675 pChild
->Accept( this );
678 //Set selected, if everything was selected
679 pNode
->SetSelected( WasSelecting
&& mbSelecting
);
681 //Change state if maStartPos is after this node
682 if( maStartPos
.pSelectedNode
== pNode
&& maStartPos
.nIndex
== 1 )
683 mbSelecting
= !mbSelecting
;
684 //Change state if maEndPos is after of this node
685 if( maEndPos
.pSelectedNode
== pNode
&& maEndPos
.nIndex
== 1 )
686 mbSelecting
= !mbSelecting
;
689 void SmSetSelectionVisitor::Visit( SmTextNode
* pNode
) {
692 if( maStartPos
.pSelectedNode
== pNode
)
693 i1
= maStartPos
.nIndex
;
694 if( maEndPos
.pSelectedNode
== pNode
)
695 i2
= maEndPos
.nIndex
;
697 tools::Long start
, end
;
698 pNode
->SetSelected(true);
699 if( i1
!= -1 && i2
!= -1 ) {
700 start
= std::min(i1
, i2
);
701 end
= std::max(i1
, i2
);
702 } else if( mbSelecting
&& i1
!= -1 ) {
706 } else if( mbSelecting
&& i2
!= -1 ) {
710 } else if( !mbSelecting
&& i1
!= -1 ) {
712 end
= pNode
->GetText().getLength();
714 } else if( !mbSelecting
&& i2
!= -1 ) {
716 end
= pNode
->GetText().getLength();
718 } else if( mbSelecting
) {
720 end
= pNode
->GetText().getLength();
722 pNode
->SetSelected( false );
726 pNode
->SetSelected( start
!= end
);
727 pNode
->SetSelectionStart( start
);
728 pNode
->SetSelectionEnd( end
);
731 void SmSetSelectionVisitor::Visit( SmExpressionNode
* pNode
) {
732 VisitCompositionNode( pNode
);
735 void SmSetSelectionVisitor::Visit( SmLineNode
* pNode
) {
736 VisitCompositionNode( pNode
);
739 void SmSetSelectionVisitor::Visit( SmAlignNode
* pNode
) {
740 VisitCompositionNode( pNode
);
743 void SmSetSelectionVisitor::Visit( SmBinHorNode
* pNode
) {
744 VisitCompositionNode( pNode
);
747 void SmSetSelectionVisitor::Visit( SmUnHorNode
* pNode
) {
748 VisitCompositionNode( pNode
);
751 void SmSetSelectionVisitor::Visit( SmFontNode
* pNode
) {
752 VisitCompositionNode( pNode
);
755 // SmCaretPosGraphBuildingVisitor
757 SmCaretPosGraphBuildingVisitor::SmCaretPosGraphBuildingVisitor( SmNode
* pRootNode
)
758 : mpRightMost(nullptr)
759 , mpGraph(new SmCaretPosGraph
)
761 //pRootNode should always be a table
762 SAL_WARN_IF( pRootNode
->GetType( ) != SmNodeType::Table
, "starmath", "pRootNode must be a table node");
763 //Handle the special case where SmNodeType::Table is used a rootnode
764 if( pRootNode
->GetType( ) == SmNodeType::Table
){
765 //Children are SmLineNodes
766 //Or so I thought... Apparently, the children can be instances of SmExpression
767 //especially if there's an error in the formula... So here we go, a simple work around.
768 for( auto pChild
: *static_cast<SmStructureNode
*>(pRootNode
) )
772 mpRightMost
= mpGraph
->Add( SmCaretPos( pChild
, 0 ) );
773 pChild
->Accept( this );
776 pRootNode
->Accept(this);
779 SmCaretPosGraphBuildingVisitor::~SmCaretPosGraphBuildingVisitor()
783 void SmCaretPosGraphBuildingVisitor::Visit( SmLineNode
* pNode
){
784 for( auto pChild
: *pNode
)
788 pChild
->Accept( this );
792 /** Build SmCaretPosGraph for SmTableNode
793 * This method covers cases where SmTableNode is used in a binom or stack,
794 * the special case where it is used as root node for the entire formula is
795 * handled in the constructor.
797 void SmCaretPosGraphBuildingVisitor::Visit( SmTableNode
* pNode
){
798 SmCaretPosGraphEntry
*left
= mpRightMost
,
799 *right
= mpGraph
->Add( SmCaretPos( pNode
, 1) );
800 bool bIsFirst
= true;
801 for( auto pChild
: *pNode
)
805 mpRightMost
= mpGraph
->Add( SmCaretPos( pChild
, 0 ), left
);
807 left
->SetRight(mpRightMost
);
808 pChild
->Accept( this );
809 mpRightMost
->SetRight(right
);
811 right
->SetLeft(mpRightMost
);
817 /** Build SmCaretPosGraph for SmSubSupNode
819 * The child positions in a SubSupNode, where H is the body:
832 * Graph over these, where "left" is before the SmSubSupNode and "right" is after:
847 void SmCaretPosGraphBuildingVisitor::Visit( SmSubSupNode
* pNode
)
849 SmCaretPosGraphEntry
*left
,
858 SAL_WARN_IF( !pNode
->GetBody(), "starmath", "SmSubSupNode Doesn't have a body!" );
859 bodyLeft
= mpGraph
->Add( SmCaretPos( pNode
->GetBody( ), 0 ), left
);
860 left
->SetRight( bodyLeft
); //TODO: Don't make this if LSUP or LSUB are NULL ( not sure??? )
863 right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ) );
865 //Visit the body, to get bodyRight
866 mpRightMost
= bodyLeft
;
867 pNode
->GetBody( )->Accept( this );
868 bodyRight
= mpRightMost
;
869 bodyRight
->SetRight( right
);
870 right
->SetLeft( bodyRight
);
873 for (SmSubSup
const nodeType
: { LSUP
, LSUB
, CSUP
, CSUB
, RSUP
, RSUB
})
875 pChild
= pNode
->GetSubSup(nodeType
);
878 SmCaretPosGraphEntry
*cLeft
; //Child left
879 cLeft
= mpGraph
->Add( SmCaretPos( pChild
, 0 ), ((nodeType
== RSUP
) || (nodeType
== RSUB
))?bodyRight
:left
);
882 pChild
->Accept( this );
884 mpRightMost
->SetRight( ((nodeType
== LSUP
) || (nodeType
== LSUB
))?bodyLeft
:right
);
888 //Set return parameters
892 /** Build caret position for SmOperNode
894 * If first child is an SmSubSupNode we will ignore its
895 * body, as this body is a SmMathSymbol, for SUM, INT or similar
896 * that shouldn't be subject to modification.
897 * If first child is not a SmSubSupNode, ignore it completely
898 * as it is a SmMathSymbol.
900 * The child positions in a SmOperNode, where H is symbol, e.g. int, sum or similar:
904 * LSUP H H RSUP BBB BB BBB B B
905 * H H B B B B B B B B
908 * LSUB H H RSUB BBB BB BBB B
912 * Notice, CSUP, etc. are actually grandchildren, but inorder to ignore H, these are visited
913 * from here. If they are present, that is if pOper is an instance of SmSubSupNode.
915 * Graph over these, where "left" is before the SmOperNode and "right" is after:
929 void SmCaretPosGraphBuildingVisitor::Visit( SmOperNode
* pNode
)
931 SmNode
*pOper
= pNode
->GetSubNode( 0 ),
932 *pBody
= pNode
->GetSubNode( 1 );
934 SmCaretPosGraphEntry
*left
= mpRightMost
,
939 bodyLeft
= mpGraph
->Add( SmCaretPos( pBody
, 0 ), left
);
940 left
->SetRight( bodyLeft
);
942 //Visit body, get bodyRight
943 mpRightMost
= bodyLeft
;
944 pBody
->Accept( this );
945 bodyRight
= mpRightMost
;
948 right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ), bodyRight
);
949 bodyRight
->SetRight( right
);
951 //Get subsup pNode if any
952 SmSubSupNode
* pSubSup
= pOper
->GetType( ) == SmNodeType::SubSup
? static_cast<SmSubSupNode
*>(pOper
) : nullptr;
956 for (SmSubSup
const nodeType
: { LSUP
, LSUB
, CSUP
, CSUB
, RSUP
, RSUB
})
958 pChild
= pSubSup
->GetSubSup(nodeType
);
961 //Create position in front of pChild
962 SmCaretPosGraphEntry
*childLeft
= mpGraph
->Add( SmCaretPos( pChild
, 0 ), left
);
964 mpRightMost
= childLeft
;
965 pChild
->Accept( this );
966 //Set right on mpRightMost from pChild
967 mpRightMost
->SetRight( bodyLeft
);
976 void SmCaretPosGraphBuildingVisitor::Visit( SmMatrixNode
* pNode
)
978 SmCaretPosGraphEntry
*left
= mpRightMost
,
979 *right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ) );
981 for (size_t i
= 0; i
< pNode
->GetNumRows(); ++i
)
983 SmCaretPosGraphEntry
* r
= left
;
984 for (size_t j
= 0; j
< pNode
->GetNumCols(); ++j
)
986 SmNode
* pSubNode
= pNode
->GetSubNode( i
* pNode
->GetNumCols( ) + j
);
988 mpRightMost
= mpGraph
->Add( SmCaretPos( pSubNode
, 0 ), r
);
989 if( j
!= 0 || ( pNode
->GetNumRows() - 1U ) / 2 == i
)
990 r
->SetRight( mpRightMost
);
992 pSubNode
->Accept( this );
996 mpRightMost
->SetRight( right
);
997 if( ( pNode
->GetNumRows() - 1U ) / 2 == i
)
998 right
->SetLeft( mpRightMost
);
1001 mpRightMost
= right
;
1004 /** Build SmCaretPosGraph for SmTextNode
1006 * Lines in an SmTextNode:
1010 * Where A B and C are characters in the text.
1012 * Graph over these, where "left" is before the SmTextNode and "right" is after:
1020 * Notice that C and right is the same position here.
1022 void SmCaretPosGraphBuildingVisitor::Visit( SmTextNode
* pNode
)
1024 SAL_WARN_IF( pNode
->GetText().isEmpty(), "starmath", "Empty SmTextNode is bad" );
1026 OUString
& aText
= pNode
->GetText();
1028 while (i
< aText
.getLength())
1030 aText
.iterateCodePoints(&i
);
1031 SmCaretPosGraphEntry
* pRight
= mpRightMost
;
1032 mpRightMost
= mpGraph
->Add( SmCaretPos( pNode
, i
), pRight
);
1033 pRight
->SetRight( mpRightMost
);
1037 /** Build SmCaretPosGraph for SmBinVerNode
1039 * Lines in an SmBinVerNode:
1046 * Graph over these, where "left" is before the SmBinVerNode and "right" is after:
1055 void SmCaretPosGraphBuildingVisitor::Visit( SmBinVerNode
* pNode
)
1057 //None if these children can be NULL, see SmBinVerNode::Arrange
1058 SmNode
*pNum
= pNode
->GetSubNode( 0 ),
1059 *pDenom
= pNode
->GetSubNode( 2 );
1061 SmCaretPosGraphEntry
*left
,
1066 assert(mpRightMost
);
1071 right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ) );
1074 numLeft
= mpGraph
->Add( SmCaretPos( pNum
, 0 ), left
);
1075 left
->SetRight( numLeft
);
1078 mpRightMost
= numLeft
;
1079 pNum
->Accept( this );
1080 mpRightMost
->SetRight( right
);
1081 right
->SetLeft( mpRightMost
);
1084 denomLeft
= mpGraph
->Add( SmCaretPos( pDenom
, 0 ), left
);
1087 mpRightMost
= denomLeft
;
1088 pDenom
->Accept( this );
1089 mpRightMost
->SetRight( right
);
1091 //Set return parameter
1092 mpRightMost
= right
;
1095 /** Build SmCaretPosGraph for SmVerticalBraceNode
1097 * Lines in an SmVerticalBraceNode:
1106 void SmCaretPosGraphBuildingVisitor::Visit( SmVerticalBraceNode
* pNode
)
1108 SmNode
*pBody
= pNode
->Body(),
1109 *pScript
= pNode
->Script();
1110 //None of these children can be NULL
1112 SmCaretPosGraphEntry
*left
,
1120 right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ) );
1123 bodyLeft
= mpGraph
->Add( SmCaretPos( pBody
, 0 ), left
);
1124 left
->SetRight( bodyLeft
);
1125 mpRightMost
= bodyLeft
;
1126 pBody
->Accept( this );
1127 mpRightMost
->SetRight( right
);
1128 right
->SetLeft( mpRightMost
);
1131 scriptLeft
= mpGraph
->Add( SmCaretPos( pScript
, 0 ), left
);
1132 mpRightMost
= scriptLeft
;
1133 pScript
->Accept( this );
1134 mpRightMost
->SetRight( right
);
1137 mpRightMost
= right
;
1140 /** Build SmCaretPosGraph for SmBinDiagonalNode
1142 * Lines in an SmBinDiagonalNode:
1148 * Where A and B are lines.
1150 * Used in formulas such as "A wideslash B"
1152 void SmCaretPosGraphBuildingVisitor::Visit( SmBinDiagonalNode
* pNode
)
1154 SmNode
*A
= pNode
->GetSubNode( 0 ),
1155 *B
= pNode
->GetSubNode( 1 );
1157 SmCaretPosGraphEntry
*left
,
1165 right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ) );
1168 leftA
= mpGraph
->Add( SmCaretPos( A
, 0 ), left
);
1169 left
->SetRight( leftA
);
1172 mpRightMost
= leftA
;
1174 rightA
= mpRightMost
;
1177 leftB
= mpGraph
->Add( SmCaretPos( B
, 0 ), rightA
);
1178 rightA
->SetRight( leftB
);
1181 mpRightMost
= leftB
;
1183 mpRightMost
->SetRight( right
);
1184 right
->SetLeft( mpRightMost
);
1187 mpRightMost
= right
;
1190 //Straight forward ( I think )
1191 void SmCaretPosGraphBuildingVisitor::Visit( SmBinHorNode
* pNode
)
1193 for( auto pChild
: *pNode
)
1197 pChild
->Accept( this );
1200 void SmCaretPosGraphBuildingVisitor::Visit( SmUnHorNode
* pNode
)
1202 // Unary operator node
1203 for( auto pChild
: *pNode
)
1207 pChild
->Accept( this );
1211 void SmCaretPosGraphBuildingVisitor::Visit( SmExpressionNode
* pNode
)
1213 for( auto pChild
: *pNode
)
1217 pChild
->Accept( this );
1221 void SmCaretPosGraphBuildingVisitor::Visit( SmFontNode
* pNode
)
1223 //Has only got one child, should act as an expression if possible
1224 for( auto pChild
: *pNode
)
1228 pChild
->Accept( this );
1232 /** Build SmCaretPosGraph for SmBracebodyNode
1233 * Acts as an SmExpressionNode
1235 * Below is an example of a formula tree that has multiple children for SmBracebodyNode
1239 * label= "Equation: \"lbrace i mline i in setZ rbrace\"";
1240 * n0 [label="SmTableNode"];
1241 * n0 -> n1 [label="0"];
1242 * n1 [label="SmLineNode"];
1243 * n1 -> n2 [label="0"];
1244 * n2 [label="SmExpressionNode"];
1245 * n2 -> n3 [label="0"];
1246 * n3 [label="SmBraceNode"];
1247 * n3 -> n4 [label="0"];
1248 * n4 [label="SmMathSymbolNode: {"];
1249 * n3 -> n5 [label="1"];
1250 * n5 [label="SmBracebodyNode"];
1251 * n5 -> n6 [label="0"];
1252 * n6 [label="SmExpressionNode"];
1253 * n6 -> n7 [label="0"];
1254 * n7 [label="SmTextNode: i"];
1255 * n5 -> n8 [label="1"];
1256 * n8 [label="SmMathSymbolNode: |"]; // Unicode "VERTICAL LINE"
1257 * n5 -> n9 [label="2"];
1258 * n9 [label="SmExpressionNode"];
1259 * n9 -> n10 [label="0"];
1260 * n10 [label="SmBinHorNode"];
1261 * n10 -> n11 [label="0"];
1262 * n11 [label="SmTextNode: i"];
1263 * n10 -> n12 [label="1"];
1264 * n12 [label="SmMathSymbolNode: ∈"]; // Unicode "ELEMENT OF"
1265 * n10 -> n13 [label="2"];
1266 * n13 [label="SmMathSymbolNode: ℤ"]; // Unicode "DOUBLE-STRUCK CAPITAL Z"
1267 * n3 -> n14 [label="2"];
1268 * n14 [label="SmMathSymbolNode: }"];
1272 void SmCaretPosGraphBuildingVisitor::Visit( SmBracebodyNode
* pNode
)
1274 for( auto pChild
: *pNode
)
1278 SmCaretPosGraphEntry
* pStart
= mpGraph
->Add( SmCaretPos( pChild
, 0), mpRightMost
);
1279 mpRightMost
->SetRight( pStart
);
1280 mpRightMost
= pStart
;
1281 pChild
->Accept( this );
1285 /** Build SmCaretPosGraph for SmAlignNode
1286 * Acts as an SmExpressionNode, as it only has one child this okay
1288 void SmCaretPosGraphBuildingVisitor::Visit( SmAlignNode
* pNode
)
1290 for( auto pChild
: *pNode
)
1294 pChild
->Accept( this );
1298 /** Build SmCaretPosGraph for SmRootNode
1300 * Lines in an SmRootNode:
1307 * A: pExtra ( optional, can be NULL ),
1310 * Graph over these, where "left" is before the SmRootNode and "right" is after:
1319 void SmCaretPosGraphBuildingVisitor::Visit( SmRootNode
* pNode
)
1321 SmNode
*pExtra
= pNode
->GetSubNode( 0 ), //Argument, NULL for sqrt, and SmTextNode if cubicroot
1322 *pBody
= pNode
->GetSubNode( 2 ); //Body of the root
1325 SmCaretPosGraphEntry
*left
,
1330 //Get left and save it
1331 assert(mpRightMost
);
1335 bodyLeft
= mpGraph
->Add( SmCaretPos( pBody
, 0 ), left
);
1336 left
->SetRight( bodyLeft
);
1339 right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ) );
1342 mpRightMost
= bodyLeft
;
1343 pBody
->Accept( this );
1344 bodyRight
= mpRightMost
;
1345 bodyRight
->SetRight( right
);
1346 right
->SetLeft( bodyRight
);
1350 mpRightMost
= mpGraph
->Add( SmCaretPos( pExtra
, 0 ), left
);
1351 pExtra
->Accept( this );
1352 mpRightMost
->SetRight( bodyLeft
);
1355 mpRightMost
= right
;
1359 /** Build SmCaretPosGraph for SmPlaceNode
1360 * Consider this a single character.
1362 void SmCaretPosGraphBuildingVisitor::Visit( SmPlaceNode
* pNode
)
1364 SmCaretPosGraphEntry
* right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ), mpRightMost
);
1365 mpRightMost
->SetRight( right
);
1366 mpRightMost
= right
;
1369 /** SmErrorNode is context dependent metadata, it can't be selected
1371 * @remarks There's no point in deleting, copying and/or moving an instance
1372 * of SmErrorNode as it may not exist in another context! Thus there are no
1373 * positions to select an SmErrorNode.
1375 void SmCaretPosGraphBuildingVisitor::Visit( SmErrorNode
* )
1379 /** Build SmCaretPosGraph for SmBlankNode
1380 * Consider this a single character, as it is only a blank space
1382 void SmCaretPosGraphBuildingVisitor::Visit( SmBlankNode
* pNode
)
1384 SmCaretPosGraphEntry
* right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ), mpRightMost
);
1385 mpRightMost
->SetRight( right
);
1386 mpRightMost
= right
;
1389 /** Build SmCaretPosGraph for SmBraceNode
1391 * Lines in an SmBraceNode:
1399 * Graph over these, where "left" is before the SmBraceNode and "right" is after:
1407 void SmCaretPosGraphBuildingVisitor::Visit( SmBraceNode
* pNode
)
1409 SmNode
* pBody
= pNode
->Body();
1411 SmCaretPosGraphEntry
*left
= mpRightMost
,
1412 *right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ) );
1414 if( pBody
->GetType() != SmNodeType::Bracebody
) {
1415 mpRightMost
= mpGraph
->Add( SmCaretPos( pBody
, 0 ), left
);
1416 left
->SetRight( mpRightMost
);
1420 pBody
->Accept( this );
1421 mpRightMost
->SetRight( right
);
1422 right
->SetLeft( mpRightMost
);
1424 mpRightMost
= right
;
1427 /** Build SmCaretPosGraph for SmAttributeNode
1429 * Lines in an SmAttributeNode:
1435 * There's a body and an attribute, the construction is used for "widehat A", where "A" is the body
1436 * and "^" is the attribute ( note GetScaleMode( ) on SmAttributeNode tells how the attribute should be
1439 void SmCaretPosGraphBuildingVisitor::Visit( SmAttributeNode
* pNode
)
1441 SmNode
*pAttr
= pNode
->Attribute(),
1442 *pBody
= pNode
->Body();
1446 SmCaretPosGraphEntry
*left
= mpRightMost
,
1453 bodyLeft
= mpGraph
->Add( SmCaretPos( pBody
, 0 ), left
);
1454 left
->SetRight( bodyLeft
);
1457 right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ) );
1460 mpRightMost
= bodyLeft
;
1461 pBody
->Accept( this );
1462 bodyRight
= mpRightMost
;
1463 bodyRight
->SetRight( right
);
1464 right
->SetLeft( bodyRight
);
1467 attrLeft
= mpGraph
->Add( SmCaretPos( pAttr
, 0 ), left
);
1470 mpRightMost
= attrLeft
;
1471 pAttr
->Accept( this );
1472 mpRightMost
->SetRight( right
);
1475 mpRightMost
= right
;
1478 //Consider these single symbols
1479 void SmCaretPosGraphBuildingVisitor::Visit( SmSpecialNode
* pNode
)
1481 SmCaretPosGraphEntry
* right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ), mpRightMost
);
1482 mpRightMost
->SetRight( right
);
1483 mpRightMost
= right
;
1485 void SmCaretPosGraphBuildingVisitor::Visit( SmGlyphSpecialNode
* pNode
)
1487 SmCaretPosGraphEntry
* right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ), mpRightMost
);
1488 mpRightMost
->SetRight( right
);
1489 mpRightMost
= right
;
1491 void SmCaretPosGraphBuildingVisitor::Visit( SmMathSymbolNode
* pNode
)
1493 SmCaretPosGraphEntry
* right
= mpGraph
->Add( SmCaretPos( pNode
, 1 ), mpRightMost
);
1494 mpRightMost
->SetRight( right
);
1495 mpRightMost
= right
;
1498 void SmCaretPosGraphBuildingVisitor::Visit( SmRootSymbolNode
* )
1503 void SmCaretPosGraphBuildingVisitor::Visit( SmRectangleNode
* )
1507 void SmCaretPosGraphBuildingVisitor::Visit( SmPolyLineNode
* )
1514 SmNode
* SmCloningVisitor::Clone( SmNode
* pNode
)
1516 SmNode
* pCurrResult
= mpResult
;
1517 pNode
->Accept( this );
1518 SmNode
* pClone
= mpResult
;
1519 mpResult
= pCurrResult
;
1523 void SmCloningVisitor::CloneNodeAttr( SmNode
const * pSource
, SmNode
* pTarget
)
1525 pTarget
->SetScaleMode( pSource
->GetScaleMode( ) );
1526 //Other attributes are set when prepare or arrange is executed
1527 //and may depend on stuff not being cloned here.
1530 void SmCloningVisitor::CloneKids( SmStructureNode
* pSource
, SmStructureNode
* pTarget
)
1532 //Cache current result
1533 SmNode
* pCurrResult
= mpResult
;
1535 //Create array for holding clones
1536 size_t nSize
= pSource
->GetNumSubNodes( );
1537 SmNodeArray
aNodes( nSize
);
1540 for (size_t i
= 0; i
< nSize
; ++i
)
1543 if( nullptr != ( pKid
= pSource
->GetSubNode( i
) ) )
1544 pKid
->Accept( this );
1547 aNodes
[i
] = mpResult
;
1550 //Set subnodes of pTarget
1551 pTarget
->SetSubNodes( std::move(aNodes
) );
1553 //Restore result as where prior to call
1554 mpResult
= pCurrResult
;
1557 void SmCloningVisitor::Visit( SmTableNode
* pNode
)
1559 SmTableNode
* pClone
= new SmTableNode( pNode
->GetToken( ) );
1560 pClone
->SetSelection( pNode
->GetSelection() );
1561 CloneNodeAttr( pNode
, pClone
);
1562 CloneKids( pNode
, pClone
);
1566 void SmCloningVisitor::Visit( SmBraceNode
* pNode
)
1568 SmBraceNode
* pClone
= new SmBraceNode( pNode
->GetToken( ) );
1569 pClone
->SetSelection( pNode
->GetSelection() );
1570 CloneNodeAttr( pNode
, pClone
);
1571 CloneKids( pNode
, pClone
);
1575 void SmCloningVisitor::Visit( SmBracebodyNode
* pNode
)
1577 SmBracebodyNode
* pClone
= new SmBracebodyNode( pNode
->GetToken( ) );
1578 pClone
->SetSelection( pNode
->GetSelection() );
1579 CloneNodeAttr( pNode
, pClone
);
1580 CloneKids( pNode
, pClone
);
1584 void SmCloningVisitor::Visit( SmOperNode
* pNode
)
1586 SmOperNode
* pClone
= new SmOperNode( pNode
->GetToken( ) );
1587 pClone
->SetSelection( pNode
->GetSelection() );
1588 CloneNodeAttr( pNode
, pClone
);
1589 CloneKids( pNode
, pClone
);
1593 void SmCloningVisitor::Visit( SmAlignNode
* pNode
)
1595 SmAlignNode
* pClone
= new SmAlignNode( pNode
->GetToken( ) );
1596 pClone
->SetSelection( pNode
->GetSelection() );
1597 CloneNodeAttr( pNode
, pClone
);
1598 CloneKids( pNode
, pClone
);
1602 void SmCloningVisitor::Visit( SmAttributeNode
* pNode
)
1604 SmAttributeNode
* pClone
= new SmAttributeNode( pNode
->GetToken( ) );
1605 pClone
->SetSelection( pNode
->GetSelection() );
1606 CloneNodeAttr( pNode
, pClone
);
1607 CloneKids( pNode
, pClone
);
1611 void SmCloningVisitor::Visit( SmFontNode
* pNode
)
1613 SmFontNode
* pClone
= new SmFontNode( pNode
->GetToken( ) );
1614 pClone
->SetSelection( pNode
->GetSelection() );
1615 pClone
->SetSizeParameter( pNode
->GetSizeParameter( ), pNode
->GetSizeType( ) );
1616 CloneNodeAttr( pNode
, pClone
);
1617 CloneKids( pNode
, pClone
);
1621 void SmCloningVisitor::Visit( SmUnHorNode
* pNode
)
1623 SmUnHorNode
* pClone
= new SmUnHorNode( pNode
->GetToken( ) );
1624 pClone
->SetSelection( pNode
->GetSelection() );
1625 CloneNodeAttr( pNode
, pClone
);
1626 CloneKids( pNode
, pClone
);
1630 void SmCloningVisitor::Visit( SmBinHorNode
* pNode
)
1632 SmBinHorNode
* pClone
= new SmBinHorNode( pNode
->GetToken( ) );
1633 pClone
->SetSelection( pNode
->GetSelection() );
1634 CloneNodeAttr( pNode
, pClone
);
1635 CloneKids( pNode
, pClone
);
1639 void SmCloningVisitor::Visit( SmBinVerNode
* pNode
)
1641 SmBinVerNode
* pClone
= new SmBinVerNode( pNode
->GetToken( ) );
1642 pClone
->SetSelection( pNode
->GetSelection() );
1643 CloneNodeAttr( pNode
, pClone
);
1644 CloneKids( pNode
, pClone
);
1648 void SmCloningVisitor::Visit( SmBinDiagonalNode
* pNode
)
1650 SmBinDiagonalNode
*pClone
= new SmBinDiagonalNode( pNode
->GetToken( ) );
1651 pClone
->SetSelection( pNode
->GetSelection() );
1652 pClone
->SetAscending( pNode
->IsAscending( ) );
1653 CloneNodeAttr( pNode
, pClone
);
1654 CloneKids( pNode
, pClone
);
1658 void SmCloningVisitor::Visit( SmSubSupNode
* pNode
)
1660 SmSubSupNode
*pClone
= new SmSubSupNode( pNode
->GetToken( ) );
1661 pClone
->SetSelection( pNode
->GetSelection() );
1662 pClone
->SetUseLimits( pNode
->IsUseLimits( ) );
1663 CloneNodeAttr( pNode
, pClone
);
1664 CloneKids( pNode
, pClone
);
1668 void SmCloningVisitor::Visit( SmMatrixNode
* pNode
)
1670 SmMatrixNode
*pClone
= new SmMatrixNode( pNode
->GetToken( ) );
1671 pClone
->SetSelection( pNode
->GetSelection() );
1672 pClone
->SetRowCol( pNode
->GetNumRows( ), pNode
->GetNumCols( ) );
1673 CloneNodeAttr( pNode
, pClone
);
1674 CloneKids( pNode
, pClone
);
1678 void SmCloningVisitor::Visit( SmPlaceNode
* pNode
)
1680 mpResult
= new SmPlaceNode( pNode
->GetToken( ) );
1681 mpResult
->SetSelection( pNode
->GetSelection() );
1682 CloneNodeAttr( pNode
, mpResult
);
1685 void SmCloningVisitor::Visit( SmTextNode
* pNode
)
1687 SmTextNode
* pClone
= new SmTextNode( pNode
->GetToken( ), pNode
->GetFontDesc( ) );
1688 pClone
->SetSelection( pNode
->GetSelection() );
1689 pClone
->ChangeText( pNode
->GetText( ) );
1690 CloneNodeAttr( pNode
, pClone
);
1694 void SmCloningVisitor::Visit( SmSpecialNode
* pNode
)
1696 mpResult
= new SmSpecialNode( pNode
->GetToken( ) );
1697 mpResult
->SetSelection( pNode
->GetSelection() );
1698 CloneNodeAttr( pNode
, mpResult
);
1701 void SmCloningVisitor::Visit( SmGlyphSpecialNode
* pNode
)
1703 mpResult
= new SmGlyphSpecialNode( pNode
->GetToken( ) );
1704 mpResult
->SetSelection( pNode
->GetSelection() );
1705 CloneNodeAttr( pNode
, mpResult
);
1708 void SmCloningVisitor::Visit( SmMathSymbolNode
* pNode
)
1710 mpResult
= new SmMathSymbolNode( pNode
->GetToken( ) );
1711 mpResult
->SetSelection( pNode
->GetSelection() );
1712 CloneNodeAttr( pNode
, mpResult
);
1715 void SmCloningVisitor::Visit( SmBlankNode
* pNode
)
1717 SmBlankNode
* pClone
= new SmBlankNode( pNode
->GetToken( ) );
1718 pClone
->SetSelection( pNode
->GetSelection() );
1719 pClone
->SetBlankNum( pNode
->GetBlankNum( ) );
1721 CloneNodeAttr( pNode
, mpResult
);
1724 void SmCloningVisitor::Visit( SmErrorNode
* pNode
)
1726 mpResult
= new SmErrorNode( pNode
->GetToken( ) );
1727 mpResult
->SetSelection( pNode
->GetSelection() );
1728 CloneNodeAttr( pNode
, mpResult
);
1731 void SmCloningVisitor::Visit( SmLineNode
* pNode
)
1733 SmLineNode
* pClone
= new SmLineNode( pNode
->GetToken( ) );
1734 pClone
->SetSelection( pNode
->GetSelection() );
1735 CloneNodeAttr( pNode
, pClone
);
1736 CloneKids( pNode
, pClone
);
1740 void SmCloningVisitor::Visit( SmExpressionNode
* pNode
)
1742 SmExpressionNode
* pClone
= new SmExpressionNode( pNode
->GetToken( ) );
1743 pClone
->SetSelection( pNode
->GetSelection() );
1744 CloneNodeAttr( pNode
, pClone
);
1745 CloneKids( pNode
, pClone
);
1749 void SmCloningVisitor::Visit( SmPolyLineNode
* pNode
)
1751 mpResult
= new SmPolyLineNode( pNode
->GetToken( ) );
1752 mpResult
->SetSelection( pNode
->GetSelection() );
1753 CloneNodeAttr( pNode
, mpResult
);
1756 void SmCloningVisitor::Visit( SmRootNode
* pNode
)
1758 SmRootNode
* pClone
= new SmRootNode( pNode
->GetToken( ) );
1759 pClone
->SetSelection( pNode
->GetSelection() );
1760 CloneNodeAttr( pNode
, pClone
);
1761 CloneKids( pNode
, pClone
);
1765 void SmCloningVisitor::Visit( SmRootSymbolNode
* pNode
)
1767 mpResult
= new SmRootSymbolNode( pNode
->GetToken( ) );
1768 mpResult
->SetSelection( pNode
->GetSelection() );
1769 CloneNodeAttr( pNode
, mpResult
);
1772 void SmCloningVisitor::Visit( SmRectangleNode
* pNode
)
1774 mpResult
= new SmRectangleNode( pNode
->GetToken( ) );
1775 mpResult
->SetSelection( pNode
->GetSelection() );
1776 CloneNodeAttr( pNode
, mpResult
);
1779 void SmCloningVisitor::Visit( SmVerticalBraceNode
* pNode
)
1781 SmVerticalBraceNode
* pClone
= new SmVerticalBraceNode( pNode
->GetToken( ) );
1782 pClone
->SetSelection( pNode
->GetSelection() );
1783 CloneNodeAttr( pNode
, pClone
);
1784 CloneKids( pNode
, pClone
);
1788 // SmSelectionDrawingVisitor
1790 SmSelectionDrawingVisitor::SmSelectionDrawingVisitor( OutputDevice
& rDevice
, SmNode
* pTree
, const Point
& rOffset
)
1791 : SmSelectionRectanglesVisitor( rDevice
, pTree
)
1793 //Draw selection if there's any
1794 if(GetSelection().IsEmpty()) return;
1796 tools::Rectangle aSelectionArea
= GetSelection() + rOffset
;
1799 rDevice
.Push( vcl::PushFlags::LINECOLOR
| vcl::PushFlags::FILLCOLOR
);
1801 rDevice
.SetLineColor( );
1802 rDevice
.SetFillColor( COL_LIGHTGRAY
);
1805 rDevice
.DrawRect( aSelectionArea
);
1807 //Restore device state
1811 // SmSelectionRectanglesVisitor
1813 SmSelectionRectanglesVisitor::SmSelectionRectanglesVisitor(OutputDevice
& rDevice
, SmNode
* pTree
)
1817 SAL_WARN_IF(!pTree
, "starmath", "pTree can't be null!");
1819 pTree
->Accept(this);
1822 void SmSelectionRectanglesVisitor::DefaultVisit( SmNode
* pNode
)
1824 if( pNode
->IsSelected( ) )
1825 ExtendSelectionArea( pNode
->AsRectangle( ) );
1826 VisitChildren( pNode
);
1829 void SmSelectionRectanglesVisitor::VisitChildren( SmNode
* pNode
)
1831 if(pNode
->GetNumSubNodes() == 0)
1833 for( auto pChild
: *static_cast<SmStructureNode
*>(pNode
) )
1837 pChild
->Accept( this );
1841 void SmSelectionRectanglesVisitor::Visit( SmTextNode
* pNode
)
1843 if( !pNode
->IsSelected())
1846 mrDev
.Push( vcl::PushFlags::TEXTCOLOR
| vcl::PushFlags::FONT
);
1848 mrDev
.SetFont( pNode
->GetFont( ) );
1849 Point Position
= pNode
->GetTopLeft( );
1850 tools::Long left
= Position
.getX( ) + mrDev
.GetTextWidth( pNode
->GetText( ), 0, pNode
->GetSelectionStart( ) );
1851 tools::Long right
= Position
.getX( ) + mrDev
.GetTextWidth( pNode
->GetText( ), 0, pNode
->GetSelectionEnd( ) );
1852 tools::Long top
= Position
.getY( );
1853 tools::Long bottom
= top
+ pNode
->GetHeight( );
1854 tools::Rectangle
rect( left
, top
, right
, bottom
);
1856 ExtendSelectionArea( rect
);
1861 // SmNodeToTextVisitor
1863 SmNodeToTextVisitor::SmNodeToTextVisitor( SmNode
* pNode
, OUString
&rText
)
1865 pNode
->Accept( this );
1866 maCmdText
.stripEnd(' ');
1867 rText
= maCmdText
.makeStringAndClear();
1870 void SmNodeToTextVisitor::Visit( SmTableNode
* pNode
)
1872 if( pNode
->GetToken( ).eType
== TBINOM
) {
1874 LineToText( pNode
->GetSubNode( 0 ) );
1875 LineToText( pNode
->GetSubNode( 1 ) );
1877 } else if( pNode
->GetToken( ).eType
== TSTACK
) {
1880 for( auto pChild
: *pNode
)
1891 LineToText( pChild
);
1895 } else { //Assume it's a toplevel table, containing lines
1897 for( auto pChild
: *pNode
)
1909 pChild
->Accept( this );
1914 void SmNodeToTextVisitor::Visit( SmBraceNode
* pNode
)
1916 if ( pNode
->GetToken().eType
== TEVALUATE
)
1918 SmNode
*pBody
= pNode
->Body();
1919 Append(u
"evaluate { ");
1920 pBody
->Accept( this );
1924 SmNode
*pLeftBrace
= pNode
->OpeningBrace(),
1925 *pBody
= pNode
->Body(),
1926 *pRightBrace
= pNode
->ClosingBrace();
1927 //Handle special case where it's absolute function
1928 if( pNode
->GetToken( ).eType
== TABS
) {
1930 LineToText( pBody
);
1932 if( pNode
->GetScaleMode( ) == SmScaleMode::Height
)
1934 pLeftBrace
->Accept( this );
1936 pBody
->Accept( this );
1938 if( pNode
->GetScaleMode( ) == SmScaleMode::Height
)
1940 pRightBrace
->Accept( this );
1945 void SmNodeToTextVisitor::Visit( SmBracebodyNode
* pNode
)
1947 for( auto pChild
: *pNode
)
1952 pChild
->Accept( this );
1956 void SmNodeToTextVisitor::Visit( SmOperNode
* pNode
)
1958 Append( pNode
->GetToken( ).aText
);
1960 if( pNode
->GetToken( ).eType
== TOPER
){
1961 //There's an SmGlyphSpecialNode if eType == TOPER
1962 if( pNode
->GetSubNode( 0 )->GetType( ) == SmNodeType::SubSup
)
1963 Append( pNode
->GetSubNode( 0 )->GetSubNode( 0 )->GetToken( ).aText
);
1965 Append( pNode
->GetSubNode( 0 )->GetToken( ).aText
);
1967 if( pNode
->GetSubNode( 0 )->GetType( ) == SmNodeType::SubSup
) {
1968 SmSubSupNode
*pSubSup
= static_cast<SmSubSupNode
*>( pNode
->GetSubNode( 0 ) );
1969 SmNode
* pChild
= pSubSup
->GetSubSup( LSUP
);
1973 LineToText( pChild
);
1976 pChild
= pSubSup
->GetSubSup( LSUB
);
1980 LineToText( pChild
);
1983 pChild
= pSubSup
->GetSubSup( RSUP
);
1987 LineToText( pChild
);
1990 pChild
= pSubSup
->GetSubSup( RSUB
);
1994 LineToText( pChild
);
1997 pChild
= pSubSup
->GetSubSup( CSUP
);
2000 if (pSubSup
->IsUseLimits())
2004 LineToText( pChild
);
2007 pChild
= pSubSup
->GetSubSup( CSUB
);
2010 if (pSubSup
->IsUseLimits())
2014 LineToText( pChild
);
2018 LineToText( pNode
->GetSubNode( 1 ) );
2021 void SmNodeToTextVisitor::Visit( SmAlignNode
* pNode
)
2023 Append( pNode
->GetToken( ).aText
);
2024 LineToText( pNode
->GetSubNode( 0 ) );
2027 void SmNodeToTextVisitor::Visit( SmAttributeNode
* pNode
)
2029 Append( pNode
->GetToken( ).aText
);
2030 LineToText( pNode
->Body() );
2033 void SmNodeToTextVisitor::Visit( SmFontNode
* pNode
)
2036 sal_uInt8 nr
, ng
, nb
;
2037 switch ( pNode
->GetToken( ).eType
)
2049 Append(u
"nitalic ");
2052 Append(u
"phantom ");
2057 switch ( pNode
->GetSizeType( ) )
2059 case FontSizeType::PLUS
:
2062 case FontSizeType::MINUS
:
2065 case FontSizeType::MULTIPLY
:
2068 case FontSizeType::DIVIDE
:
2071 case FontSizeType::ABSOLUT
:
2075 Append( ::rtl::math::doubleToUString(
2076 static_cast<double>( pNode
->GetSizeParameter( ) ),
2077 rtl_math_StringFormat_Automatic
,
2078 rtl_math_DecimalPlaces_Max
, '.', true ) );
2083 case TDVIPSNAMESCOL
:
2084 Append(u
"color dvip ");
2085 nc
= pNode
->GetToken().cMathChar
.toUInt32(16);
2086 Append( starmathdatabase::Identify_Color_Parser( nc
).aIdent
);
2092 nc
= pNode
->GetToken().cMathChar
.toUInt32(16);
2093 Append( starmathdatabase::Identify_Color_Parser( nc
).aIdent
);
2096 nc
= pNode
->GetToken().cMathChar
.toUInt32(16);
2097 Append(u
"color rgb ");
2103 Append(OUString::number(nr
));
2105 Append(OUString::number(ng
));
2107 Append(OUString::number(nb
));
2111 Append(u
"color rgba ");
2112 nc
= pNode
->GetToken().cMathChar
.toUInt32(16);
2119 Append(OUString::number(nr
));
2121 Append(OUString::number(ng
));
2123 Append(OUString::number(nb
));
2125 Append(OUString::number(nc
));
2129 Append(u
"color hex ");
2130 nc
= pNode
->GetToken().cMathChar
.toUInt32(16);
2131 Append(OUString::number(nc
,16));
2135 Append(u
"font sans ");
2138 Append(u
"font serif ");
2141 Append(u
"font fixed ");
2146 LineToText( pNode
->GetSubNode( 1 ) );
2149 void SmNodeToTextVisitor::Visit( SmUnHorNode
* pNode
)
2151 if(pNode
->GetSubNode( 1 )->GetToken( ).eType
== TFACT
)
2153 // visit children in the reverse order
2154 for( auto it
= pNode
->rbegin(); it
!= pNode
->rend(); ++it
)
2160 pChild
->Accept( this );
2165 for( auto pChild
: *pNode
)
2170 pChild
->Accept( this );
2175 void SmNodeToTextVisitor::Visit( SmBinHorNode
* pNode
)
2177 const SmNode
*pParent
= pNode
->GetParent();
2178 bool bBraceNeeded
= pParent
;
2179 SmNode
*pLeft
= pNode
->LeftOperand(),
2180 *pOper
= pNode
->Symbol(),
2181 *pRight
= pNode
->RightOperand();
2185 pLeft
->Accept( this );
2187 pOper
->Accept( this );
2189 pRight
->Accept( this );
2195 void SmNodeToTextVisitor::Visit( SmBinVerNode
* pNode
)
2197 if( pNode
->GetToken().eType
== TOVER
){
2198 SmNode
*pNum
= pNode
->GetSubNode( 0 ),
2199 *pDenom
= pNode
->GetSubNode( 2 );
2203 LineToText( pDenom
);
2206 SmNode
*pNum
= pNode
->GetSubNode( 0 ),
2207 *pDenom
= pNode
->GetSubNode( 2 );
2208 Append(u
"{ frac {");
2211 LineToText( pDenom
);
2216 void SmNodeToTextVisitor::Visit( SmBinDiagonalNode
* pNode
)
2218 SmNode
*pLeftOperand
= pNode
->GetSubNode( 0 ),
2219 *pRightOperand
= pNode
->GetSubNode( 1 );
2221 LineToText( pLeftOperand
);
2223 Append(u
"wideslash ");
2224 LineToText( pRightOperand
);
2228 void SmNodeToTextVisitor::Visit( SmSubSupNode
* pNode
)
2230 if( pNode
->GetToken().eType
== TEVALUATE
)
2232 Append(u
"evaluate { ");
2233 pNode
->GetSubNode( 0 )->GetSubNode( 1 )->Accept(this);
2235 SmNode
* pChild
= pNode
->GetSubSup( RSUP
);
2239 LineToText( pChild
);
2242 pChild
= pNode
->GetSubSup( RSUB
);
2246 LineToText( pChild
);
2252 LineToText( pNode
->GetBody( ) );
2253 SmNode
*pChild
= pNode
->GetSubSup( LSUP
);
2257 LineToText( pChild
);
2259 pChild
= pNode
->GetSubSup( LSUB
);
2263 LineToText( pChild
);
2265 pChild
= pNode
->GetSubSup( RSUP
);
2269 LineToText( pChild
);
2271 pChild
= pNode
->GetSubSup( RSUB
);
2275 LineToText( pChild
);
2277 pChild
= pNode
->GetSubSup( CSUP
);
2280 if (pNode
->IsUseLimits())
2284 LineToText( pChild
);
2286 pChild
= pNode
->GetSubSup( CSUB
);
2289 if (pNode
->IsUseLimits())
2293 LineToText( pChild
);
2298 void SmNodeToTextVisitor::Visit( SmMatrixNode
* pNode
)
2301 for (size_t i
= 0; i
< pNode
->GetNumRows(); ++i
)
2303 for (size_t j
= 0; j
< pNode
->GetNumCols( ); ++j
)
2305 SmNode
* pSubNode
= pNode
->GetSubNode( i
* pNode
->GetNumCols( ) + j
);
2308 pSubNode
->Accept( this );
2310 if (j
!= pNode
->GetNumCols() - 1U)
2314 if (i
!= pNode
->GetNumRows() - 1U)
2320 void SmNodeToTextVisitor::Visit( SmPlaceNode
* )
2325 void SmNodeToTextVisitor::Visit( SmTextNode
* pNode
)
2327 SmTokenType type
= pNode
->GetToken( ).eType
;
2331 Append( pNode
->GetToken().aText
);
2335 Append( pNode
->GetToken().aText
);
2338 Append( pNode
->GetToken().aText
);
2342 Append( pNode
->GetToken().aText
);
2346 Append( pNode
->GetToken().aText
);
2349 Append( pNode
->GetToken().aText
);
2354 void SmNodeToTextVisitor::Visit( SmSpecialNode
* pNode
)
2356 SmTokenType type
= pNode
->GetToken().eType
;
2359 Append(u
"lim sup ");
2362 Append(u
"lim inf ");
2365 Append( pNode
->GetToken().aText
);
2370 void SmNodeToTextVisitor::Visit( SmGlyphSpecialNode
* pNode
)
2372 if( pNode
->GetToken( ).eType
== TBOPER
)
2376 Append( pNode
->GetToken( ).aText
);
2379 //TODO to improve this it is required to improve mathmlimport.
2380 void SmNodeToTextVisitor::Visit( SmMathSymbolNode
* pNode
)
2382 if ( ( pNode
->GetToken().nGroup
& TG::LBrace
)
2383 || ( pNode
->GetToken().nGroup
& TG::RBrace
)
2384 || ( pNode
->GetToken().nGroup
& TG::Sum
)
2385 || ( pNode
->GetToken().nGroup
& TG::Product
)
2386 || ( pNode
->GetToken().nGroup
& TG::Relation
)
2387 || ( pNode
->GetToken().nGroup
& TG::UnOper
)
2388 || ( pNode
->GetToken().nGroup
& TG::Oper
)
2390 Append( pNode
->GetToken().aText
);
2393 sal_Unicode cChar
= pNode
->GetToken().cMathChar
[0];
2412 if( pNode
->GetToken().eType
== TTOWARD
) Append(u
"toward");
2413 else Append(u
"rightarrow");
2416 Append(u
"leftarrow");
2422 Append(u
"downarrow");
2425 Append(u
"lambdabar");
2473 Append(u
"dlrarrow");
2485 Append(u
"notexists");
2488 Append(u
"emptyset");
2493 case MS_BACKEPSILON
:
2494 Append(u
"backepsilon");
2500 Append(u
"infinity");
2502 case 0x22b2: // NORMAL SUBGROUP OF
2503 Append(OUStringChar(cChar
));
2505 case 0x22b3: // CONTAINS AS NORMAL SUBGROUP
2506 Append(OUStringChar(cChar
));
2512 Append(u
"dotsvert");
2515 Append(u
"dotsaxis");
2521 Append(u
"dotsdown");
2530 Append(u
"widetilde");
2535 case 0xeb01: //no space
2536 case 0xeb08: //normal space
2538 case 0xef04: //tiny space
2539 case 0xef05: //tiny space
2540 case 0xeb02: //small space
2541 case 0xeb04: //medium space
2544 case 0xeb05: //large space
2551 Append(OUStringChar(cChar
));
2556 void SmNodeToTextVisitor::Visit( SmBlankNode
* pNode
)
2558 sal_uInt16 nNum
= pNode
->GetBlankNum();
2561 sal_uInt16 nWide
= nNum
/ 4;
2562 sal_uInt16 nNarrow
= nNum
% 4;
2563 for (sal_uInt16 i
= 0; i
< nWide
; i
++)
2565 for (sal_uInt16 i
= 0; i
< nNarrow
; i
++)
2570 void SmNodeToTextVisitor::Visit( SmErrorNode
* )
2572 // Add something for error nodes so that we can parse this back.
2576 void SmNodeToTextVisitor::Visit( SmLineNode
* pNode
)
2578 for( auto pChild
: *pNode
)
2583 pChild
->Accept( this );
2587 void SmNodeToTextVisitor::Visit( SmExpressionNode
* pNode
)
2589 bool bracketsNeeded
= pNode
->GetNumSubNodes() != 1;
2590 if (!bracketsNeeded
)
2592 const SmNode
*pParent
= pNode
->GetParent();
2595 pParent
&& pParent
->GetType() == SmNodeType::SubSup
&&
2596 pNode
->GetNumSubNodes() == 1 &&
2597 pNode
->GetSubNode(0)->GetType() == SmNodeType::SubSup
;
2600 if (bracketsNeeded
) {
2603 for( auto pChild
: *pNode
)
2607 pChild
->Accept( this );
2610 if (bracketsNeeded
) {
2615 void SmNodeToTextVisitor::Visit( SmPolyLineNode
* )
2619 void SmNodeToTextVisitor::Visit( SmRootNode
* pNode
)
2621 SmNode
*pExtra
= pNode
->GetSubNode( 0 ),
2622 *pBody
= pNode
->GetSubNode( 2 );
2625 LineToText( pExtra
);
2628 LineToText( pBody
);
2631 void SmNodeToTextVisitor::Visit( SmRootSymbolNode
* )
2635 void SmNodeToTextVisitor::Visit( SmRectangleNode
* )
2639 void SmNodeToTextVisitor::Visit( SmVerticalBraceNode
* pNode
)
2641 SmNode
*pBody
= pNode
->Body(),
2642 *pScript
= pNode
->Script();
2643 LineToText( pBody
);
2644 Append( pNode
->GetToken( ).aText
);
2645 LineToText( pScript
);
2648 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */