GetCharacterRect из TexBlock Inline возвращает неправильную позицию

Обновить

Я понял, что проблема в том, GetPositionAtOffset(i)

Смещение - это не индекс символов, а символы. Я изменил эти строки:

double posTop = inline.ElementStart.GetPositionAtOffset(i).GetCharacterRect(LogicalDirection.Forward).Top;
double posLeft = inline.ElementStart.GetPositionAtOffset(i).GetCharacterRect(LogicalDirection.Forward).Left;

с:

double posTop = inline.ElementStart.GetPositionAtOffset(i).GetNextInsertionPosition(LogicalDirection.Forward).GetCharacterRect(LogicalDirection.Forward).Top;
double posLeft = inline.ElementStart.GetPositionAtOffset(i).GetNextInsertionPosition(LogicalDirection.Forward).GetCharacterRect(LogicalDirection.Forward).Left;

Это обеспечивает лучший результат, но все еще есть проблема с текстом внутри тегов, как вы можете видеть здесь:

Результат

Любая идея?


Вопрос

Я пытаюсь применить эффект контура к TextBlock и изменил пример, который нашел здесь: https://siderite.blogspot.com/2016/03/how-to-draw-outlined-text-in-wpf-and.html

Поскольку этот пример не поддерживает Inline, я добавил некоторый код для итерации коллекции Inline и, чтобы избежать проблем с переносом текста по центру текста, я решил нарисовать геометрию каждого символа отдельно.

Я получаю позиции персонажей, используя:

double posTop = inline.ElementStart.GetPositionAtOffset(i).GetCharacterRect(LogicalDirection.Forward).Top;
double posLeft = inline.ElementStart.GetPositionAtOffset(i).GetCharacterRect(LogicalDirection.Forward).Left;

Проблема в том, что позиции персонажей неправильные. Это швы, чтобы вернуть довольно случайные позиции.

Это часть кода, отвечающая за отрисовку геометрии:

    protected override void OnRender(DrawingContext drawingContext)
    {
        ensureTextBlock();
        base.OnRender(drawingContext);
        _textBlock.Foreground = Brushes.Transparent;

        PathGeometry _clipGeometry;

        var textPen = new Pen(StrokeBrush, StrokeThickness)
        {
            DashCap = PenLineCap.Round,
            EndLineCap = PenLineCap.Round,
            LineJoin = PenLineJoin.Round,
            StartLineCap = PenLineCap.Round
        };

        if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
        {
            textPen.Thickness = StrokeThickness * 2;
        }

        foreach (Inline inline in _textBlock.Inlines)
        {
            TextRange textRange = new TextRange(inline.ContentStart, inline.ContentEnd);
            char[] charArray = textRange.Text.ToCharArray();

            for (int i = 0; i < textRange.Text.Length - 1; i++)
            {
                var formattedText = new FormattedText(
                    charArray[i].ToString(),
                    CultureInfo.CurrentUICulture,
                    inline.FlowDirection,
                    new Typeface(inline.FontFamily, inline.FontStyle, inline.FontWeight, inline.FontStretch),
                    inline.FontSize, Brushes.Black
                );
                formattedText.SetTextDecorations(inline.TextDecorations);
                formattedText.LineHeight = _textBlock.LineHeight;

                double posTop = inline.ElementStart.GetPositionAtOffset(i).GetCharacterRect(LogicalDirection.Forward).Top;
                double posLeft = inline.ElementStart.GetPositionAtOffset(i).GetCharacterRect(LogicalDirection.Forward).Left;

                var _textGeometry = formattedText.BuildGeometry(new Point(posLeft, posTop));

                drawingContext.DrawGeometry(FillBrush, null, _textGeometry);

                if (StrokePosition == StrokePosition.Outside)
                {
                    var boundsGeo = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
                    _clipGeometry = Geometry.Combine(boundsGeo, _textGeometry, GeometryCombineMode.Exclude, null);
                    drawingContext.PushClip(_clipGeometry);
                }
                else if (StrokePosition == StrokePosition.Inside)
                {
                    drawingContext.PushClip(_textGeometry);
                }

                drawingContext.DrawGeometry(Brushes.Transparent, textPen, _textGeometry);

                if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
                {
                    drawingContext.Pop();
                }
            }
        }
    }

Это полный код:

using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace Test
{
    public enum StrokePosition
    {
        Center,
        Outside,
        Inside
    }

    public static class Adorning
    {
        private static void ensureAdorner(DependencyObject d, Action<StrokeAdorner> action)
        {
            var tb = d as TextBlock;
            if (tb == null) throw new Exception("StrokeAdorner only works on TextBlocks");
            EventHandler f = null;

            f = new EventHandler((o, e) =>
            {
                var adornerLayer = AdornerLayer.GetAdornerLayer(tb);
                if (adornerLayer == null) throw new Exception("AdornerLayer should not be empty");
                var adorners = adornerLayer.GetAdorners(tb);
                var adorner = adorners == null ? null : adorners.OfType<StrokeAdorner>().FirstOrDefault();
                if (adorner == null)
                {
                    adorner = new StrokeAdorner(tb);
                    adornerLayer.Add(adorner);
                }
                tb.LayoutUpdated -= f;
                action(adorner);
            });
            tb.LayoutUpdated += f;
        }

        public static Brush GetFillBrush(DependencyObject obj)
        {
            return (Brush)obj.GetValue(FillBrushProperty);
        }
        public static void SetFillBrush(DependencyObject obj, Brush value)
        {
            obj.SetValue(FillBrushProperty, value);
        }

        public static readonly DependencyProperty FillBrushProperty =
        DependencyProperty.RegisterAttached("FillBrush", typeof(Brush), typeof(Adorning), new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender, fillBrushChanged));

        private static void fillBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var fill = e.NewValue as Brush;
            ensureAdorner(d, a => a.FillBrush = fill);
        }

        public static Brush GetStrokeBrush(DependencyObject obj)
        {
            return (Brush)obj.GetValue(StrokeBrushProperty);
        }
        public static void SetStrokeBrush(DependencyObject obj, Brush value)
        {
            obj.SetValue(StrokeBrushProperty, value);
        }

        public static readonly DependencyProperty StrokeBrushProperty =
        DependencyProperty.RegisterAttached("StrokeBrush", typeof(Brush), typeof(Adorning), new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender, strokeBrushChanged));

        private static void strokeBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var stroke = e.NewValue as Brush;
            ensureAdorner(d, a => a.StrokeBrush = stroke);
        }

        public static double GetStrokeThickness(DependencyObject obj)
        {
            return (double)obj.GetValue(StrokeThicknessProperty);
        }

        public static void SetStrokeThickness(DependencyObject obj, double value)
        {
            obj.SetValue(StrokeThicknessProperty, value);
        }

        public static readonly DependencyProperty StrokeThicknessProperty =
        DependencyProperty.RegisterAttached("StrokeThickness", typeof(double), typeof(Adorning), new FrameworkPropertyMetadata(0.0, strokeThicknessChanged));

        private static void strokeThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ensureAdorner(d, a =>
            {
                if (DependencyProperty.UnsetValue.Equals(e.NewValue)) return;
                a.StrokeThickness = (ushort)(double)e.NewValue;
            });
        }

        public static StrokePosition GetStrokePosition(DependencyObject obj)
        {
            return (StrokePosition)obj.GetValue(StrokePositionProperty);
        }

        public static void SetStrokePosition(DependencyObject obj, StrokePosition value)
        {
            obj.SetValue(StrokePositionProperty, value);
        }

        public static readonly DependencyProperty StrokePositionProperty =
        DependencyProperty.RegisterAttached("StrokePosition", typeof(StrokePosition), typeof(Adorning), new FrameworkPropertyMetadata(StrokePosition.Outside, strokePositionChanged));

        private static void strokePositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ensureAdorner(d, a =>
            {
                if (DependencyProperty.UnsetValue.Equals(e.NewValue)) return;
                a.StrokePosition = (StrokePosition)e.NewValue;
            });
        }
    }

    public class StrokeAdorner : Adorner
    {
        private TextBlock _textBlock;
        private Brush _fill;
        private Brush _stroke;
        private ushort _strokeThickness;
        private StrokePosition _strokePosition;

        public Brush FillBrush
        {
            get
            {
                return _fill;
            }
            set
            {
                _fill = value;
                _textBlock.InvalidateVisual();
                InvalidateVisual();
            }
        }

        public Brush StrokeBrush
        {
            get
            {
                return _stroke;
            }
            set
            {
                _stroke = value;
                _textBlock.InvalidateVisual();
                InvalidateVisual();
            }
        }

        public ushort StrokeThickness
        {
            get
            {
                return _strokeThickness;
            }
            set
            {
                _strokeThickness = value;
                _textBlock.InvalidateVisual();
                InvalidateVisual();
            }
        }

        public StrokePosition StrokePosition
        {
            get
            {
                return _strokePosition;
            }
            set
            {
                _strokePosition = value;
                _textBlock.InvalidateVisual();
                InvalidateVisual();
            }
        }


        public StrokeAdorner(UIElement adornedElement) : base(adornedElement)
        {
            _textBlock = adornedElement as TextBlock;
            ensureTextBlock();
            foreach (var property in TypeDescriptor.GetProperties(_textBlock).OfType<PropertyDescriptor>())
            {
                var dp = DependencyPropertyDescriptor.FromProperty(property);
                if (dp == null) continue;
                var metadata = dp.Metadata as FrameworkPropertyMetadata;
                if (metadata == null) continue;
                if (!metadata.AffectsRender) continue;
                dp.AddValueChanged(_textBlock, (s, e) => this.InvalidateVisual());
            }
        }

        private void ensureTextBlock()
        {
            if (_textBlock == null) throw new Exception("This adorner works on TextBlocks only");
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            ensureTextBlock();
            base.OnRender(drawingContext);
            _textBlock.Foreground = Brushes.Transparent;

            PathGeometry _clipGeometry;

            var textPen = new Pen(StrokeBrush, StrokeThickness)
            {
                DashCap = PenLineCap.Round,
                EndLineCap = PenLineCap.Round,
                LineJoin = PenLineJoin.Round,
                StartLineCap = PenLineCap.Round
            };

            if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
            {
                textPen.Thickness = StrokeThickness * 2;
            }

            foreach (Inline inline in _textBlock.Inlines)
            {
                TextRange textRange = new TextRange(inline.ContentStart, inline.ContentEnd);
                char[] charArray = textRange.Text.ToCharArray();

                for (int i = 0; i < textRange.Text.Length - 1; i++)
                {
                    var formattedText = new FormattedText(
                        charArray[i].ToString(),
                        CultureInfo.CurrentUICulture,
                        inline.FlowDirection,
                        new Typeface(inline.FontFamily, inline.FontStyle, inline.FontWeight, inline.FontStretch),
                        inline.FontSize, Brushes.Black
                    );
                    formattedText.SetTextDecorations(inline.TextDecorations);
                    formattedText.LineHeight = _textBlock.LineHeight;

                    double posTop = inline.ElementStart.GetPositionAtOffset(i).GetCharacterRect(LogicalDirection.Forward).Top;
                    double posLeft = inline.ElementStart.GetPositionAtOffset(i).GetCharacterRect(LogicalDirection.Forward).Left;

                    var _textGeometry = formattedText.BuildGeometry(new Point(posLeft, posTop));

                    drawingContext.DrawGeometry(FillBrush, null, _textGeometry);

                    if (StrokePosition == StrokePosition.Outside)
                    {
                        var boundsGeo = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
                        _clipGeometry = Geometry.Combine(boundsGeo, _textGeometry, GeometryCombineMode.Exclude, null);
                        drawingContext.PushClip(_clipGeometry);
                    }
                    else if (StrokePosition == StrokePosition.Inside)
                    {
                        drawingContext.PushClip(_textGeometry);
                    }

                    drawingContext.DrawGeometry(Brushes.Transparent, textPen, _textGeometry);

                    if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
                    {
                        drawingContext.Pop();
                    }
                }
            }
        }
    }
}

Затем я использую его в xaml следующим образом:

<Grid>
    <TextBlock x:Name="LabelText"
               local:Adorning.FillBrush="Red"
               local:Adorning.StrokeBrush="White"
               local:Adorning.StrokeThickness="2"
               local:Adorning.StrokePosition="Outside"  
               VerticalAlignment="Bottom"
               TextWrapping="Wrap"
               TextAlignment="Center"
               Margin="0"
               FontWeight="Bold"
               FontSize="72" >
        This is an <Underline>outlined</Underline> text<LineBreak/>This is the second line
    </TextBlock>
</Grid>

Вот скриншот результата:

Результат

Все в моем коде выглядит правильно, но я не могу понять, в чем проблема.

0 ответов

Другие вопросы по тегам