Paragraph Level Tab Stop Distance in Impress

Impress now supports paragraph scoped definition of tab stop distance. Fixing the bugs tdf#102261 & tdf#148925 in the process and improving Impress’ interoperability with OOXML.

Thanks to our partner SUSE for working with Collabora to make this possible.

If you’d prefer a talk instead of a blog post. See the recording below 🙂

Please accept YouTube cookies to play this video. By accepting you will be accessing content from YouTube, a service provided by an external third party.

YouTube privacy policy

If you accept this notice, your choice will be saved and the page will refresh.

Comparison image of how the file visually appeared on PowerPoint and Impress

As it is the usual case with interoperability problems, the file when opened by Impress seemed different. The fundamental problem here is that in the opened file the tab stop distances appeared wrong.

Let’s breakdown the related features in the problem, I will try to refer things by their names in ODF, to keep what i call what somewhat consistent.

As you all likely know… naming is hard.

In both PowerPoint & Impress tab stops are associated with style of a paragraph.

In Impress it is possible to define properties that affect positions of the tab stops in 2 different scopes. But PowerPoint has an additional third one.

<style:paragraph-properties>
  <style:tab-stops>
    <style:tab-stop style:position="5.0cm"/>
    <style:tab-stop style:position="10.0cm"/>
  </style:tab-stops>
</style:paragraph-properties>

Firstly, Impress allows to define a list of tab stops for a paragraph style. Where each of the tab-stops specify a position.

Secondly, Impress also let’s the user to define a document wide tab-stop-distance. Since this property is only available through the default style, it cannot be customized per paragraph.

tab-stop-distance basically dictates what happens when there’s no corresponding tab-stop defined for a given tab character. It sets the automatic distance between sequential tabs.

So what is the problem? Both of these exist for PPTX too, but there’s also an additional ability that makes it possible to define paragraph scoped tab-stop-distance. And that is the feature that Impress is missing.

Therefore, it was required to implement the missing feature of paragraph scoped tab-stop-distance.

After a bit of research the plan was:

  1. Document Model
  2. Make EditEngine consider this new property
  3. Expose the UNO api
  4. Implement import/export for PPTX
  5. Implement import/export for ODF
  6. Connect the loose ends in the UI

First we’ll need to store this information in the document model.

After that we should make sure EditEngine considers this new property when layouting the text.

With that in place, we should have something that is workable with, so we expose the UNO api and make it import/exportable to PPTX and ODF.

And lastly we should make sure the existing UI elements, can take the property in the account.

Let’s elaborate on each of these steps a little bit more in depth.

Document Model

class SvxTabStopItem : SfxPoolItem
{
    SvxTabStopArr maTabStops;
    sal_Int32 mnDefaultDistance;
}

We want the property to live in the paragraph styles in the end.

Therefore this meant the new tab-stop-distance will have the same scope as the tab-stops implementation.

So adding it in the SvxTabStopItem which keeps the information for tab-stops in a paragraph made sense.

Accessing paragraph scoped tab-stop-distance was already only going to be needed where there was an access to tab stop list anyway, so bundling these together was also going to make plumbing information later quite easier too.


bool SvxTabStopItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const
{
  ...
}

bool SvxTabStopItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId )
{
  ...
}

Also SvxTabStopItem’s QueryValue & PutValue implementations was adjusted to finish up the document model related bits.

With that in place it is now time to alter the behavior.

Make EditEngine consider this new property

It is important to reiterate here that the scope of this work was Impress and Draw. Therefore the behavior is only implemented in EditEngine for now. So what is implemented here won’t cover Writer.


case EE_FEATURE_TAB:
{
  {...}
  aCurrentTab.aTabStop =
      pNode->GetContentAttribs().FindTabStop(nCurPos, aEditDoc.GetDefTab());
  {...}
}
break;

SvxTabStop ContentAttribs::FindTabStop( sal_Int32 nCurPos, sal_uInt16 nDefTab )
{
  const SvxTabStopItem& rTabs = GetItem( EE_PARA_TABS );
  {...}
  // if there's a default tab size defined for this item use that instead
  if (rTabs.GetDefaultDistance())
    nDefTab = rTabs.GetDefaultDistance();
  {...}
  return aTabStop;
}

After a bit of research, It appeared that EditEngine determines the tab stop position inside CreateLines. Using a function in ContentAttribs called FindTabStop. So we just make sure FindTabStop considers the paragraph scoped tab-stop-distance we put in place and that’s about it for here.

Exposing the UNO API

#define SVX_UNOEDIT_PARA_PROPERTIES \
  ... \
{ UNO_NAME_EDIT_PARA_TABSTOP_DEFAULT_DISTANCE, EE_PARA_TABS, ::cppu::UnoType<sal_Int32>::get(), 0, MID_TABSTOP_DEFAULT_DISTANCE }, \
  ...

With the QueryValue & PutValue implementations from before in place, exposing the UNO API only took a single line change.

Import & export for PPTX

<a:p>
  <a:pPr defTabSz="182880"/>
  <a:r>
    <a:t>0  1   2   3   4   5</a:t>
  </a:r>
</a:p>
<a:p>

For import from PPTX the property we have been calling tab-stop-distance is given by defTabSz (Default Tab Size) property of pPr (Paragraph Properties).

So we handle that during import and map it directly to our tab-stop-distance defined in SvxTabStopItem.

And as expected we just do the reverse of that for the export, this time mapping from SvxTabStopItem to paragraph properties’ defTabSz.

Import & export for ODF

For import and export of the property, Regina Henschel had a suggestion for allowing the tab-stop-distance property that was previously only allowed inside the default style in arbitrary paragraph styles. Which made super sense!

So the exported markup would appear as:

<style:style style:name="P1" style:family="paragraph">
  <style:paragraph-properties loext:tab-stop-distance="5cm">
    <style:tab-stops>
      <style:tab-stop style:position="2.54cm"/>
    </style:tab-stops>
  </style:paragraph-properties>
</style:style>

Obviously while implementing that we make sure, we export to LibreOffice Extension namespace, since this feature isn’t yet supported by the ODF standards.

But for future compatibility, we implement the import for both style & loext namespaces.

Connect loose ends in the UI

void SvxRuler::UpdateTabs()
{
  {...}
  tools::Long lCurrentDefTabDist = lDefTabDist;
  if(mxTabStopItem->GetDefaultDistance())
    lCurrentDefTabDist = mxTabStopItem->GetDefaultDistance();
  tools::Long nDefTabDist = ConvertHPosPixel(lCurrentDefTabDist);
  {...}
}

To finish up, the horizontal ruler in the UI was adjusted so that it would display the new tab-stop-distance correctly.

And that about sums it all up, now it is possible to define paragraph scoped automatic tab-stop-distance, in Impress.

What’s left?

Before finishing off, let me give you an overview of what else could be done or improved in the area.

  • Right now, there’s no UI in place to define these paragraph scoped default tab-stop-distances yet, unlike PowerPoint.
  • Also as I’ve mentioned before, the scope of this work was Impress & Draw, therefore – in Writer it is not possible to define paragraph scoped tab-stop-distance right now.

Want to start using this?

You can get a snapshot / demo of Collabora Office 23.05 and try it out yourself right now: try the unstable snapshot.

Implementation was done with a series of small changes: