ImproveMathEquationEditor/Baseline AlignmentEquations/fr
From Wiki.ooo4kids.org
|
Alignement de la ligne de base des équations avec le texte dans Writer |
Back to Improve Math Equation editor
Alignement d'objets Starmath dans Writer
Options
Notre intérêt réside dans le fait d'aligner des objets de type "Équations Starmath" alignés "comme caractères". Pour cela, il y a plusieurs options d'alignement.
Depuis le bas
<a revoir>
Positionne l'objet de telle façon que le haut de celui-ci soit au dessu de la ligne de base. 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
Trois options possibles : ligne de base, ligne, caractère. Aligne le milieu de l'objet avec le milieu de la ligne de base, de la ligne, le caractère 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.
Sm Access
The main object of Starmath from which there is access to the other objects is SmGraphicWindow. Through SmDocShell it can handle parsing, arranging and drawing the equation. Now access to SmGraphicWindow is done through SmGraphicAccessible, which we would need to reimplement if we wanted to get this new feature working. The reason is that we need to provide a way for Writer to get information for aligning purposes.
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.
Then we 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 );
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.
- Baseline is set: means that there is just one line of equation and that line has baseline set, which happens if there is some visible node (character etc.) in this case we clearly want the baseline of the visible node to be aligned with the baseline of the surrounding text.
Screenshots:
- No change
- Moved on right position
- Surrounding text modified
- Formulas with borders
Solution that I considered as 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.
I made these observations from SmTableNode::Arrange method. I haven't investigated it that deeply, but if there are some cases popping up, IMHO it's not problem to adjust Arrange methods accordingly, it's not a complicated piece of code.
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-resizeable object, where this should be implemented. Otherwise it might be applied on resizeable object also, which is wrong.
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.
Sw or 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.
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
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
-
build go-oo (and/or OOo4Kids) and Openoffice.org
-
document how to add symbols in go-oo, same in OOo4Kids
-
start debugging : find interesting breakpoints
-
In parallel to the debugging, discover starmath code organisation
-
Search how are implemented existing graphical symbols
-
Propose the implementation for the missing one
-
Document starmath code organisation, concerning the alignment issue (mostly rect.cxx, node.cxx and any usefull other file)
-
Identify all Arrange cases (said differently : enumerate the existing equation types)
-
Verify the algorithm is correct (not sure), and check for every equation type
-
Analyze completely the problem
-
Propose several solutions
-
Debate about the possibilities
-
Choose a progressive solution
-
Implement it
-
Write specs
-
commit
-
test
-
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.
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
-
describe conditionnal breakpoints with gdb (provide examples)
Goals
-
Fix issue972 Implementing a correct baseline alignment for equations anchored as characters in Writer documents
(should work for "to character" too).
-
add missing symbols (e.g. rounded symbol for angular sector and left/right arrow with vertical bar, like :
and
)
-
remove accents in greek alphabet for french ?
-
add colors (improve ...) ?
Side effects: a change in .odt specification will result, and specs must be written in parallel