none
Character spacing in TextBlock/TextElement

    Question

  • Is there a way to change the spacing between characters in a WPF text element?  Something like the CSS letter-spacing property?  Thanks!
    Wednesday, December 13, 2006 10:08 PM

Answers

  • Ahhhh sorry, I read your description of the requirements too fast there.

    It does seem that the letter-spacing aka kerning is pretty hidden away in the API. I am new to the typography area, but actually in the ongoing SDK I'll own some of the classes, so I better get familiar with this area fast :-)

    Much of the API exposure for typography is based on working against OpenType. And most of the support for OpenType in markup is through setting various attached properties of the Typography class. However, the kerning aspect of OpenType is pretty well hidden away. You can make decisions about ligatures, but that doesn't help for arbitrary letter-spacing because the ligatures are only selectively placed into the font. There's a Kerning property of Typography, but it's true/false (not helpful, and I'm not even sure what it does). If you're actually designing an OpenType font, there's a whole kerning table that can be used to specify kerning combinations. But that's way too complicated for what you are after, and it's manipulating the entire font, not just layout-on-demand.

    One way to handle what you want is to go in a different direction from the OpenType font support APIs, and instead to use a Glyphs object. See: http://msdn2.microsoft.com/en-us/library/system.windows.documents.glyphs.aspx

    Glyphs has a XAML-settable property on it called Indices. Unfortunately, this property isn't well documented. The type of the property is String. What that property actually takes is a mini-language. The minilanguage uses ; to distinguish glyph separations, and , to separate pairs of chr and offset. If you leave the front of the comma pair blank, it just applies the offset to the 'space between'.

    So, for example:

    <Page
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      >

      <StackPanel>

    <Glyphs

       UnicodeString       = "Wide load!"
       Indices             = ",150;,100;,100;,100;,100;,100;,100;,100;,100;"
       FontUri             = "file://c:/windows/fonts/times.ttf"
       Fill                = "Black"
       FontRenderingEmSize = "40"
    />

      </StackPanel>
    </Page>

    Is it elegant? Unfortunately not. The nature of how you specify the string for glyphs fights the whole nested-markup UI definition principle that XAML is supposed to work for. It's almost as inelegant as trying to use layout tricks and individual letters. And that offset takes experimentation. I'll keep rooting around and see if I can find anything else that's more in the typography or layout areas that does what you want without venturing into glyphs.

     

    Friday, December 15, 2006 1:00 AM

All replies

  • TextElement.FontStretch is probably what you want? It works off an enumeration of about 9 presets. The way it's documented now, it seems like those presets are all that you can use. However, I almost wonder whether there's some kind of number-factor usage we failed to document, because the structure of the type has a typeconverter on it. TypeConverters are often telltale signs of multiple modes of setting,

    You can set it on TextElement or TextBlock, and you can also set it as an attached property on any element container such that all the text display pieces in the container can all "inherit" the same setting.

     

    Wednesday, December 13, 2006 10:30 PM
  • Thanks, but I'm afraid FontStretch isn't what I'm after.  FontStretch widens or condenses the characters themselves; I'm looking for something that creates additional space between the characters.  (I've also found that FontStretch only seems to work for certain combinations of stretch preset and font family; for example Segoe UI doesn't seem to respect it at all.)

    To illustrate as best I can, I want something where the text is "abc" but it is rendered as a b c.  Does that clarify?

    If it makes any difference, I'm doing my testing using a TextBlock control, and I'm running on XP.
    Thursday, December 14, 2006 12:32 AM
  • Ahhhh sorry, I read your description of the requirements too fast there.

    It does seem that the letter-spacing aka kerning is pretty hidden away in the API. I am new to the typography area, but actually in the ongoing SDK I'll own some of the classes, so I better get familiar with this area fast :-)

    Much of the API exposure for typography is based on working against OpenType. And most of the support for OpenType in markup is through setting various attached properties of the Typography class. However, the kerning aspect of OpenType is pretty well hidden away. You can make decisions about ligatures, but that doesn't help for arbitrary letter-spacing because the ligatures are only selectively placed into the font. There's a Kerning property of Typography, but it's true/false (not helpful, and I'm not even sure what it does). If you're actually designing an OpenType font, there's a whole kerning table that can be used to specify kerning combinations. But that's way too complicated for what you are after, and it's manipulating the entire font, not just layout-on-demand.

    One way to handle what you want is to go in a different direction from the OpenType font support APIs, and instead to use a Glyphs object. See: http://msdn2.microsoft.com/en-us/library/system.windows.documents.glyphs.aspx

    Glyphs has a XAML-settable property on it called Indices. Unfortunately, this property isn't well documented. The type of the property is String. What that property actually takes is a mini-language. The minilanguage uses ; to distinguish glyph separations, and , to separate pairs of chr and offset. If you leave the front of the comma pair blank, it just applies the offset to the 'space between'.

    So, for example:

    <Page
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      >

      <StackPanel>

    <Glyphs

       UnicodeString       = "Wide load!"
       Indices             = ",150;,100;,100;,100;,100;,100;,100;,100;,100;"
       FontUri             = "file://c:/windows/fonts/times.ttf"
       Fill                = "Black"
       FontRenderingEmSize = "40"
    />

      </StackPanel>
    </Page>

    Is it elegant? Unfortunately not. The nature of how you specify the string for glyphs fights the whole nested-markup UI definition principle that XAML is supposed to work for. It's almost as inelegant as trying to use layout tricks and individual letters. And that offset takes experimentation. I'll keep rooting around and see if I can find anything else that's more in the typography or layout areas that does what you want without venturing into glyphs.

     

    Friday, December 15, 2006 1:00 AM
  • Thanks for your help Wolf.  I'm going to need to think about whether I want to go down to that level -- as I'm databinding rather than displaying fixed text, I'd have to create a value converter to perform the layout and generate the appropriate Indices string; I'll also need to change some code that currently uses TextPointers to instead run off the same glyph-layout engine.  Given issues like kerning, bearings, advance widths and all the other subtleties of typography, I don't think it will be practical to lay out the glyphs from the ground up, but I may be able to use a hidden TextBlock which will do a "natural" layout, capture the "natural" character positions from that using TextPointer and generate the indices by applying offsets to those.  At least now I know what direction to go in and what the effort will be.  Thanks again!
    Tuesday, December 19, 2006 1:51 AM
  • As opposed to Glyphs, here is an example of the kind of 'layout trick' that might be useful, although I don't fully understand your exact requirements yet:

    <Page
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:sys="clr-namespace:System;assembly=mscorlib"
      >
      <Page.Resources>
        <x:Array x:Key="myLetterList" Type="sys:String">
          <sys:String>M</sys:String>
          <sys:String>O</sys:String>
          <sys:String>U</sys:String>
          <sys:String>S</sys:String>
          <sys:String>E</sys:String>
        </x:Array>
        <DataTemplate x:Key="myDataTemplate">
          <TextBlock Text="{Binding}" Width="25"/>
        </DataTemplate>
      </Page.Resources>


        <ItemsControl
             ItemsSource="{Binding Source={StaticResource myLetterList}}"
             ItemTemplate="{StaticResource myDataTemplate}">
          <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
              <StackPanel Width="150" HorizontalAlignment="Left" Orientation="Horizontal"/>
            </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Page>

     

    Here, I'm just databinding to an array to keep the data simple, since I have no idea what yours looks like. And I'm hardcoding some widths.

    The main layout trick used here is creating the different ItemsPanelTemplate such that you get a horizontal orientation for your items, and can place uniform widths between the items within the templated container. You COULD also apply nonuniform widths, through properties in the data.

    This leaves you two potential problems to solve: generating individual chars to bind to if your data source comes as a complete string, and a way to avoid both of the hardcoded widths. The two widths (TextBlock in DataTemplate, StackPanel in ItemsPanelTemplate) would need to be aware of each other, and ideally you would have the former be a % of the latter, with appropriate failsafes if the textblock Width got too small to display the character. You could either have both widths be bindable properties from the data provider, or you could bind to the standard settable panel Width and apply a Converter that would factor the panel width into textblock width based on the char count from the data source (must be accessible as data or otherwise passed to the "parameter" parameter of the Converter). You may still have to premeasure the characters in a throwaway (or hidden, if that works) render pass as you describe, if your intention is that your spacing is fully relational to the advance width etc. that text generation through WPF Text property content otherwise provides.

    Wednesday, December 20, 2006 1:38 AM
  • Hi!

    Yes this is very a important to something like pseuda columns but with editing by common way by line.

    For example:
    You have the string:

    bla123   bla123   bla123   bla123   bla123


    Now I am edit It (just deleted "3" in the first word)

    bla12   bla123   bla123   bla123   bla123

    And now I have result:

    bla12b   la123b   la123b   la123b   la123


    I can edit string as I want, but every 6 letters I need some space.

    How to realize this behavior????




    People! We need help!
    • Proposed as answer by Phil InHoles Thursday, September 12, 2013 7:39 PM
    Monday, April 14, 2008 6:38 PM
  • Here's a little TextBlock hack I came up with today while trying to do the same thing (character / letter spacing / "Tracking" / whatever the dickens its called ...)

     

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    class TextBlock2 : TextBlock
    {
      /// <summary>
      /// Defines charachter/letter spacing
      /// </summary>
      public int Tracking
      {
        get { return (int)GetValue(TrackingProperty); }
        set { SetValue(TrackingProperty, value); }
      }
    
      public static readonly DependencyProperty TrackingProperty =
        DependencyProperty.Register("Tracking", typeof(int), typeof(TextBlock2), 
        new UIPropertyMetadata(0, 
        new PropertyChangedCallback(TrackingPropertyChanged)));
    
      static void TrackingPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
      {
        TextBlock2 tb = o as TextBlock2;
        
        if (tb == null
          || String.IsNullOrEmpty(tb.Text))
          return;
    
        tb._Tracking.X = (int)e.NewValue;
        tb._TrackingAlignment.X = -(int)e.NewValue * tb.Text.Length;
    
        if (tb._LastTrackingTextLength == tb.Text.Length)
          return; // Avoid re-creating effects when you don't have to..
    
        // Remove unused effects (string has shortened)
        while (tb._TrackingEffects.Count > tb.Text.Length)
        {
          tb.TextEffects.Remove(tb._TrackingEffects[tb._TrackingEffects.Count - 1]);
          tb._TrackingEffects.RemoveAt(tb._TrackingEffects.Count - 1);
        }
    
        tb._LastTrackingTextLength = tb.Text.Length;
        
        // Add missing effects (string has grown)
        for (int i = tb._TrackingEffects.Count; i <tb.Text.Length; i++)
        {
          TextEffect fx = new TextEffect()
          {
            PositionCount = i,
            Transform = tb._Tracking          
          };
          tb._TrackingEffects.Add(fx);
          tb.TextEffects.Add(fx);
        }
    
        // Ugly hack to fix overall alignment
        tb.RenderTransform = tb._TrackingAlignment;
        
      }
    
      TranslateTransform _Tracking = new TranslateTransform();
      TranslateTransform _TrackingAlignment = new TranslateTransform();
      List<TextEffect> _TrackingEffects = new List<TextEffect>();
      int _LastTrackingTextLength;
    
    }
    
    The general idea is to automatically create a TextEffect for each character in the string (this gives you expanded/condensed spacing, but right-aligned), and then offset the overall Visual render to keep the text left-aligned. There's probably a cleaner way to fix alignment but I didn't see any way to override the "Measure" and change the Visual bounds on a TextBlock.

     

    • Edited by Sichbo Sunday, May 09, 2010 4:47 PM Code formatting still broken,
    Sunday, May 09, 2010 4:44 PM
  • Has anyone figured out a clean way to do this yet? I'm surprised that "character tracking" (sometimes called "character spacing") isn't supported yet in WPF as it is a commonly tweaked text design attribute. For example, the Zune desktop music software appears to use it to shrink down the Segoe UI Light font in header text.
    http://csharponphone.blogspot.com
    Friday, July 02, 2010 10:47 PM
  • I'm also looking for this. In GDI, I beleive the method to use for this was SetTextCharacterExta. Did this concept not get ported? Not knowing the user input, I just want to apply an overall adjustment to the kerning, exactly like CSS letter-spacing (as opposed to glyph-by-glyph like Wolf suggested).
    Sunday, February 12, 2012 7:06 PM
  • This is a really old thread. And I don't even specifically work on WPF anymore so didn't really notice its more recent revival.

    WPF as of now has all this covered as attached properties of the Typography class.

    See http://msdn.microsoft.com/en-us/library/ms745109.aspx

    Monday, March 19, 2012 7:56 PM