A couple monthes ago I had to implement an application UI using WPF. FlowDocument was used mainly, because it needed to be as close as possible to web pages. I used to freely position HTML-elements with CSS, but couldn’t find any solution for relative positioning of the inner elements. The Top, Left, Right and Bottom properties are completly missing. MSDN had given only Figure class. Neither HorizontalOffset nor VerticalOffset do work with FlowDocumentScrollViewer. Googling didn’t help also.

However, the solution was more than simple.

How web-browser engines do work

The most interesting information about its internal implementation provides WebKit. Blog is an essential source of information for those who do not want to learn the source code of the project, but wants to know about its architecture.

A special interest was caused by series of posts on the WebCore Rendering. On this page the main sentence was found, which helped to come up with solution for the problem: «Relative positioning is literally nothing more than a paint-time translation».

Being guided by the “postulate” above, I decided to find something similar in the depths of WPF.

TextEffect

While using RenderTransform with Translate, Rotate и Scale 2D-trasforms for UIElement, this techniques could be used for for text too. TextEffect class is the container of all transformations.

So, lets proceed to implementation. An example will be used from the blog’s page.

<div style="border:5px solid black; padding:20px; width:300px; margin-left:auto; margin-right:auto">
Here is a line of text. <span style="position:relative;top:-10px; background-color:#eeeeee">This part is shifted<br> up a bit</span>, but the rest of the line is in its original position.
</div>

The same example, but only for WPF, without positioning:

<FlowDocumentScrollViewer>
  <FlowDocumentScrollViewer.Document>
    <FlowDocument Name="doc" FontFamily="Verdana" FontSize="12px" LineHeight="18px">
      <Paragraph Padding="20px" BorderThickness="5px" BorderBrush="Black">
        Here is a line of text.
        <Span Name="shiftedText" Background="#EEE">This part is shifted<LineBreak/>
          up a bit
        </Span>, but the rest of the line is in its original position.
      </Paragraph>
    </FlowDocument>
  </FlowDocumentScrollViewer.Document>
</FlowDocumentScrollViewer>

Before applying TextEffect, you must know that it works only with the Run type. the effect will be observed when applied to Span or other element in the document.

Applying effect:

void DoEffect()
{
  foreach (var run in shiftedText.Inlines.OfType<Run>())
  {
    TextEffect f = new TextEffect();
    TranslateTransform t = new TranslateTransform(0, -10d);
    f.Transform = t;
    int selectionStart = doc.ContentStart.GetOffsetToPosition(run.ElementStart);
    int selectionLength = run.ElementStart.GetOffsetToPosition(run.ElementEnd);
    f.PositionStart = selectionStart;
    f.PositionCount = selectionLength;
    run.TextEffects.Add(f);
  }
}

And code for undo:

void UndoEffect()
{
  foreach (var run in shiftedText.Inlines.OfType<Run>())
  {
    run.TextEffects.Clear();
  }
}

Thus, we were able to get the desired effect.