ImproveMathEquationEditor/Baseline AlignmentEquations

From Wiki.ooo4kids.org
Jump to: navigation, search


Baseline Alignment Equations in Writer


Back to Improve Math Equation editor


Important: If you use these information, don't forget everything you'll find here is under CC by-sa License, and you must mention your sources in what you write ...

Related issues:
Go to Borders of non resizable objects
Go to LineNode Arrange Bug
Go to UI improvement
Go to Adding formulas using highlighting
Go to Editing equation from Writer

Contents


Alignment of Sm objects in Writer

Options

Our interest lies in objects, more precisely StarMath formulas, aligned as characters. For these, there are several options of aligning.

From Bottom

This places object so that it's top is at the level of baseline, but allows you also to change the distance of top and text baseline afterwards.

Center

Has three options: baseline, row, character. And aligns the center of the object to the center of row etc.

Top / Bottom

Places the top/bottom of the object to top/bottom of the row or character or to the baseline.

Conclusion

If we want to somehow synchronize the position of Sm formula with baseline of the text, we probably need to introduce new option of aligning these. We could calculate how far should be top of the object from the text baseline. Then we could use the option 'from bottom' to move the object, plugging in our calculated value.

Our Aim

Another problem is actually deciding what we want to achieve. We want the equation to look nice between the text, but there are various options for this.

If we have just one line of equation (it's quite hard to say what is nice looking alignment if there are more lines stuck as character) and there is text directly in that line, we want to have the Sm text baseline match the surrounding text baseline. This can be probably done by looking up whether baseline of the line node is calculated and pass it on for the alignment of the object.

If there is no text directly in the line node and therefore nothing to take the baseline from, we probably want to align the equals sign to the middle of the surrounding text line.

If there is no equals sign, then we might want the 'over bar' be in the middle of the line (if there is one).

We could go on and on, so we can't really give alignment for any type of equation, because it's pretty much matter of taste. We are trying to achieve nice alignment for few cases that we think are essential.

Tracing the alignment setting function

For us probably most interesting file to start with is ascharanchoredobjectposition.cxx or for definition of functions of class SwAsCharAnchoredObjectPosition we might have to go to anchoredobjectposition.hxx. The class have pointers on SwFrmFmt which has pointer on SwFmtVertOrient which has eVertOrient. eVertOrient determines vertical orientation for example it can equal text::VertOrientation::LINE_CENTER.

Functions that calculates the position are SwAsCharAnchoredObjectPosition::CalcPosition() which then calls SwAsCharAnchoredObjectPosition::_GetRelPosToBase to get the relative position to baseline.

From Sm side

Placing breakpoint on SmDocShell or SmGraphicWindow constructors, might lead us to the function that sets the alignment parameters. Firstly I tried to trace what passes the Window to SmGraphicWindow hoping to lead me to what we look for, but I lost the track because it was passed by some frames.

The only interesting thing from this end is that backtracing we get to:

   #14 0x0468a8a3 in SwWrtShell::InsertObject (this=0x85e2800, xRef=..., 
   pName=0xbfffe354, bActivate=1 '\001', nSlotId=0)
   at /home/miko/ooo-build/build/ooo320-m12/sw/source/ui/wrtsh/wrtsh1.cxx:424

which is the innermost function we probably have to go, but we can't really get any information from here.

From Sw side

Another approach is to look at ascharanchoredobjectposition.cxx and trace from here. Earlier on I examined SmDocShell construction SmGraphicWindow, now our interest is in SetBase. The order in which they are ran is SmDocShell - SetBase - SmGraphicWindow.

The trace here described is done by adding equations to opened document, but the same way applies to opening a document containing an equation.

eVertOrient

This value determines the alignment. It can take several constants, the one set initially for objects in Sw is com::sun::star::text::VertOrientation::CHAR_CENTER = 5.

     b objectpositioning::SwAsCharAnchoredObjectPosition::_GetRelPosToBase(const SwTwips _nObjBoundHeight,
                                                                           const SwFmtVertOrient& _rVert)

shows usage of the value in aligning, the value is get by: (iniside SwFmtVertOrient is the value called eOrient)

    const sal_Int16 eVertOrient = _rVert.GetVertOrient();

Because it's get from SwFmtVertOrient I looked for the object definition and found that the value is set by:

   b SwFmtVertOrient::SetVertOrient

but the breakpoint on that function was never hit except by SwNumFmt::SetGraphicBrush which sets it to 0 on some occasions.

So the value probably gets there when the object is constructed, where it can be set.

So I looked down from _GetRelPosToBase, it's called by:

   b objectpositioning::SwAsCharAnchoredObjectPosition::CalcPosition()
   113     const SwFrmFmt& rFrmFmt = GetFrmFmt();
   187     const SwFmtVertOrient& rVert = rFrmFmt.GetVertOrient();
   191     const SwTwips nRelPos = _GetRelPosToBase( nObjBoundHeight, rVert );

GetFrmFmt gets mpFrmFmt which value is set in constructor:

   b SwAnchoredObjectPosition::SwAnchoredObjectPosition( SdrObject& _rDrawObj )  // it's the constructor of parent

it goes to

   b objectpositioning::SwAnchoredObjectPosition::_GetInfoAboutObj()

where we can find

   103         mpContact = static_cast<SwContact*>(GetUserCall( &mrDrawObj ));
   106     }
   108     // determine anchored object, the object belongs to
   109     {
   111         mpAnchoredObj = mpContact->GetAnchoredObj( &mrDrawObj );
   114     }
   124     // determine format the object belongs to
   125     {
   127         mpFrmFmt = &mpAnchoredObj->GetFrmFmt();

so we have to go back to the constructor of SwAsCharAnchoredObjectPosition object which is in

   b SwFlyCntPortion::SetBase( const SwTxtFrm& rFrm, const Point &rBase,
   358     // determine drawing object
   359     SdrObject* pSdrObj = 0L;
   380     else
   381     {
   382         pSdrObj = GetFlyFrm()->GetVirtDrawObj();
   383     }
   384 
   385     // position object
   386     objectpositioning::SwAsCharAnchoredObjectPosition aObjPositioning(
   387                                     *pSdrObj,
   388                                     rBase, nFlags,
   389                                     nLnAscent, nLnDescent, nFlyAsc, nFlyDesc );
   390 
   391     // OD 2004-04-13 #i26791# - scope of local variable <aObjPosInProgress>
   392     {
   393         // OD 2004-04-13 #i26791#
   394         SwObjPositioningInProgress aObjPosInProgress( *pSdrObj );
   395         aObjPositioning.CalcPosition();
   396     }
   397 
   398     SetAlign( aObjPositioning.GetLineAlignment() );

So we have to investigate what's happening when SdrObject which is passed to the SwAsChar.. constructor is constructed.

VertOrientation CHAR_CENTER

CHAR_CENTER is the default constant for Sm objects, therefore I searched every occurence in the code and looked for where it's assigned to some value. It does in three cases:

   SwFmt* SwDoc::GetFmtFromPool( USHORT nId ) at poolfmt.cxx:1335
   BOOL SwFmtVertOrient::QueryValue( uno::Any& rVal, BYTE nMemberId ) const at atrfrm.cxx:1244
   BOOL SwFmtVertOrient::PutValue( const uno::Any& rVal, BYTE nMemberId ) at atrfrm.cxx:1285


GetFmtFromPool got hit with the right constructor values when document was saved and opened again. Also PutValue got hit few times which one of them conatianed vertical orientation CHAR_CENTER. This happened before CalcPosition was hit.

SwFmtVertOrient constructor

As we had chance to learn above, there are cached objects with alignment that are later passed to aligning function (_GetRelPosToBase). The alignment values therefore get to the object when they're constructed. So I tried putting a bp on constructor.

   b SwFmtVertOrient::SwFmtVertOrient

The object is constructed at the start twice for eOrient 1(TOP) and 5(CHAR_CENTER). This case I'll examine later.

Then I tried to change the alignment through object dialog. I got hit the constructor twice.

#0  SwFmtVertOrient (this=0x8b58f08, nY=-218, eVert=6, eRel=0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/atrfrm.cxx:1223
#4  0x06717e74 in SfxItemSet::Put (this=0xbfffdb08, rItem=...)
    at /home/miko/ooo-build/build/ooo320-m12/solver/320/unxlngi6.pro/inc/svtools/itemset.hxx:154
#5  0x067a59e5 in SwFrmPage::FillItemSet (this=0x8b32bd0, rSet=...)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/ui/frmdlg/frmpage.cxx:1068
#12 0x019dfed3 in Button::Click (this=0x8b2a158)
    at /home/miko/ooo-build/build/ooo320-m12/vcl/source/control/button.cxx:166

Here button click calls some functions which finally gets to FillItemSet, which calls the construction with new value (eVert corresponds to new eOrient). I read the code in FillItemSet and found out, that it takes the SwFmtVertOrient object out of some stack, gets the value of new eOrient from the dialog window and sets it to the object. Then it puts it back to the obscure stack.

Later on:

#0  SwFmtVertOrient (this=0x8b28ad8, nY=-213, eVert=6, eRel=0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/atrfrm.cxx:1223
#4  0x03db5918 in SfxItemSet::Put (this=0xb445f01c, rItem=...)
    at /home/miko/ooo-build/build/ooo320-m12/solver/320/unxlngi6.pro/inc/svtools/itemset.hxx:154
#5  0x03db4d06 in SwFmt::SetFmtAttr (this=0xb445f000, rAttr=...)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/attr/format.cxx:459
#6  0x03e9b463 in SwFlyInCntFrm::MakeObjPos (this=0x8ac6548)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/flyincnt.cxx:224
#11 0x0426b712 in objectpositioning::SwAsCharAnchoredObjectPosition::CalcPosition (this=0xbfffcfa8)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/objectpositioning/ascharanchoredobjectposition.cxx:334

Here the CalcPosition gets the value through some functions. MakeObjPos gets the SwFmtVertOrient, but the new value of eOrient is already set. The code here is quite messy but not that interesting since the value is already set and the object position is just being calculated.

inserting Sm object - SwFmtVertOrient constructor hits

Breakpoint on constructor, which gets eVertOrient value for constructing SwFmtVertOrient.

   (gdb) b SwFmtVertOrient::SwFmtVertOrient(long, short, short)

the constructor is called many times by other functions not of our interest therefore we put a condition:

   (gdb) condition 1 eVert!=0

Now we can insert a formula (important is that the formula is inserted first time since the document was open or we can open a document with already inserted formula) and we got first hit:

#0  SwFmtVertOrient (this=0xbfffdd58, nY=0, eVert=1, eRel=0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/atrfrm.cxx:1223
#1  0x040936b3 in SwDoc::GetFmtFromPool (this=0x8593d18, nId=3074)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/doc/poolfmt.cxx:1335
#2  0x03dd691f in SwEditShell::GetFmtFromPool (this=0x85e16a0, nId=3074)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/edit/edfmt.cxx:172
#3  0x04535227 in SwFlyFrmAttrMgr (this=0xbfffe024, bNew=1 '\001', 
    pSh=0x85e16a0, nType=4 '\004')
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/ui/frmdlg/frmmgr.cxx:92
#4  0x0468b34c in SwWrtShell::InsertOleObject (this=0x85e16a0, xRef=..., 
    pFlyFrmFmt=0x0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/ui/wrtsh/wrtsh1.cxx:600
#5  0x0468abd0 in SwWrtShell::InsertObject (this=0x85e16a0, xRef=..., 
    pName=0xbfffe344, bActivate=1 '\001', nSlotId=0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/ui/wrtsh/wrtsh1.cxx:499

In this bt is most important function InsertOleObject.

The function works with Sm objects in different way than with others and uses bStarMath. Our first breakpoint went through:

   597 	SwFlyFrmAttrMgr aFrmMgr( TRUE, this, FRMMGR_TYPE_OLE );

Which constructs SwFlyAttrFrmMgr with type OLE. Inside SwFlyFrmAttrMgr constructor the object SwWrtShell (caller of InsertObject) calls GetFmtFromPool

    83 			case FRMMGR_TYPE_OLE:	nId = RES_POOLFRM_OLE;		break;
    86 		aSet.SetParent( &pOwnSh->GetFmtFromPool( nId )->GetAttrSet());

Now SwDoc::GetFmtFromPool searches whether object with the same nId exists, if it doesn't it creates it calling:

  1162 	SwAttrSet aSet( GetAttrPool(), pWhichRange );
  1328             aSet.Put( SwFmtVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME ));

(first line shows what aSet is)

Our second hit of SwFmtVertOrient constructor is not very important. While the object is going to be put into aSet it's cloned therefore there is the constructor call.


Moving back to InsertOleObject after adjusting some parameters of aFrmMgr it goes:

   615     SwFlyFrmFmt *pFmt = SwFEShell::InsertObject( xRef, &aFrmMgr.GetAttrSet() );

which goes inside SwDoc::Insert which in case of sm object (again it has to find out whether the caller is Sm object) goes:

   982             nId = RES_POOLFRM_FORMEL;
   984 		pFrmFmt = GetFrmFmtFromPool( nId );

in GetFrmFmtFromPool if object with the same nId is not yet created it gets to:

  1332 	case RES_POOLFRM_FORMEL:
  1333 		{
  1334             aSet.Put( SwFmtAnchor( FLY_AS_CHAR ) );
  1335             aSet.Put( SwFmtVertOrient( 0, text::VertOrientation::CHAR_CENTER, text::RelOrientation::FRAME ) );
  1336             aSet.Put( SvxLRSpaceItem( 114, 114, 0, 0, RES_LR_SPACE ) );
  1337 		}


Let's go back to InsertOleObject. At the end it calls:

   620 	EndAllAction();

which prepares the object to be drawn, getting to:

   #12 0x03f75e6f in SwFlyCntPortion::SetBase (this=0x8832940, rFrm=..., 

from where it goes normally as described in eVertOrient section.

Recap

When Sm Object is inserted SwWrtShell::InsertOleObject is called and creates certain alignment objects that are later used in aligning. Important function that determines initial alignment is

   SwDoc::Insert(SwPaM const&, svt::EmbeddedObjectRef const&, SfxItemSet const*, SfxItemSet const*,SwFrmFmt*)

which calls

   SwDoc::GetFmtFromPool(unsigned short)

with nId = RES_POOLFRM_FORMEL which determines alignment type.

Once the object is created we can change this type through object dialog

   SwFrmPage::FillItemSet

gets the value set in dialog and changes the SwFmtVertOrient.eOrient type.

After every change in the document SwFlyCntPortion::SetBase is called and calculates specific position of object.

RequestObjectResize

Function SwFEShell::RequestObjectResize is responsible for changing the size of the object. Therefore I tried to trace a little bit around hoping to find something about how the object are constructed and aligned.

The only calls when the size is actually changed were done by SwOleClient::ViewChanged which retrieves the VisualAreaSize from the object and sets it using RequestObjectResize. Unfortunately the information about object aligning is not very descriptive.

Inside RequestObjectResize the changes are done through SwFlyFrm of the aligned object. The information basically is stored in aFrm (SwRect) which is frame in which object lies and aPrt (SwRect) which is rectangle with position relative to aFrm. Borders around the object (if there are any) make the difference between aFrm size and aPrt size.

Very strange issue is that when the function gets hit first time after changing the object size, it goes through pFly->ChgSize(aSz) where aSz is a new size, but after this command is done the object size in pFly (SwFlyFrm) seems not to be changed. We can notice the change first at the start of the next hit of the function, which means the changes had to be applied in between.

Actual changing of size in RequestOjectResize is not done always. It's done only when changing the formula, bt:

#0  SwFEShell::RequestObjectResize (this=0x85e0898, rRect=..., xObj=...)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/frmedt/fefly1.cxx:1440
#1  0x0467c131 in SwOleClient::ViewChanged (this=0x88392f8)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/ui/uiview/swcli.cxx:165
#11 0x00b362c3 in SfxObjectShell::SetVisAreaSize (this=0x89fe800, rVisSize=...)
    at /home/miko/ooo-build/build/ooo320-m12/sfx2/source/doc/objembed.cxx:186
#12 0x065d8043 in SmDocShell::Repaint (this=0x89fe800)
    at /home/miko/ooo-build/build/ooo320-m12/starmath/source/document.cxx:677

Implementation

Introduction

What do we want to implement? We need a piece of code that would in case of Sm object instead of default way of aligning got value from the object representing the distance between text baseline and top of object rectangle and use it in alignment.

There have been three options discussed by now:

(read the original text from issue 972 here)

  • Introduce new text::VertOrientation:: constant for equations (basically new option in the object dialog) and add this option to _GetRelPosToBase which would return value got from Sm object. This option I consider to be appropriate, because this is really new way of aligning and therefore should have its own text::VertOrientation:: constant. But there is a serious compatibility issue, new option of aligning would need change of odt file and would be incompatible with older verions, therefore is not possible.
  • (proposed by mba) 'Don't store it (the position value) at all and make baseline alignments a pure UI interaction that has to be applied manually. Once the formula is loaded again we could detect that it is baseline aligned (because its current position "eventually" matches the one we would get by baseline alignment) and offer to maintain this alignment by recalculation in case the formula is modified. ' Might be doable if there is no other option.
  • Set vertical orientation of Sm objects in Writer to NONE(as from bottom in object dialog) and in some appropriate piece of code change the value of SmFmtVertOrient.nY to the one we want. Then when aligning algorithm gets to _GetRelPosToBase it in case of VertOrientation::NONE returns value nY. There were doubts about this option that when changing text the value would have to be recalculated. That is not needed as we just want the equation to "sit" on the baseline and it will do it regardless of any changes to the text around. So the only point when the value is going to be calculated is when the formula is changed. This solution does not require any changes to file format as the only thing we effectively do is "grab" the object and move it to the right position using program instead of having users to do it manually.

Parts of code involved (3rd option)

The SwFmtVertOrient is get from SwDoc::GetFmtFromPool which is called by SwDoc::Insert with special value nId=RES_POOLFRM_FORMEL (formel as German for formula) in case of Sm object. SwDoc::GetFmtFromPool takes the SwFmtVertOrient object from aSet and if it's not yet there it creates it with eOrient = text::VertOrientation::CHAR_CENTER. This would be changed to text::VertOrientation::NONE.

Now the only task is to put the correct value into SmFmtVertOrient.nY (simply by using SmFmtVertOrient::SetPos) before the algorithm gets to _GetRelPosToBase, which returns the nY value. After that the algorithm finishes the aligning which is not of our interest any more and we shouldn't do the changes after this point.

First problem is that I didn't find any part of code that would handle SwFmtVertOrient between it's get by GetFmtFromPool and it's used by CalcPosition. I thought firstly that we could put it into CalcPosition, but that gets called every time we change anything in the line, but we want the code to be executed only when the object is changed. So we need to find appropriate part of code for the hack and find a way how to access and rewrite things in aSet.

I found an important function that sets the size and position of the object called RequestObjectResize

   b SwFEShell::RequestObjectResize

it can be called by SwOleClient::ViewChanged, SwWrtShell::CalcAndSetScale, SwWrtShell::InsertObject, SwOleClient::RequestNewObjectArea (the latter two doesn't concern us, first is just for inserting object and second I didn't get hit at all)

The RequestObjectResize has access to every aligning object as SwFlyFrm etc. We could implement there that in case of changing size of the object the position is retrieved from Sm and used to set the Baseline value.

Another option might be to try to do this from Sm side. Every time formula is changed it's parsed, we could implement it into parsing method, where it would be more appropriate, however we have to be able to get to the objects that store the values.

Access to Sm

There has to be value retrieved from Sm object, specifically SmTableNode (as explained below).

So we need two processes, setting the value after arranging all the nodes (probably in SmTableNode::Arrange) and reading it on an appropriate place in Sw. We can do this by adding the value to property set of SmModel.

From udkapi/com/sun/star/beans:

   void setPropertyValue( [in] string aPropertyName, [in] any aValue ) 
           raises( com::sun::star::beans::UnknownPropertyException, 
                   com::sun::star::beans::PropertyVetoException, 
                   com::sun::star::lang::IllegalArgumentException, 
                   com::sun::star::lang::WrappedTargetException ); 
   any getPropertyValue( [in] string PropertyName ) 
           raises( com::sun::star::beans::UnknownPropertyException, 
                   com::sun::star::lang::WrappedTargetException ); 

example of code that works with PropertySet wrtsh1.cxx:

   573 	if( aMathData.Len() && svt::EmbeddedObjectRef::TryRunningState( xRef.GetObject() ) )
   574 	{
   575 		uno::Reference < beans::XPropertySet > xSet( xRef->getComponent(), uno::UNO_QUERY );
   576 		if ( xSet.is() )
   577 		{
   578 			try
   579 			{
   580 				xSet->setPropertyValue( ::rtl::OUString::createFromAscii("Formula"), uno::makeAny(::rtl::OUString( aMathData ) ) );
   581                                 bActivate = FALSE;
   582 			}
   583 			catch ( uno::Exception& )
   584 			{
   585 			}
   586 		}
   587 	}

before this we have to make sure it's a Sm object:

   564 	SvGlobalName aCLSID( xRef->getClassID() );
   565 	bStarMath = ( SotExchange::IsMath( aCLSID ) != 0 );

E-mails about this with mba and tl can be found here

Baseline of Sm object

This section deals with the baseline value that would be passed to Sw.

Essentially there is only nBaseline of SmTableNode that has to be passed, with value 0 if the baseline is not set (it can't be set to zero as a proper value). We basically deal with two cases:

  • Baseline is not set: means that
    • there are more lines in the equation object (in which case it's not possible to align it to the text and IMHO anchored as character is not the best option of anchor). In this case we want probably align it to the middle of the line and leave the user to change it if required.
    • there are used structure nodes that don't have baseline (this is investigated below)
  • Baseline is set: means that there is just one line of equation and that line has baseline set, in this case we clearly want the baseline of the table node (whole formula) to be aligned with the baseline of the surrounding text.

Screenshots:

  • No change
Formulas inserted without any change (vertical orientation is therefore centre)


  • Moved on right position
Formulas manually moved to the right position (orientation none = "from bottom


  • Surrounding text modified
Surrounding text is changed, we can see that it doesn't effect the baseline to baseline alignment


  • Formulas with borders
There are borders added to the formulas, which changes their size without changing position, therefore they are misaligned


Solution that I considered as the best, is one where program does exactly the same as user would do manually and we can see that in case of changing text, alignment is preserved (small problem with the borders). Saving and loading file preserves alignment as well.

The problem with borders arises because for other objects that can be resized are the borders put into the object rectangle and that is not changed therefore nothing moves. Whereas for Sm objects, which can't be resized are the borders added onto the object rectangle changing its size and thus moving the object in case of "From Bottom" alignment. This problem is therefore more general and have to be treated for all non-resizeable objects. In function RequestObjectResize we might be able to get the width of borders (because changing the object rectangle is done here) and move the object by the appropriate value up, but this solution doesn't seem as a good approach as there has to be more general way of treating non-resizable object, where this should be implemented. Otherwise it might be applied on resizeable object also, which is wrong. With this issue deals properly subpage Borders of non resizable objects

formulas without baseline

To find out whether there could be some formulas not having a baseline and still being possibly 'rightly' aligned we need to review all arrange methods for different nodes and look for which can end up not having a baseline.

Node on which subnode depends having a baseline
Visible Nodes always
AttributeNode body
BinDiagonalNode never
BinHorNode always
BinVerNode never
BracebodyNode any subnode must have
BraceNode body
LineNode always*/any subnode must have
ExpressionNode always*/any subnode must have
MatrixNode if only one row
OperNode body
RootNode body
SubSupNode body
TableNode only one LineNode
UnHorNode body or operator
VerticalBraceNode body

I missed out AlignNode, FontNode because it's exactly the same as the it's subnode, just changing the alignment.

The * means that it's wrong and after slash there is what it should be. Fix for LineNode is described here. The fact that it works despite there are mistakes, relies on a very obscure thing. The distance between baseline and nAlignM is the same for every node in the formula (except cases like 'sum' character, but these doesn't change the final distance in the structure node). Which happens because every visible node (ie character) has this distance the same (if the font size is the same) and any structure node either is horizontally aligned and the distance is preserved, or is vertically/diagonally aligned and the baseline is cleared (so there is no distance). Subsequently if a node without a baseline is aligned to one that has it, the distance will preserve as they'll align by middles and the resulting baseline will be taken from the one that has it. This is precisely what happens when arranging a Line/ExpressionNode.

00845         // provide an empty rectangle with alignment parameters for the "current"
00846         // font (in order to make "a^1 {}_2^3 a_4" work correct, that is, have the
00847         // same sub-/supscript positions.)
00851         SmRect::operator = (SmRect(aTmpDev, &rFormat, C2S("a"),GetFont().GetBorderWidth()));

In arrange function of LineNode (the same for ExpressionNode) is firstly made a fictional SmRect as if of letter "a" which is shrunk to width 1. Then all the real nodes are aligned to it. As the SmRect "a" has baseline, resulting LineNode will have baseline, although there might be just one subnode which doesn't have baseline. And this is wrong.

On the other hand as I pointed out above, this works, because the distance between nAlingM and nBaseline is the same and if there would be objects that should normally align by nAlingM they will align by nBaseline, but the result will be the same. Nevertheless we need it this way as if there were objects that have different distances between nBaseline and nAlignM and we wanted to align them, either we would align them by baselines which would result in strange error, not having the middles aligned, or we would align them by middles and the baselines would be misaligned.

So the way it's coded now, there can be no formula without a baseline (if it has only one line), because LineNode always have a baseline and it's the only subnode of TableNode (if there is only one line of formula). I was thinking whether it's good to rewrite it, which is not very hard, but I think that this way it doesn't do any harm and we have a way how to align all formulas into the surrounding text, as all of them has baselines.

It is still disputable whether in complicated formulas that wouldn't have baseline (but now has because of the fact that baseline is created in LineNode) we are satisfied with the final alignment in the text (ie satisfied with that the middle is simply distant some constant from the surrounding text baseline). I see no problem with this, as for such formulas there is no real way to say which alignment is right.

putting SwFmtVertOrient into SwAttrSet

I was not sure whether putting SwFmtVertOrient inside RequestObjectResize will work and will change the position so I tried it out.

Code that I added was:

	const SwFrmFmt *pFmt = pFly->GetFmt();				
	if (pFmt)
	{
	    SfxItemSet aFrmSet( pDoc->GetAttrPool(), pFmt->GetAttrSet().GetRanges() );
    	    aFrmSet.Set( pFmt->GetAttrSet() );
	    aFrmSet.Put( SwFmtVertOrient( 100 , text::VertOrientation::NONE, text::RelOrientation::FRAME ));
	}

which is supposed to add SwFmtVertOrient into SfwItemSet which is used to store objects for alignment. Code was compiled fine. But executing it, there was no difference. SwFmtVertOrient was added into the set, but when the program got to CalcPosition the SwFmtVertOrient was different (the usual one), so the object had to be rewritten or it's not retrieved from this SfxItemSet. Although I sort of did this looking at code of function SwFEShell::ReplaceSdrObj which is supposed to change the alignment and works in similar way.


So next thing I tried was changing this in SwFlyCntPortion::SetBase which calls CalcPosition and therefore is directly before using the alignment values.

             SwFlyInCntFrm *pFlyFrm = GetFlyFrm();
             SwFlyFrmFmt *pFmt = (SwFlyFrmFmt*)pFlyFrm->GetFmt();
             const SwFmtVertOrient &rVert = pFmt->GetVertOrient();
             SwFmtVertOrient aVert( rVert );
             aVert.SetPos( 0 );
             aVert.SetVertOrient( ::com::sun::star::text::VertOrientation::NONE );
             pFmt->LockModify();
             pFmt->SetFmtAttr( aVert );
             pFmt->UnlockModify(); 

This worked well changing vertical orientation to NONE and setting position 0 (or any other which I have put in).

So I tried this kind of approach to change the SwFmtVertOrient in RequestObjectResize. By this kind of approach I mean instead of putting the object into the set I used SetFmtAttr command of SwFmt. The following code was placed into the RequestObjectResize:

	SwFrmFmt *pFrmFmt = pFly->GetFmt();
				
	if (pFrmFmt)
	{
				
	     const SwFmtVertOrient &rVert = pFrmFmt->GetVertOrient();
             SwFmtVertOrient aVert( rVert );
             aVert.SetPos( 300 );
             aVert.SetVertOrient( ::com::sun::star::text::VertOrientation::NONE );
             pFrmFmt->LockModify();
             pFrmFmt->SetFmtAttr( aVert );
             pFrmFmt->UnlockModify();

	}

And it changed the position as it should have done.


This means that we are capable of playing with it inside RequestObjectResize. My latest idea of implementation tries to put it into SmDocShell::SetText, which has to be tried out. The problem might be with getting to the SwFmt of the object. I'm not sure whether it's possible. Subject to investigation later.

trying Sm implementation

SmDocShell::SetText

I started to think of much easier and nicer solution. Every time the equation is parsed new position has to be put into SwFmtVertOrient which ensures baseline to baseline alignment. This could be done directly in SmDocShell::SetText which has easy access to baseline of SmTableNode. SmDocShell has parent SfxObjecShell which has parent SfxShell which has access to SfxItemPool. If the pool stores information about aligning of the object, than we can easily get SwFmtVertOrient which is SfxPoolItem, change it and put back into the pool.

Unfortunately I didn't manage yet to find out what is stored in the pool. We can't look it up in gdb, and it's hard to try to get something unless we know what is there. The easiest way I thought of was just track down where is it copied, but surprisingly it's not easy, it appears to be copying from SfxApplication item pool, but it should be the same as SfxShell item pool which it inherits from.

SmModule and SmConfig

This object stores values about formula as well as SmConfig which is contained in it. There are values like horizontal alignment and such, so I was investigating whether it can't be used to store and retrieve vertical alignment that we are interested in. Sadly, this is used only when closing and saving file, not each time formula is edited.

Sm - look for any SfxItemSet/Pool

If there was a way how to get to SwFmtVertOrient in Sm it would be stored as SfxPoolItem, therefore I was trying to review all the uses of ItemSet/Pool to see whether there are some possibilities.

objects that use it:

  • SmModule/SmConfig
  • EditEngineItemPool
  • SmDialog
  • Sm..TabPage
  • SmViewShell
  • SmDocShell

None of these seem to have it, although as I described above I'm not sure where SmDocShell takes it from. SmViewShell gets it from SfxViewFrame which again I'm not sure whether is supposed to have alignment information, but I really doubt that. Looking at how is it used in Sw it should be stored in SwFmt which Sm doesn't have access to.

SwFEShell::RequestObjectResize implementation

I finally decided to try and implement it here to see how it works, because as described in 'Sw or Sm implementation' it's probably not possible to implement it into Sm. The function RequestObjectResize is responsible for resizing the objects and the code changing baseline need to be used exactly when the formula is changed; object is resized.

				SwFrmFmt *pFFmt = pFly->GetFmt();
				
				if (pFFmt)
				{
				
				     const SwFmtVertOrient &rVert = pFFmt->GetVertOrient();
             SwFmtVertOrient aVert( rVert );
             
             if (aVert.GetVertOrient()==(::com::sun::star::text::VertOrientation::NONE))
             {
   
   sal_Int32 nBaseline;
   BOOL bStarMath = TRUE;
   uno::Any aBaseline;
             
   SvGlobalName aCLSID( xObj->getClassID() );
   bStarMath = ( SotExchange::IsMath( aCLSID ) != 0 );

   if (bStarMath) 
     if( svt::EmbeddedObjectRef::TryRunningState( xObj ) )
     {
    		uno::Reference < beans::XPropertySet > xSet( xObj->getComponent(), uno::UNO_QUERY );

    		if ( xSet.is() )
    		{
    			try
    			{
    				aBaseline = xSet->getPropertyValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("BaseLine") ) );
    			}
    			catch ( uno::Exception& )

    			{
    			}
    		}
    	}
    
    nBaseline = ::comphelper::getINT32(aBaseline);
		
		// nBaseline is in OutputDevice metric MAP_100TH_MM but the position in Sw is in MAP_TWIP therefore the map mode has to be changed
		long lBaseline = (long) nBaseline;
    
    const MapMode aSourceMapMode( MAP_100TH_MM );
    const MapMode aTargetMapMode( MAP_TWIP );
    
    lBaseline = OutputDevice::LogicToLogic( lBaseline, aSourceMapMode.GetMapUnit(), aTargetMapMode.GetMapUnit() );
    
    nBaseline = (sal_Int32) lBaseline;
    
    // nBaseline*=264;
    // nBaseline/=467;
    
    // we want to have the distance is messured so that positive is moving it down, hence the minus
    aVert.SetPos( -nBaseline );
             
             }
             pFFmt->LockModify();
             pFFmt->SetFmtAttr( aVert );
             pFFmt->UnlockModify();

				}

The code is not very tidy, because I don't want to leave it there anyway.

Moreover I had to change sm unomodel.cxx to be able to retrieve nBaseline. And poolfmt.cxx to write that formulas should be aligned 'from bottom' as default. Finally it worked well so now we need to find better place to put it in, because if by any chance size of the formula is not changed but baseline is, this code won't be executed.

Second thing to do is check Sm rounding and changes of metrics, because I noticed that sometimes there are small errors in baseline and letter position; happens when zooming too much.

Where to implement? - ideas once more

Changing a formula triggers a very complicated process of parsing setting view, drawing and so on. Now the problem is to decide where in this process we want to set the position. Logicaly as there exists object for positioning of as char anchored objects, it should be implemented there, rather than anywhere else.

The object has suitable name SwAsCharAnchoredObjectPosition. But this object is constructed every time the formula needs to be positioned by SwFlyCntPortion::SetBase, therefore we can't store the value there. The values must be stored elsewhere. They are taken from SwFmtVertOrient which is SfxPoolItem stored in SwAttrSet inside SwFmt.

Normally changing the object, in our case the formula, doesn't have any effect on format. Therefore there is no direct way of using actions that change it. So we have to make use of actions that have something to do with positioning, although are not directly related. Basically the only action done when changing the formula is changing the size.

Of course there might be some other calls that we could make use of. To find these I begun more thorough trace. We start in method that sets the text of the formula.

SmDocShell::SetText

Subsequent calls to important methods in Sm.

  • SmDocShell::Parse
  • SmDocShell::Repaint
    • SmDocShell::GetSize
      • SmDocShell::ArrangeFormula
      • SmNode::GetSize
    • SmDocShell(SfxObjectShell)::SetVisAreaSize
    • SmGraphicWindow::Invalidate

from SetVisAreaSize the code goes to
SfxObjectShell::SetVisArea
SFX_APP()->NotifyEvent(SfxEventHint( SFX_EVENT_VISAREACHANGED, GlobalEventConfig::GetEventName(STR_EVENT_VISAREACHANGED), this));

then it goes through notifies and broadcasts

SfxInPlaceClient_Impl::notifyEvent calls ViewChanged and after that Invalidate on the Client which is in this case SwOleClient

SwOleClient::ViewChanged

SwWrtShell(SwFEShell)::RequestObjectResize
SwWrtShell(SwEditShell)::EndAllAction

#0  objectpositioning::SwAsCharAnchoredObjectPosition::CalcPosition (
    this=0xbfffcbd8)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/objectpositioning/ascharanchoredobjectposition.cxx:108
#1  0x03fbff47 in SwFlyCntPortion::SetBase (this=0x87eb1a0, rFrm=..., 
    rBase=..., nLnAscent=224, nLnDescent=52, nFlyAsc=224, nFlyDesc=52, 
    nFlags=6 '\006')
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/porfly.cxx:412
#2  0x03fbfbbe in SwFlyCntPortion (this=0x87eb1a0, rFrm=..., pFly=0x8ad4330, 
    rBase=..., nLnAscent=224, nLnDescent=52, nFlyAsc=224, nFlyDesc=52, 
    nFlags=6 '\006')
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/porfly.cxx:311
#3  0x03fe1481 in SwTxtFormatter::NewFlyCntPortion (this=0xbfffd5e4, rInf=..., 
    pHint=0x8aed990)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/txtfly.cxx:737
#4  0x03fdeac0 in SwTxtFormatter::NewExtraPortion (this=0xbfffd5e4, rInf=...)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/txtfld.cxx:339
#5  0x03fb16cf in SwTxtFormatter::NewPortion (this=0xbfffd5e4, rInf=...)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/itrform2.cxx:1330
#6  0x03fae714 in SwTxtFormatter::BuildPortions (this=0xbfffd5e4, rInf=...)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/itrform2.cxx:408
#7  0x03fb207f in SwTxtFormatter::FormatLine (this=0xbfffd5e4, nStartPos=0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/itrform2.cxx:1552
#8  0x03f8cdfc in SwTxtFrm::FormatLine (this=0xb3172000, rLine=..., 
    bPrev=0 '\000')
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/frmform.cxx:1232
#9  0x03f8de5d in SwTxtFrm::_Format (this=0xb3172000, rLine=..., rInf=..., 
    bAdjust=0 '\000')
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/frmform.cxx:1603
#10 0x03f8e530 in SwTxtFrm::_Format (this=0xb3172000, pPara=0xb4462000)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/frmform.cxx:1795
#11 0x03f8f18f in SwTxtFrm::Format (this=0xb3172000)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/text/frmform.cxx:1996
#12 0x03ec9f28 in SwCntntFrm::MakeAll (this=0xb3172000)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/calcmove.cxx:1467
#13 0x03ec5dc6 in SwFrm::OptPrepareMake (this=0xb3172000)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/calcmove.cxx:418
#14 0x03f0b99d in SwFrm::OptCalc (this=0xb3172000) at ../inc/frame.hxx:1070
#15 0x03f098a9 in SwLayAction::_FormatCntnt (this=0xbfffdfec, 
    pCntnt=0xb3172000, pPage=0xb316f000)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/layact.cxx:2493
#16 0x03f08e99 in SwLayAction::FormatCntnt (this=0xbfffdfec, pPage=0xb316f000)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/layact.cxx:2305
#17 0x03f04783 in SwLayAction::InternalAction (this=0xbfffdfec)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/layact.cxx:923
#18 0x03f03cf1 in SwLayAction::Action (this=0xbfffdfec)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/layact.cxx:644
#19 0x03e7a220 in ViewShell::ImplEndAction (this=0x85dad38, bIdleEnd=0 '\000')
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/view/viewsh.cxx:242
#20 0x03e2dfc8 in ViewShell::EndAction (this=0x85dad38, bIdleEnd=0 '\000')
    at ../../../inc/viewsh.hxx:625

In this process we need to change the value by the time we get to CalcPosition, because there the value is used. We have to do it after arranging the formula. And in Sw because otherwise we don't have access to it. Also I think that it should be logically done before EndAllAction (we don't want it to be executed every time position of the formula on the page changes).

Therefore the only possible place is SwOleClient::ViewChanged or SwFEShell::RequestObjectResize.

But ViewChanged and consequently RequestObjectResize are called every time inside whole EndAllAction process at some other place. Therefore we want to place the code at some place where it's called only if the formula changes. Which is precisely the place in RequestObjectResize where the size is actually changed (if the new size is different from the old one).


There is a small trouble though, if the formula having different baseline and at the same time the same size. I can't imagine this happening, but in that case the baseline wouldn't be retrieved and the formula would be misaligned.

Let's have a look by whom ViewChanged can be called.

writing a letter in the line where the object is(or moving the object):
SfxInPlaceClient::Invalidate

changing the formula:
SfxInPlaceClient_Impl::notifyEvent (as seen above)
SfxInPlaceClient::Invalidate

so we could use that when changing the formula the SmOleClient::ViewChanged is called by SfxInPlaceClient_Impl::notifyEvent, overload ViewChanged with some boolean that in this case would be set true and retrieve the nBaseline value only in such case. I'm not sure whether this chaos is worth it.


Another thing is, that when formula is inserted ViewChanged is not called from SfxInPlaceClient_Impl::notifyEvent nor it's called with a different size therefore the baseline is not retrieved inside RequestObjectResize. Let's have a look, when and how is SwFmtVertOrient constructed for the first time.

#0  SwFmtVertOrient (this=0xbfffdcf8, nY=0, eVert=0, eRel=0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/layout/atrfrm.cxx:1223
#1  0x040dd870 in SwDoc::GetFmtFromPool (this=0x858d438, nId=3075)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/doc/poolfmt.cxx:1342
#2  0x040ddddd in SwDoc::GetFrmFmtFromPool (this=0x858d438, nId=3075)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/doc/poolfmt.cxx:1398
#3  0x040143c9 in SwDoc::Insert (this=0x858d438, rRg=..., xObj=..., 
    pFlyAttrSet=0xbfffdfc4, pGrfAttrSet=0x0, pFrmFmt=0x0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/doc/doc.cxx:1009
#4  0x042db63d in SwFEShell::InsertObject (this=0x85da948, xObj=..., 
    pFlyAttrSet=0xbfffdfc4, pGrfAttrSet=0x0, pFrmFmt=0x0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/core/frmedt/fefly1.cxx:882
#5  0x046d5acc in SwWrtShell::InsertOleObject (this=0x85da948, xRef=..., 
    pFlyFrmFmt=0x0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/ui/wrtsh/wrtsh1.cxx:618
#6  0x046d51b4 in SwWrtShell::InsertObject (this=0x85da948, xRef=..., 
    pName=0xbfffe2e4, bActivate=1 '\001', nSlotId=0)
    at /home/miko/ooo-build/build/ooo320-m12/sw/source/ui/wrtsh/wrtsh1.cxx:499

and right after, in InsertOleObject is EndAllAction() and we want to have the right value in SwFmtVertOrient before that

So we have to retrieve the baseline in between. As this would be copying the previus code in RequestObjectResize we could make a new function for this.

In SwDoc::Insert (right above in the bt) we would get to SwDocShell (pDocShell) and from there to SwWrtShell (pWrtShell) and we can call some function defined for SwFEShell to change the format SwFmtVertOrient. But these calls might be unnecessary complication, they might not even work, because I'm not sure whether SwDocShell is created and whether it refers to the right object.

We could also use our knowledge of Sm and simply set the value not retrieving anything, because we know that the value at the start will be the baseline for simple character, which we could get from output device, but we don't know the font used in Sm(we would have to retrieve that). :(

Update: I just tried and implemented it, copying the code from RequestObjectResize, adjusting it a bit, so that it doesn't need to call SwDocShell and SwWrtShell, but it directly changes the SwFrmFmt that it returns. The implementation worked well, having to include few files.


There is a small annoyance that if someone decides to change the vertical orientation to center, or anything else and afterwards decides to put it back, he needs to open Sm, change the equation and close it, so that the value is retrieved again (because it was put 0 by default changing to other orientation). This can be fixed adjusting the code for changing the orientation from the object dialog.

