| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- using System;
- using Avalonia.Media.TextFormatting.Unicode;
- namespace Avalonia.Media.TextFormatting
- {
- /// <summary>
- /// A text run that holds shaped characters.
- /// </summary>
- public sealed class ShapedTextRun : DrawableTextRun, IDisposable
- {
- private GlyphRun? _glyphRun;
- public ShapedTextRun(ShapedBuffer shapedBuffer, TextRunProperties properties)
- {
- ShapedBuffer = shapedBuffer;
- Properties = properties;
- TextMetrics = new TextMetrics(properties.CachedGlyphTypeface, properties.FontRenderingEmSize);
- }
- public bool IsReversed { get; private set; }
- public sbyte BidiLevel => ShapedBuffer.BidiLevel;
- public ShapedBuffer ShapedBuffer { get; }
- /// <inheritdoc/>
- public override ReadOnlyMemory<char> Text
- => ShapedBuffer.Text;
- /// <inheritdoc/>
- public override TextRunProperties Properties { get; }
- /// <inheritdoc/>
- public override int Length
- => ShapedBuffer.Text.Length;
- public TextMetrics TextMetrics { get; }
- public override double Baseline => -TextMetrics.Ascent;
- public override Size Size => GlyphRun.Bounds.Size;
- public GlyphRun GlyphRun => _glyphRun ??= CreateGlyphRun();
- /// <inheritdoc/>
- public override void Draw(DrawingContext drawingContext, Point origin)
- {
- using (drawingContext.PushTransform(Matrix.CreateTranslation(origin)))
- {
- if (GlyphRun.GlyphInfos.Count == 0)
- {
- return;
- }
- if (Properties.Typeface == default)
- {
- return;
- }
- if (Properties.ForegroundBrush == null)
- {
- return;
- }
- if (Properties.BackgroundBrush != null)
- {
- drawingContext.DrawRectangle(Properties.BackgroundBrush, null, GlyphRun.Bounds.Translate(new Vector(0, -Baseline)));
- }
- drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun);
- if (Properties.TextDecorations == null)
- {
- return;
- }
- foreach (var textDecoration in Properties.TextDecorations)
- {
- textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush);
- }
- }
- }
- internal void Reverse()
- {
- _glyphRun = null;
- ShapedBuffer.Reverse();
- IsReversed = !IsReversed;
- }
- /// <summary>
- /// Measures the number of characters that fit into available width.
- /// </summary>
- /// <param name="availableWidth">The available width.</param>
- /// <param name="length">The count of fitting characters.</param>
- /// <returns>
- /// <c>true</c> if characters fit into the available width; otherwise, <c>false</c>.
- /// </returns>
- public bool TryMeasureCharacters(double availableWidth, out int length)
- {
- length = 0;
- var currentWidth = 0.0;
- var charactersSpan = GlyphRun.Characters.Span;
- for (var i = 0; i < ShapedBuffer.Length; i++)
- {
- var advance = ShapedBuffer[i].GlyphAdvance;
- var currentCluster = ShapedBuffer[i].GlyphCluster;
- if (currentWidth + advance > availableWidth)
- {
- break;
- }
- if(i + 1 < ShapedBuffer.Length)
- {
- var nextCluster = ShapedBuffer[i + 1].GlyphCluster;
- var count = nextCluster - currentCluster;
- length += count;
- }
- else
- {
- Codepoint.ReadAt(charactersSpan, length, out var count);
- length += count;
- }
-
- currentWidth += advance;
- }
- return length > 0;
- }
- internal bool TryMeasureCharactersBackwards(double availableWidth, out int length, out double width)
- {
- length = 0;
- width = 0;
- var charactersSpan = GlyphRun.Characters.Span;
- for (var i = ShapedBuffer.Length - 1; i >= 0; i--)
- {
- var advance = ShapedBuffer[i].GlyphAdvance;
- if (width + advance > availableWidth)
- {
- break;
- }
- Codepoint.ReadAt(charactersSpan, length, out var count);
- length += count;
- width += advance;
- }
- return length > 0;
- }
- internal SplitResult<ShapedTextRun> Split(int length)
- {
- var isReversed = IsReversed;
- if (isReversed)
- {
- Reverse();
- length = Length - length;
- }
- #if DEBUG
- if (length == 0)
- {
- throw new ArgumentOutOfRangeException(nameof(length), "length must be greater than zero.");
- }
- #endif
- var splitBuffer = ShapedBuffer.Split(length);
- var first = new ShapedTextRun(splitBuffer.First, Properties);
- #if DEBUG
- if (first.Length != length)
- {
- throw new InvalidOperationException("Split length mismatch.");
- }
- #endif
- var second = new ShapedTextRun(splitBuffer.Second!, Properties);
- if (isReversed)
- {
- return new SplitResult<ShapedTextRun>(second, first);
- }
- return new SplitResult<ShapedTextRun>(first, second);
- }
- internal GlyphRun CreateGlyphRun()
- {
- return new GlyphRun(
- ShapedBuffer.GlyphTypeface,
- ShapedBuffer.FontRenderingEmSize,
- Text,
- ShapedBuffer,
- biDiLevel: BidiLevel,
- baselineOrigin: new Point());
- }
- public void Dispose()
- {
- _glyphRun?.Dispose();
- ShapedBuffer.Dispose();
- }
- }
- }
|