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>
Вот скриншот результата:
Все в моем коде выглядит правильно, но я не могу понять, в чем проблема.