Silverlight 5 Beta Rough Notes–Character Spacing and Line Stacking

Note: these are early notes based on some initial experiments with the Silverlight 5 beta, apply a pinch of salt to what you read.

The Silverlight 5 beta has a couple of more options around controlling text layout (with some more slated for the RTM of V5) with those specifically being the new CharacterSpacing option and an addition to the LineStackingStrategy enumeration.

CharacterSpacing

Of these, I find the former easier to understand. If you look at the Control class in Silverlight 5 then you’ll spot a new dependency property added to it (shown here in Reflector);

image

and this new CharacterSpacing property really applies to where you use text so places such as;

  • the TextBlock
  • the Label
  • the TextElement from which the Paragraph, Section, Run, Span classes that you use to form the content of a RichTextBox are all derived

the value itself (from the docs) is an integer dependency property which controls the distance between characters in “1000s of an em” where, again, the docs tell us;

“Em size is a typographical measure that specifies the approximate width of the capital letter "M" in the Roman alphabet, measured in the units that are prevalent in a particular technology. Silverlight em sizes are given in pixels. The apparent visual size of the em size varies per font.”

Here’s a simple example of using this setting on a TextBlock using the “Buxton Sketch” font;

Get Microsoft Silverlight

and that’s simply binding the slider to the CharacterSpacing on the TextBlock directly as you can see on line 23 of this XAML snippet below;

<UserControl
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d"
  x:Class="SilverlightApplication15.MainPage"
  Width="640"
  Background="#FFF37676">
  <Grid
    x:Name="LayoutRoot"
    Background="#FF78A7F2">
    <StackPanel
      Orientation="Vertical"
      d:LayoutOverrides="Height"
      HorizontalAlignment="Center">
      <TextBlock
        HorizontalAlignment="Center"
        Text="this is 48pt Buxton Sketch"
        FontFamily="/SilverlightApplication15;component/Fonts/Fonts.zip#Buxton Sketch"
        FontSize="48"
        Margin="6"
        CharacterSpacing="{Binding ElementName=slider,Path=Value}" />
      <StackPanel
        Orientation="Horizontal"
        Width="284">
        <Slider
          x:Name="slider"
          Minimum="-500.0"
          Maximum="500.0"
          Width="192" />
        <TextBlock
          TextWrapping="Wrap"
          VerticalAlignment="Center"
          Text="{Binding ElementName=slider,Path=Value,StringFormat=\{0:F0\} em spacing}" />
      </StackPanel>
    </StackPanel>
  </Grid>
</UserControl>

I could equally well have set the CharacterSpacing on the UserControl itself rather than setting it locally on the TextBlock and this example below tries to illustrate that;


Get Microsoft Silverlight

and all this is doing is setting the CharacterSpacing property on the UserControl and having it picked up from the “ambient” value on the control rather than having it set directly on each element. Note that there’s a;

  • TextBlock
  • Label
  • DataGrid
  • RichTextBox

all (hopefully) picking up the CharacterSpacing although I notice that the RichTextBox doesn’t seem to play ball and I’m not sure at the time of writing whether that’s expected behaviour or not.

That XAML looks like;

<UserControl
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
  mc:Ignorable="d"
  x:Class="SilverlightApplication15.MainPage"
  Background="#FFF37676"
  d:DesignHeight="558.527"
  FontFamily="/SilverlightApplication15;component/Fonts/Fonts.zip#Buxton Sketch"
  FontSize="48"
  x:Name="control"
  d:DesignWidth="612.468">
  <Grid
    x:Name="LayoutRoot"
    Background="#FF78A7F2"
    DataContext="{Binding Source={StaticResource SampleDataSource}}">
    <Grid
      Margin="6">
      <StackPanel
        Orientation="Vertical"
        d:LayoutOverrides="Height">
        <TextBlock
          HorizontalAlignment="Center"
          Text="this is a TextBlock"
          Margin="6" />
        <sdk:Label
          Content="this is a Label"
          HorizontalContentAlignment="Center" />
        <sdk:DataGrid
          Height="100"
          AutoGenerateColumns="False"
          ItemsSource="{Binding Collection}"
          Margin="6"
          FontSize="18.667">
          <sdk:DataGrid.Columns>
            <sdk:DataGridTextColumn
              Binding="{Binding Property1}"
              Header="Property1" />
            <sdk:DataGridCheckBoxColumn
              Binding="{Binding Property2}"
              Header="Property2" />
          </sdk:DataGrid.Columns>
        </sdk:DataGrid>
        <RichTextBox
          Margin="6"
          IsReadOnly="True">
          <Paragraph>
            <Run
              Text="This is a rich text box" />
          </Paragraph>
          <Paragraph>
            <Run
              Text="with a few paragraphs" />
          </Paragraph>
          <Paragraph>
            <Run
              Text="within it" />
          </Paragraph>
        </RichTextBox>
        <StackPanel
          Orientation="Horizontal"
          HorizontalAlignment="Center">
          <Slider
            x:Name="slider"
            Minimum="-500.0"
            Maximum="500.0"
            Width="192"
            Value="{Binding ElementName=control,Path=CharacterSpacing,Mode=TwoWay}" />
          <TextBlock
            TextWrapping="Wrap"
            VerticalAlignment="Center"
            Text="{Binding Value, ElementName=slider, StringFormat=\{0:F0\} em spacing}" />
        </StackPanel>
      </StackPanel>
    </Grid>
  </Grid>
</UserControl>

and so while it’s based on a complex metric, that CharacterSpacing setting seems simple enough to figure.

LineStackingStrategy

Silverlight today has 2 options around LineStackingStrategy and they only apply to TextBlock whereas in Silverlight 5 they also apply on Block (and the types derived from it) and RichTextBox;

MaxHeight

The stack height is the smallest value that contains the extended block progression dimension of all the inline elements on that line when those elements are properly aligned. This is the default.


BlockLineHeight

The stack height is determined by the block element line-height property value.

If I’m honest, I don’t find that particularly easy to understand. I’m not a big “typography” person so some of the terminology gets complicated but I can kind of understand that in this text (taken from PowerPoint)

image

I’ve drawn the baselines in red and then marked on the text at the bottom how the ascent and descent are measured for the glyphs.

Given these metrics – how do you decide how to lay out lines of text that flow beneath each other? It’s very interesting to experiment with this a little and so I wrote a little user control that would display lines across the screen at every 10 pixels and experimented with some TextBlocks and Runs on top of it so I could see how positioning was working.

LineStackingStrategy = MaxHeight

This is the default strategy and so you don’t have to change any options to get it. In this simple example below;image

which is just built up using this XAML;

<TextBlock>
    <Run
        FontSize="24"
        Text="aaa" />
    <Run
        FontSize="48"
        Text="eee" />
        <Run
        FontSize="64"
        Text="ccc" />  
    <LineBreak />
    <Run
        FontSize="64"
        Text="ddd" />
    <Run
        FontSize="48"
        Text="ddd" />
        <Run
        FontSize="24"
        Text="ddd" />
            
    </TextBlock>

and I’m using 3 sizes of fonts on each line.

How come the text “ccc” isn’t at position 0 at the top of the screen and why the big gap between the bottom of the text on the first line and the top of the text on the line below? It seems simple enough to answer and it’s because I might have different letters such as;

image

and so the algorithm for laying out the text here feels something like;

  1. for the largest text, figure out the largest ascent possible for that font at that particular size and the largest descent possible and add them together to produce a height for the text line
  2. for the largest text, figure out where the baseline should be based on the ratio between largest ascent and largest descent possible
  3. position the baseline for all text at that position

what this means though is that for our original example we have quite a gap around our text and there’s no way of reducing that although what I can do is manipulate LineHeight but that only has effect if the height I am setting is larger than the height that would have been calculated anyway. For example;

image

but setting LineHeight to small values has no effect because the calculated line height is larger.

LineStackingStrategy = BlockLineHeight

If I switch to BlockLineHeight then I think the effect is the same as for MaxHeight except that if I set the LineHeight property to be lower than the value which would be calculated, it does indeed have an effect as you can see below;

image

for this XAML;

    <TextBlock
      LineStackingStrategy="BlockLineHeight"
      LineHeight="45">
    <Run
        FontSize="24"
        Text="aaa" />
    <Run
        FontSize="48"
        Text="eee" />
        <Run
        FontSize="64"
        Text="ccc" />  
    <LineBreak />
    <Run
        FontSize="64"
        Text="aaa" />
    <Run
        FontSize="48"
        Text="ccc" />
        <Run
        FontSize="24"
        Text="eee" />
            
    </TextBlock>

and that all looks great until you realise what happens if I add some other characters;

image

and so this is literally hard-coding the height of the line regardless of what that means for the text displayed on it and it feels difficult to know how you’d ever quite come to the right value for mixed text.

LineStackingStrategy = BaselineToBaseline

This option is new for Silverlight 5 and as the name suggests it’s controlling the space between one baseline and the next. I switched my example to use that for the first time and was a little surprised;

image

woah – I wasn’t really expecting my first line of text to be clipped like that. What’s going on? As you might expect, BaselineToBaseline is a bit odd for the first line of text because there’s no previous Baseline to use for the measurement so what the framework does is to use the ascent of the font set on the text container for the measurement.

In this case, the TextBlock doesn’t really have a font set on it but my UserControl that it lives within is using 48 point Buxton Sketch and so that’s being used here and it’s not really working out for my 64 point font as the ascent in question wouldn’t be large enough.

So, the first line is a special case. I can control that of course;

image

and now by varying LineHeight I can control the distance from the baseline of my first line of text to the baseline of subsequent lines of text as in;

image

and that gives me an element of control that the other 2 strategies don’t give me.

I thought I’d build this into a sample which tries to present the different options in more of a complete way – embedded into the page here although it might be nicer to take it out of browser to gain some screen space;

Get Microsoft Silverlight

Enjoy Smile