wpfxamlitemscontrolformatted-text

How to position FormattedText with ItemsControl on Canvas (MVVM)


Using a MVVM pattern with ItemsControl for the XAML, I am trying to exactly position the topleft ink point of a FormattedText string at exactly the topleft corner of a specified rectangle. (The rectangle is given by the InkAnalyzer.Location.GetBounds()).

For reasons I can't figure out (or find on Google), the FormattedText string is always positioned down and to the right of the topleft corner of my rectangle, the distance seems proportional to the size of the font used for printing.

How can I achieve positioning of the FormattedText to hit the topleft corner of my rectangle?

Any help is greatly appreciated.

XAML

<ItemsControl 
                ItemsSource="{Binding Transcription}"
                >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas 
                            Background="Transparent"
                             Width="{x:Static h:Constants.widthCanvas}" 
                             Height="{x:Static h:Constants.heightCanvas}" 
                             />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>             
 </ItemsControl>

C#

 private ObservableCollection<StringViewModel> transcription = new ObservableCollection<StringViewModel>();
    public ObservableCollection<StringViewModel> Transcription
    {
        get
        {
            return transcription;
        }
    }


public class StringViewModel : FrameworkElement, INotifyPropertyChanged
{
    public StringViewModel(
        Point topleft, 
        string text, 
        double fontsize, 
        SolidColorBrush brush, 
        double linewidth, 
        double columnheight,
        Func<FormattedText,FormattedText> f)    
    {
        this.text = text;
        this.FontSize = fontsize * (72.0/96.0);     
        this.color = brush;
        this.origin = topleft;
        this.origin_X = topleft.X;
        this.origin_Y = topleft.Y;
        this.maxtextwidth = linewidth * (72.0/96.0);
        this.maxtextheight = columnheight ;
        this.f = f;
    }

    protected override void OnRender(DrawingContext dc)
    {

        FormattedText ft = new FormattedText(
            text, 
            CultureInfo.CurrentCulture, 
            FlowDirection.LeftToRight,
            new Typeface(new FontFamily("Segoe Script"), FontStyles.Italic, FontWeights.Normal, FontStretches.Normal), 
            FontSize, 
            color);

        ft.TextAlignment = TextAlignment.Left;

        ft.MaxTextWidth = maxtextwidth;
        ft.MaxTextHeight = maxtextheight;


        dc.DrawText(ft, origin);
    }

Solution

  • I've noticed that the Segoe family of fonts tends to have pretty generous spacing in the metrics. A quick test of Segoe Script at 16px (12pt at 72dpi) shows around 5px padding between the top of the taller glyphs and the bounding box.

    LineHeight Example

    In the example above, each chunk of text is positioned at the top-left of the pink border. You can see the extra vertical space in the first line, which I suspect reproduces what you're seeing in your own code.

    Setting the LineHeight to the font size eliminates the extra space, but clips some of the taller glyphs. My experimentation shows that a LineHeight of 1.25 * FontSize tends to be ideal for this particular font, as shown in the third line. It also maintains adequate spacing to display multi-line text.

    As an aside, you may want to embed the font in your application and reference it by its resource name, as opposed to simply specifying the font name and hoping your user has it installed (be sure to check the licensing details, though).