Update: Sadly this can't be fixed any simple way and it's probably not considered as faulty. It works this way for all objects and it can't be fixed because easily because the value of "From Bottom" orientation is stored in SwFmtVertOrient which is changed every time the object is moved for other types of orientation.


Another fully new option for all of this would be to reimplement current system, so that for the positioning and aligning information is stored in the Sm project. In some SmFmt, which would be derived from SwFmt and then passed to Sw when it needs it. This way the object would have had direct control. It doesn't seem as nice solution though, because it's establishing totally new contact. The projects are basically self controlled, without communication, except some dialog window communication. And there was no need for such a contact by now, because basically Sm drew the formula passed it to sw and it places it according to its own rules.

Update: Or we could implement function in SwOleClient that would be called from SfxInPlaceClient_Impl::notifyEvent something like SwOleClient::FormatChanged defined as dummy in SfxInPlaceClient_Impl and this function would be executed instead of the code in RequestObjectResize, but we have to check that it's called every time the formula is parsed, which I'm not sure of, although it was like that in my trace.

SwOleClient::FormatChanged

When repainting the equation, SmDocShell::SetVisArea is called, afterwards event 'OnVisAreaChaged' is broadcasted. We can see this in backtrace

#0  SfxInPlaceClient_Impl::notifyEvent (this=0x8ae6288, aEvent=...)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/sfx2/source/view/ipclient.cxx:217
#1  0x06593ebe in OCommonEmbeddedObject::PostEvent_Impl (this=0x89fa210, aEventName=...)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/embeddedobj/source/commonembedding/miscobj.cxx:340
#2  0x065be0e7 in DocumentHolder::notifyEvent (this=0x89fded0, Event=...)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/embeddedobj/source/general/docholder.cxx:1255
#3  0x00b12742 in SfxBaseModel::postEvent_Impl (this=0x8a0c2c0, aName=...)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/sfx2/source/doc/sfxbasemodel.cxx:2919
#4  0x00b10597 in SfxBaseModel::Notify (this=0x8a0c2c0, rBC=..., rHint=...)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/sfx2/source/doc/sfxbasemodel.cxx:2552
#5  0x00e8aa48 in SfxBroadcaster::Broadcast (this=0x8a03b98, rHint=...)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/svtools/source/notify/brdcst.cxx:66
#6  0x009d2fc5 in SfxApplication::NotifyEvent (this=0x81bdae8, rEventHint=..., bSynchron=1)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/sfx2/source/appl/appcfg.cxx:1067
#7  0x00b2d863 in SfxObjectShell::SetVisArea (this=0x8a03b98, rVisArea=...)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/sfx2/source/doc/objembed.cxx:136
#8  0x0662f6fd in SmDocShell::SetVisArea (this=0x8a03b98, rVisArea=...)
    at /home/miko/stable_ooo-build/ooo-build/build/ooo320-m19/starmath/source/document.cxx:1351

In SfxInPlaceClient_Impl::notifyEvent is called (for this event):
00217 m_pClient->ViewChanged();
00218 m_pClient->Invalidate();

We could simply add new method to the SwOleClient that would be called from here, dealing with format that has to be changed in case of starmath formulas. This way of implementation could be used by other objects in future.

Once we get SwOleClient::FormatChanged called, we can, in case of Sm object, call some new function for SwWrtShell, that would retrieve and set the baseline as it's now done in SwFEShell::RequestObjectResize (by the last implementation).

I was tracing around to find out if we couldn't make the change from SwOleClinet, using public methods of SwWrtShell (and the objects it's derived from), instead of calling a new function from SwWrtShell. The problem is with getting SwFlyFrm or in fact we need SwFmt (or SwFrmFmt). There are methods like GetCurFrmFmt() or GetCurrFlyFrm(), but these don't have arguments. Whereas FindFlyFrm( ..XEmbeddedObject>& ) which is used in RequestObjectResize and other methods from SwFEShell, take the reference to the object as an argument to do extended search for the FlyFrm. I'm not sure whether this is needed, but FindFlyFrm is private, therefore we can use it only by the SwFEShell(or SwWrtShell) methods.

I implemented it like this. There were some problems when I added new private method to SfxInPlaceClient and started to build Sw without writing a new method that would override it in SwOleClient. Once I put it there, it worked, but I'm worried it might not work for other objects derived from SfxInPlaceClient (as in Sc and such). The rest of implementation (eg new function to SwFEShell) was straightforward and worked.

In this implementation we must ensure that the function that retrieves the baseline from Sm is called only by the formula object that has just been arranged, otherwise the value of baseline, is not the one for the object that calls it.

Baseline Alignment Equations

This project is part of Google Summer of Code 2010.

Problem

(copied from the famous Issue 972

When a formula is inserted in a writer document (anchored as char) the baselines of the formula and the surrounding text are not aligned. Thus the formulas appear jumping up and down on the textline.The current implementation does not work, and equations are misaligned.

Work in progress from previous students


TAG TODO.png the needs

  • new interface definition ( offapi),
  • new file format extension ( needs ODF TC agreement ),
  • new implementation ( starmath )

Questions from Michal Spisiak, we'll progressively answer :

1(no) if the nBaseline was calculated (by the present function) every time something changes for every node, 
everything would be correctly aligned
2(yes) nBaseline is calculated only by the function aFM.GetAscent() which is at rect.cxx:157 
(the line is in opengrok in my code it's 161) otherwise is assigned value only when passed by another node
3(yes) bHasBaseline is not always true at the end of arranging
4(yes) aFM.GetAscent() is not defined in starmath project (why is this?)
5(yes) if nBaseline is calculated then nAlignB has always the same value as nBaseline
6(yes) Draw function of text node calls GetBaselineOffset which then calls GetBaseline and the text is drawn
7(yes) the statement above says implicitly that Baseline must be calculated otherwise we would
have got error when calling GetBaseline
8(yes) variables nAlignT nAlignC and nAlignB are not used when actually drawing the text but are used
in SmRect::draw function which draws the rectangles


Identified Tasks

  • TAG DONE.png build go-oo (and/or OOo4Kids) and Openoffice.org
  • TAG DONE.png document how to add symbols in go-oo, same in OOo4Kids
  • TAG DONE.png start debugging : find interesting breakpoints
  • TAG DONE.png In parallel to the debugging, discover starmath code organisation
  • TAG TODO.png Search how are implemented existing graphical symbols
  • TAG TODO.png Propose the implementation for the missing one
  • TAG DONE.png Document starmath code organisation, concerning the alignment issue (mostly rect.cxx, node.cxx and any usefull other file)
  • TAG DONE.png Identify all Arrange cases (said differently : enumerate the existing equation types)
  • TAG DONE.png Verify the algorithm is correct (not sure), and check for every equation type
  • TAG TODO.png Analyze completely the problem
  • TAG TODO.png Propose several solutions
  • TAG TODO.png Debate about the possibilities
  • TAG TODO.png Choose a progressive solution
  • TAG TODO.png Implement it
  • TAG TODO.png Write specs
  • TAG TODO.png commit
  • TAG TODO.png test
  • TAG TODO.png Improve

Arrange methods

Motivation

Equation nodes are firstly created by parser. Before they can be drawn they have to be arranged ie some values are initialized and position has to be calculated. Hence we examine this part of code.


Default arrange method

void SmNode::Arrange(const OutputDevice &rDev, const SmFormat &rFormat)
{
        SmNode *pNode;
        USHORT  nSize = GetNumSubNodes();
        for (USHORT i = 0;      i < nSize;      i++)
        if (NULL != (pNode = GetSubNode(i)))
                        pNode->Arrange(rDev, rFormat);
}

As we can see this arrange method only calls arrangement of subnodes. For SmVisibleNodes, which don't have any subnodes, is the arrange method reimplemented so that actual node is arranged ie certain attributes are prepared.

Code can be looked up choosing appropriate visible node and looking at the arrange method here.

SmStructureNode arrange method

At the start of arrange function SmStructerNodes doesn't have any alignment information. Firstly are arranged subnodes, so they get all alignment info, but don't have correct position. Using size of the subnodes, they can be aligned one to another in a way particular to each structure node. AlignTo finds the point they should be placed and Move moves them to that position (? it seems to me that it doesn't move the subnodes of the subnode, which would suggest, that the position ie aTopLeft is only relative to it's parent). Rectangle of the node (calling the arrange) is rewritten with first subnode using operator =. Afterwards area of structure node is extended by areas of subnodes using ExtendBy, so all its subnodes are contained in it.

AlignTo gets parameters (in arrange function) that determine how is the node going to be aligned to its parent node, using alignment information of the parent node.

Code can be looked up choosing appropriate structure node and looking at the arrange method here.

Usage of nBaseline in Arrange methods

Omitting reference to nBaseline in function where is just copied over, is used in SmRect::BuildRect (for initialization), SmRect::AlignTo (through GetBaseline), SmRect::Draw (through GetBaseline), SmTextNode::Draw (through GetBaselineOffset)

IMHO: It shows that nBaseline in fact can't have any other value then got by function FontMetric::GetAscent() in SmRect::BuildRect, because it's just copied over, unless some function eg ExtendBy won't set bHasBaseline false and than it's not worked with it at all.

IMHO-Answer: That is all that is needed, nBaseline is used only for text nodes and get the value when Arranged, other nodes doesn't need baseline.

Basic node structure

All that is done with node tree is: SmNode::Prepare (initializes the values), SmNode::Arrange (arranges the nodes, their position), SmNode::Draw (finally nodes are drawn) First constructed node by parser is SmTableNode that gets SmLineNode as subnodes. SmLineNode represents line of an equation and gets ExpressionArray as subnodes which contains all other nodes in line.

SmRect alignment parameters

Which parameters are actually used when drawing and what do they represent?

drawing functions

SmRect::Draw basically draws sth only for debugging puposes in debug mode.

SmNode::Draw we can see here that it recursively is called on subnodes adding offset to a rPosition, but aTopLeft is not relative (to its parent), because it's drawn by rPosition.

SmRectangleNode::Draw uses ItalicSize and aTopLeft and cancels nBorderWidth

SmPolyLineNode::Draw used for wideslash

SmRootSymbolNode::Draw used for root

SmTextNode::Draw used for text, uses nBaseline to find baseline of text to be drawn

attributes of rect
basic one, both dimensional:
Point 	aTopLeft
Size 	aSize

horizontal dimension:
long 	nItalicLeftSpace
long 	nItalicRightSpace

vertical dimension:
long 	nBaseline
long 	nAlignT
long 	nAlignM
long 	nAlignB
long 	nLoAttrFence
long 	nHiAttrFence
(these are used in AlignTo for SmAtributNode)

long 	nGlyphTop
long 	nGlyphBottom
(Glyphs are used only for SmGlyphSpecialNode)

width:
USHORT 	nBorderWidth
(it's set by SmRect::SmRect usually using GetFont().GetBorderWidth() but I don't really see the purpose, it's used only in SmRectangleNode::Draw)
usage of the parameters

As we can see, most of the parameters are not actually used for drawing, but they are essential for aligning, ie finding position for subnodes of structure node. (see SmRect::AlignTo)

Code obscurities

This section is about parts of code that I don't or didn't understand. Various subsections will be probably moved to more appropriate sections once I will find out their meaning.

operator = in Arrange function

At the end of most Arrange functions there is command SmRect::operator = (*pNum); with suitable pointer in place of pNum. The main command in definition of the operator is new (this) SmRect(rRect);, which is so called placement new. It assigns new memory for object type SmRect with value rRect, but unlike basic new operator it assigns the the memory at given place. In this case the place is 'this', location of the caller function. So the operator overwrites the caller with the operand of operator =.

(for ericb: this is no longer question, as you can see, I've figured it out, I was just confused by the fact that it actually overwrites the caller by its subnode)

To sum it up, the caller (of the arrange method) is almost the same with one of its subnodes. The only command in Arrange function after the operator is ExtedBy, which extends boundaries of the caller by other subnodes and adjusts alignment information appropriately.

strange syntax of ExtendBy in Arrange function

At the end of some Arrange function goes command ExtendBy(*pDenom, RCP_NONE).ExtendBy(*pLine, RCP_NONE, pLine->GetCenterY()); where ExtendBy is function defined in SmRect. I did some debugging, checked always who is the caller of the function, which object is taken as rRect in ExtendBy. The result is, that the line is executed exactly as

ExtendBy(*pDenom, RCP_NONE);

ExtendBy(*pLine, RCP_NONE, pLine->GetCenterY());

so de facto it works well. Might be worth trying to rewrite it to the normal form, whether it goes with the same result.

Answer: ExtendBy returns the resulting node therefore using dot, we get called extend by on it second time ;)

starting with GDB

This section deals with how I started to use gdb and shows some interesting breakpoints. If you aren't familiar with gdb have a look at some tutorials on the web, it's easy to find useful information.

Building

After compiling ooo-build we have to recompile the starmath with symbols. Also, for debugging it's very useful to have debugging mode of Starmath turned on. So, firstly we apply a simple patch. Probably the easiest way is to write it there manually. All that is needed is to add these lines into rect.hxx

#ifdef DEBUG
// when defined, allows to draw rectangles for debugging
#define SM_RECT_DEBUG
#endif

on the right place. Preferably at the start, where are libraries included. And uncomment line

#define SM_RECT_DEBUG

in node.cxx.


Then we just build the project with symbols.

cd starmath/
rm -rf unxlngi6.pro
source ../LinuxX86Env.Set.sh
build debug=true


Breakpoints

For us the most important breakpoints are in Arrange functions. For instance write

(gdb) b SmBinVerNode::Arrange

You can try any node in place of SmBinVerNode. Most of the structure nodes use function ExtendBy which calculates their final position and alignment.

(gdb) b SmRect::ExtendBy

Very good way to have a look at the code is Starmath documentation here: SmNode class reference

  • TAG TODO.png describe conditionnal breakpoints with gdb (provide examples)


Goals

  • TAG TODO.png Fix issue972 Implementing a correct baseline alignment for equations anchored as characters in Writer documents

(should work for "to character" too).

  • TAG PROGRESS.png add missing symbols (e.g. rounded symbol for angular sector and left/right arrow with vertical bar, like : Lvert line arrow.png and Rvert line arrow.png )
  • TAG DONE.png remove accents in greek alphabet for french ?
  • TAG TODO.png add colors (improve ...) ?

Side effects: a change in .odt specification will result, and specs must be written in parallel

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox