已经有一个关于文本阴影的问题并且已经得到了 回答。
简而言之,解决方案的本质是FormattedText从内容中创建一个隐藏的TextBlock,然后从中创建一个几何体FormattedText,使用显示Path,然后使用动画在其上绘制。
当文本占一行时一切正常,但是当有几行时,结果就不是我们想要的了。
我不会重复 XAML,因为 它与上述答案中的完全相同。我的代码也没有太大区别,但为了清楚起见,我将给出它。
private List<Rect> _RectsForFill; // прямоугольники с каждым символом
private double _LengthFillText; // общая ширина рисованного текста
//анимация
Storyboard _Storyboard;
DoubleAnimation _FromAnimation;
DoubleAnimation _ToAnimation;
private void CreateFillText()
{
//получаем текстовое содержимое
TextBlock tb = this.textBlockHidden;
var text = tb.Text;
//создаем экземпляр форматированного текста
FormattedText formattedText = new FormattedText(
text,
CultureInfo.GetCultureInfo("en-US"),
FlowDirection.LeftToRight,
new Typeface(
tb.FontFamily,
tb.FontStyle,
tb.FontWeight,
tb.FontStretch),
tb.FontSize,
Brushes.Black // конкретная кисть нам не важна, мы используем только геометрию
);
//берем переносы строк у эталонного текстблока
formattedText.MaxTextWidth = this.textBlockHidden.Width;
formattedText.MaxTextHeight = this.textBlockHidden.Height;
// стащили геометрию у текста...
var geo = formattedText.BuildGeometry(new Point());
// ...и отдали её Path'у
Target.Data = geo;
//вычислим прямоугольники для заполнения
GetRectsForFill(text, formattedText);
}
private void GetRectsForFill(string text, FormattedText formattedText)
{
var bb = formattedText.BuildHighlightGeometry(new Point());
_LengthFillText = bb.Bounds.Width; // общая ширина
//заполняем коллекцию побуквенных боксов
_RectsForFill = Enumerable.Range(0, text.Length)
.Select(k => formattedText.BuildHighlightGeometry(new Point(), k, 1)
.Bounds)
.ToList();
//ссылки на анимацию для дальнейшей работы с ней
_Storyboard = (Storyboard)Target.Resources["AnimationStoryboard"];
_FromAnimation = (DoubleAnimation)_Storyboard.Children[0];
_ToAnimation = (DoubleAnimation)_Storyboard.Children[1];
}
这是一种用黑色填充渲染文本的方法
/// <summary>
/// Закрашивание рисованного текста
/// </summary>
/// <param name="startPos">начальная позиция слова</param>
/// <param name="count">число закрашиваемых букв в слове</param>
public void FillTextPath(int startPos, int count)
{
if (count == 0) throw new ArgumentException(nameof(count));
//вычисляем индекс необходимого прямоугольника
int index = startPos + count;
if (index >= _RectsForFill.Count) index = _RectsForFill.Count - 1;
//необходимый прямоугольник
Rect box = _RectsForFill[index];
//закрашиваем
_FromAnimation.From = box.Left / _LengthFillText;
_FromAnimation.To = box.Right / _LengthFillText;
_ToAnimation.From = box.Left / _LengthFillText;
_ToAnimation.To = box.Right / _LengthFillText;
_Storyboard.Begin();
}

你走对了路,拆分成单独
Path的 's 是个好主意。让我们执行到底。
对于初学者来说,回放功能已经相当复杂,所以我们将把它移到一个单独的
UserControl. 然后,让每个控件负责一行。Clip为了不按几何搜索碎片,我们简单地用'a.切断这条线。在输入端,UserControl我们将提交几何文本的解析结果(通过函数Create)。代码隐藏中会有一个动画:
为什么我们需要这样复杂的
Clip和totalExtent?不幸的是,我没有找到一种方法来只咬出所需的几何部分。因此,我们将整个几何图形作为输入,但我们只想显示当前行。为此,我们计算出一个对应于几何图形所需部分(当前行)的矩形,并用Clip'a. 但是我们的比率计算(b.Left / extent等)需要占'a总宽度的百分比Path,而不是当前行的宽度!(回想一下,我们Path获取整条线的几何形状,也包括其余线。)因此,我们还必须传递总宽度。现在是主要代码。随着部分功能的分离,它变得更简单了。在其中,我们不能放一个 fixed
Path,因为我们事先不知道行数。因此,我们将动态添加控件。主窗口看起来很简单:
和代码隐藏:
一切!
结果: