TextFormatterImpl.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987
  1. // ReSharper disable ForCanBeConvertedToForeach
  2. using System;
  3. using System.Buffers;
  4. using System.Collections.Generic;
  5. using System.Runtime.InteropServices;
  6. using Avalonia.Media.TextFormatting.Unicode;
  7. using Avalonia.Utilities;
  8. using static Avalonia.Media.TextFormatting.FormattingObjectPool;
  9. namespace Avalonia.Media.TextFormatting
  10. {
  11. internal sealed class TextFormatterImpl : TextFormatter
  12. {
  13. private static readonly char[] s_empty = { ' ' };
  14. private static readonly string s_defaultText = new string('a', TextRun.DefaultTextSourceLength);
  15. [ThreadStatic] private static BidiData? t_bidiData;
  16. [ThreadStatic] private static BidiAlgorithm? t_bidiAlgorithm;
  17. /// <inheritdoc cref="TextFormatter.FormatLine"/>
  18. public override TextLine? FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
  19. TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null)
  20. {
  21. TextLineBreak? nextLineBreak = null;
  22. var objectPool = FormattingObjectPool.Instance;
  23. var fontManager = FontManager.Current;
  24. // we've wrapped the previous line and need to continue wrapping: ignore the textSource and do that instead
  25. if (previousLineBreak is WrappingTextLineBreak wrappingTextLineBreak
  26. && wrappingTextLineBreak.AcquireRemainingRuns() is { } remainingRuns
  27. && paragraphProperties.TextWrapping != TextWrapping.NoWrap)
  28. {
  29. return PerformTextWrapping(remainingRuns, true, firstTextSourceIndex, paragraphWidth,
  30. paragraphProperties, previousLineBreak.FlowDirection, previousLineBreak, objectPool);
  31. }
  32. RentedList<TextRun>? fetchedRuns = null;
  33. RentedList<TextRun>? shapedTextRuns = null;
  34. try
  35. {
  36. fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool, out var textEndOfLine,
  37. out var textSourceLength);
  38. if (fetchedRuns.Count == 0)
  39. {
  40. return null;
  41. }
  42. shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager,
  43. out var resolvedFlowDirection);
  44. if (nextLineBreak == null && textEndOfLine != null)
  45. {
  46. nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
  47. }
  48. switch (paragraphProperties.TextWrapping)
  49. {
  50. case TextWrapping.NoWrap:
  51. {
  52. var textLine = new TextLineImpl(shapedTextRuns.ToArray(), firstTextSourceIndex,
  53. textSourceLength,
  54. paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
  55. textLine.FinalizeLine();
  56. return textLine;
  57. }
  58. case TextWrapping.WrapWithOverflow:
  59. case TextWrapping.Wrap:
  60. {
  61. return PerformTextWrapping(shapedTextRuns, false, firstTextSourceIndex, paragraphWidth,
  62. paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool);
  63. }
  64. default:
  65. throw new ArgumentOutOfRangeException(nameof(paragraphProperties.TextWrapping));
  66. }
  67. }
  68. finally
  69. {
  70. objectPool.TextRunLists.Return(ref shapedTextRuns);
  71. objectPool.TextRunLists.Return(ref fetchedRuns);
  72. }
  73. }
  74. /// <summary>
  75. /// Split a sequence of runs into two segments at specified length.
  76. /// </summary>
  77. /// <param name="textRuns">The text run's.</param>
  78. /// <param name="length">The length to split at.</param>
  79. /// <param name="objectPool">A pool used to get reusable formatting objects.</param>
  80. /// <returns>The split text runs.</returns>
  81. internal static SplitResult<RentedList<TextRun>> SplitTextRuns(IReadOnlyList<TextRun> textRuns, int length,
  82. FormattingObjectPool objectPool)
  83. {
  84. var first = objectPool.TextRunLists.Rent();
  85. var currentLength = 0;
  86. for (var i = 0; i < textRuns.Count; i++)
  87. {
  88. var currentRun = textRuns[i];
  89. var currentRunLength = currentRun.Length;
  90. if (currentLength + currentRunLength < length)
  91. {
  92. currentLength += currentRunLength;
  93. continue;
  94. }
  95. var firstCount = currentRunLength >= 1 ? i + 1 : i;
  96. if (firstCount > 1)
  97. {
  98. for (var j = 0; j < i; j++)
  99. {
  100. first.Add(textRuns[j]);
  101. }
  102. }
  103. var secondCount = textRuns.Count - firstCount;
  104. if (currentLength + currentRunLength == length)
  105. {
  106. var second = secondCount > 0 ? objectPool.TextRunLists.Rent() : null;
  107. if (second != null)
  108. {
  109. var offset = currentRunLength >= 1 ? 1 : 0;
  110. for (var j = 0; j < secondCount; j++)
  111. {
  112. second.Add(textRuns[i + j + offset]);
  113. }
  114. }
  115. first.Add(currentRun);
  116. return new SplitResult<RentedList<TextRun>>(first, second);
  117. }
  118. else
  119. {
  120. secondCount++;
  121. var second = objectPool.TextRunLists.Rent();
  122. if (currentRun is ShapedTextRun shapedTextCharacters)
  123. {
  124. var split = shapedTextCharacters.Split(length - currentLength);
  125. first.Add(split.First);
  126. second.Add(split.Second!);
  127. }
  128. for (var j = 1; j < secondCount; j++)
  129. {
  130. second.Add(textRuns[i + j]);
  131. }
  132. return new SplitResult<RentedList<TextRun>>(first, second);
  133. }
  134. }
  135. for (var i = 0; i < textRuns.Count; i++)
  136. {
  137. first.Add(textRuns[i]);
  138. }
  139. return new SplitResult<RentedList<TextRun>>(first, null);
  140. }
  141. /// <summary>
  142. /// Shape specified text runs with specified paragraph embedding.
  143. /// </summary>
  144. /// <param name="textRuns">The text runs to shape.</param>
  145. /// <param name="paragraphProperties">The default paragraph properties.</param>
  146. /// <param name="resolvedFlowDirection">The resolved flow direction.</param>
  147. /// <param name="objectPool">A pool used to get reusable formatting objects.</param>
  148. /// <param name="fontManager">The font manager to use.</param>
  149. /// <returns>
  150. /// A list of shaped text characters.
  151. /// </returns>
  152. private static RentedList<TextRun> ShapeTextRuns(IReadOnlyList<TextRun> textRuns,
  153. TextParagraphProperties paragraphProperties, FormattingObjectPool objectPool,
  154. FontManager fontManager, out FlowDirection resolvedFlowDirection)
  155. {
  156. var flowDirection = paragraphProperties.FlowDirection;
  157. var shapedRuns = objectPool.TextRunLists.Rent();
  158. if (textRuns.Count == 0)
  159. {
  160. resolvedFlowDirection = flowDirection;
  161. return shapedRuns;
  162. }
  163. var bidiData = t_bidiData ??= new();
  164. bidiData.Reset();
  165. bidiData.ParagraphEmbeddingLevel = (sbyte)flowDirection;
  166. for (var i = 0; i < textRuns.Count; ++i)
  167. {
  168. var textRun = textRuns[i];
  169. ReadOnlySpan<char> text;
  170. if (!textRun.Text.IsEmpty)
  171. text = textRun.Text.Span;
  172. else if (textRun.Length == TextRun.DefaultTextSourceLength)
  173. text = s_defaultText.AsSpan();
  174. else
  175. {
  176. text = new string('a', textRun.Length).AsSpan();
  177. }
  178. bidiData.Append(text);
  179. }
  180. var bidiAlgorithm = t_bidiAlgorithm ??= new();
  181. bidiAlgorithm.Process(bidiData);
  182. var resolvedEmbeddingLevel = bidiAlgorithm.ResolveEmbeddingLevel(bidiData.Classes);
  183. resolvedFlowDirection =
  184. (resolvedEmbeddingLevel & 1) == 0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft;
  185. var processedRuns = objectPool.TextRunLists.Rent();
  186. var groupedRuns = objectPool.UnshapedTextRunLists.Rent();
  187. try
  188. {
  189. CoalesceLevels(textRuns, bidiAlgorithm.ResolvedLevels.Span, fontManager, processedRuns);
  190. bidiData.Reset();
  191. bidiAlgorithm.Reset();
  192. var textShaper = TextShaper.Current;
  193. for (var index = 0; index < processedRuns.Count; index++)
  194. {
  195. var currentRun = processedRuns[index];
  196. switch (currentRun)
  197. {
  198. case UnshapedTextRun shapeableRun:
  199. {
  200. groupedRuns.Clear();
  201. groupedRuns.Add(shapeableRun);
  202. var text = shapeableRun.Text;
  203. var properties = shapeableRun.Properties;
  204. while (index + 1 < processedRuns.Count)
  205. {
  206. if (processedRuns[index + 1] is not UnshapedTextRun nextRun)
  207. {
  208. break;
  209. }
  210. if (shapeableRun.BidiLevel == nextRun.BidiLevel
  211. && TryJoinContiguousMemories(text, nextRun.Text, out var joinedText)
  212. && CanShapeTogether(properties, nextRun.Properties))
  213. {
  214. groupedRuns.Add(nextRun);
  215. index++;
  216. shapeableRun = nextRun;
  217. text = joinedText;
  218. continue;
  219. }
  220. break;
  221. }
  222. var shaperOptions = new TextShaperOptions(
  223. properties.CachedGlyphTypeface, properties.FontFeatures,
  224. properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo,
  225. paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
  226. ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns);
  227. break;
  228. }
  229. default:
  230. {
  231. shapedRuns.Add(currentRun);
  232. break;
  233. }
  234. }
  235. }
  236. }
  237. finally
  238. {
  239. objectPool.TextRunLists.Return(ref processedRuns);
  240. objectPool.UnshapedTextRunLists.Return(ref groupedRuns);
  241. }
  242. return shapedRuns;
  243. }
  244. /// <summary>
  245. /// Tries to join two potnetially contiguous memory regions.
  246. /// </summary>
  247. /// <param name="x">The first memory region.</param>
  248. /// <param name="y">The second memory region.</param>
  249. /// <param name="joinedMemory">On success, a memory region representing the union of the two regions.</param>
  250. /// <returns>true if the two regions were contigous; false otherwise.</returns>
  251. private static bool TryJoinContiguousMemories(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y,
  252. out ReadOnlyMemory<char> joinedMemory)
  253. {
  254. if (MemoryMarshal.TryGetString(x, out var xString, out var xStart, out var xLength))
  255. {
  256. if (MemoryMarshal.TryGetString(y, out var yString, out var yStart, out var yLength)
  257. && ReferenceEquals(xString, yString)
  258. && TryGetContiguousStart(xStart, xLength, yStart, yLength, out var joinedStart))
  259. {
  260. joinedMemory = xString.AsMemory(joinedStart, xLength + yLength);
  261. return true;
  262. }
  263. }
  264. else if (MemoryMarshal.TryGetArray(x, out var xSegment))
  265. {
  266. if (MemoryMarshal.TryGetArray(y, out var ySegment)
  267. && ReferenceEquals(xSegment.Array, ySegment.Array)
  268. && TryGetContiguousStart(xSegment.Offset, xSegment.Count, ySegment.Offset, ySegment.Count, out var joinedStart))
  269. {
  270. joinedMemory = xSegment.Array.AsMemory(joinedStart, xSegment.Count + ySegment.Count);
  271. return true;
  272. }
  273. }
  274. else if (MemoryMarshal.TryGetMemoryManager(x, out MemoryManager<char>? xManager, out xStart, out xLength))
  275. {
  276. if (MemoryMarshal.TryGetMemoryManager(y, out MemoryManager<char>? yManager, out var yStart, out var yLength)
  277. && ReferenceEquals(xManager, yManager)
  278. && TryGetContiguousStart(xStart, xLength, yStart, yLength, out var joinedStart))
  279. {
  280. joinedMemory = xManager.Memory.Slice(joinedStart, xLength + yLength);
  281. return true;
  282. }
  283. }
  284. joinedMemory = default;
  285. return false;
  286. static bool TryGetContiguousStart(int xStart, int xLength, int yStart, int yLength, out int joinedStart)
  287. {
  288. var xRange = (Start: xStart, Length: xLength);
  289. var yRange = (Start: yStart, Length: yLength);
  290. var (firstRange, secondRange) = xStart <= yStart ? (xRange, yRange) : (yRange, xRange);
  291. if (firstRange.Start + firstRange.Length == secondRange.Start)
  292. {
  293. joinedStart = firstRange.Start;
  294. return true;
  295. }
  296. joinedStart = default;
  297. return false;
  298. }
  299. }
  300. private static bool CanShapeTogether(TextRunProperties x, TextRunProperties y)
  301. => MathUtilities.AreClose(x.FontRenderingEmSize, y.FontRenderingEmSize)
  302. && x.Typeface == y.Typeface
  303. && x.BaselineAlignment == y.BaselineAlignment;
  304. private static void ShapeTogether(IReadOnlyList<UnshapedTextRun> textRuns, ReadOnlyMemory<char> text,
  305. TextShaperOptions options, TextShaper textShaper, RentedList<TextRun> results)
  306. {
  307. var shapedBuffer = textShaper.ShapeText(text, options);
  308. for (var i = 0; i < textRuns.Count; i++)
  309. {
  310. var currentRun = textRuns[i];
  311. var splitResult = shapedBuffer.Split(currentRun.Length);
  312. results.Add(new ShapedTextRun(splitResult.First, currentRun.Properties));
  313. shapedBuffer = splitResult.Second!;
  314. }
  315. }
  316. /// <summary>
  317. /// Coalesces ranges of the same bidi level to form <see cref="UnshapedTextRun"/>
  318. /// </summary>
  319. /// <param name="textCharacters">The text characters to form <see cref="UnshapedTextRun"/> from.</param>
  320. /// <param name="levels">The bidi levels.</param>
  321. /// <param name="fontManager">The font manager to use.</param>
  322. /// <param name="processedRuns">A list that will be filled with the processed runs.</param>
  323. /// <returns></returns>
  324. private static void CoalesceLevels(IReadOnlyList<TextRun> textCharacters, ReadOnlySpan<sbyte> levels,
  325. FontManager fontManager, RentedList<TextRun> processedRuns)
  326. {
  327. if (levels.Length == 0)
  328. {
  329. return;
  330. }
  331. var levelIndex = 0;
  332. var runLevel = levels[0];
  333. TextRunProperties? previousProperties = null;
  334. TextCharacters? currentRun = null;
  335. ReadOnlyMemory<char> runText = default;
  336. for (var i = 0; i < textCharacters.Count; i++)
  337. {
  338. var j = 0;
  339. currentRun = textCharacters[i] as TextCharacters;
  340. if (currentRun == null)
  341. {
  342. var drawableRun = textCharacters[i];
  343. processedRuns.Add(drawableRun);
  344. levelIndex += drawableRun.Length;
  345. continue;
  346. }
  347. runText = currentRun.Text;
  348. var runTextSpan = runText.Span;
  349. for (; j < runTextSpan.Length;)
  350. {
  351. Codepoint.ReadAt(runTextSpan, j, out var count);
  352. if (levelIndex + 1 == levels.Length)
  353. {
  354. break;
  355. }
  356. levelIndex++;
  357. j += count;
  358. if (j == runTextSpan.Length)
  359. {
  360. currentRun.GetShapeableCharacters(runText.Slice(0, j), runLevel, fontManager,
  361. ref previousProperties, processedRuns);
  362. runLevel = levels[levelIndex];
  363. continue;
  364. }
  365. if (levels[levelIndex] == runLevel)
  366. {
  367. continue;
  368. }
  369. // End of this run
  370. currentRun.GetShapeableCharacters(runText.Slice(0, j), runLevel, fontManager,
  371. ref previousProperties, processedRuns);
  372. runText = runText.Slice(j);
  373. runTextSpan = runText.Span;
  374. j = 0;
  375. // Move to next run
  376. runLevel = levels[levelIndex];
  377. }
  378. }
  379. if (currentRun is null || runText.IsEmpty)
  380. {
  381. return;
  382. }
  383. currentRun.GetShapeableCharacters(runText, runLevel, fontManager, ref previousProperties, processedRuns);
  384. }
  385. /// <summary>
  386. /// Fetches text runs.
  387. /// </summary>
  388. /// <param name="textSource">The text source.</param>
  389. /// <param name="firstTextSourceIndex">The first text source index.</param>
  390. /// <param name="objectPool">A pool used to get reusable formatting objects.</param>
  391. /// <param name="endOfLine">On return, the end of line, if any.</param>
  392. /// <param name="textSourceLength">On return, the processed text source length.</param>
  393. /// <returns>
  394. /// The formatted text runs.
  395. /// </returns>
  396. private static RentedList<TextRun> FetchTextRuns(ITextSource textSource, int firstTextSourceIndex,
  397. FormattingObjectPool objectPool, out TextEndOfLine? endOfLine, out int textSourceLength)
  398. {
  399. textSourceLength = 0;
  400. endOfLine = null;
  401. var textRuns = objectPool.TextRunLists.Rent();
  402. var textRunEnumerator = new TextRunEnumerator(textSource, firstTextSourceIndex);
  403. while (textRunEnumerator.MoveNext())
  404. {
  405. TextRun textRun = textRunEnumerator.Current!;
  406. if (textRun is TextEndOfLine textEndOfLine)
  407. {
  408. endOfLine = textEndOfLine;
  409. textSourceLength += textEndOfLine.Length;
  410. textRuns.Add(textRun);
  411. break;
  412. }
  413. switch (textRun)
  414. {
  415. case TextCharacters textCharacters:
  416. {
  417. if (TryGetLineBreak(textCharacters, out var runLineBreak))
  418. {
  419. var splitResult = new TextCharacters(textCharacters.Text.Slice(0, runLineBreak.PositionWrap),
  420. textCharacters.Properties);
  421. textRuns.Add(splitResult);
  422. textSourceLength += runLineBreak.PositionWrap;
  423. return textRuns;
  424. }
  425. textRuns.Add(textCharacters);
  426. break;
  427. }
  428. default:
  429. {
  430. textRuns.Add(textRun);
  431. break;
  432. }
  433. }
  434. textSourceLength += textRun.Length;
  435. }
  436. return textRuns;
  437. }
  438. private static bool TryGetLineBreak(TextRun textRun, out LineBreak lineBreak)
  439. {
  440. lineBreak = default;
  441. var text = textRun.Text;
  442. if (text.IsEmpty)
  443. {
  444. return false;
  445. }
  446. var lineBreakEnumerator = new LineBreakEnumerator(text.Span);
  447. while (lineBreakEnumerator.MoveNext(out lineBreak))
  448. {
  449. if (!lineBreak.Required)
  450. {
  451. continue;
  452. }
  453. return lineBreak.PositionWrap >= textRun.Length || true;
  454. }
  455. return false;
  456. }
  457. private static int MeasureLength(IReadOnlyList<TextRun> textRuns, double paragraphWidth)
  458. {
  459. var measuredLength = 0;
  460. var currentWidth = 0.0;
  461. for (var i = 0; i < textRuns.Count; ++i)
  462. {
  463. var currentRun = textRuns[i];
  464. switch (currentRun)
  465. {
  466. case ShapedTextRun shapedTextCharacters:
  467. {
  468. if (shapedTextCharacters.ShapedBuffer.Length > 0)
  469. {
  470. var runLength = 0;
  471. for (var j = 0; j < shapedTextCharacters.ShapedBuffer.Length; j++)
  472. {
  473. var currentInfo = shapedTextCharacters.ShapedBuffer[j];
  474. var clusterWidth = currentInfo.GlyphAdvance;
  475. GlyphInfo nextInfo = default;
  476. while (j + 1 < shapedTextCharacters.ShapedBuffer.Length)
  477. {
  478. nextInfo = shapedTextCharacters.ShapedBuffer[j + 1];
  479. if (currentInfo.GlyphCluster == nextInfo.GlyphCluster)
  480. {
  481. clusterWidth += nextInfo.GlyphAdvance;
  482. j++;
  483. continue;
  484. }
  485. break;
  486. }
  487. var clusterLength = Math.Max(0, nextInfo.GlyphCluster - currentInfo.GlyphCluster);
  488. if (clusterLength == 0)
  489. {
  490. clusterLength = currentRun.Length - runLength;
  491. }
  492. if (clusterLength == 0)
  493. {
  494. clusterLength = shapedTextCharacters.GlyphRun.Metrics.FirstCluster + currentRun.Length - currentInfo.GlyphCluster;
  495. }
  496. if (currentWidth + clusterWidth > paragraphWidth)
  497. {
  498. if (runLength == 0 && measuredLength == 0)
  499. {
  500. runLength = clusterLength;
  501. }
  502. return measuredLength + runLength;
  503. }
  504. currentWidth += clusterWidth;
  505. runLength += clusterLength;
  506. }
  507. measuredLength += runLength;
  508. }
  509. break;
  510. }
  511. case DrawableTextRun drawableTextRun:
  512. {
  513. if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth)
  514. {
  515. return measuredLength;
  516. }
  517. measuredLength += currentRun.Length;
  518. currentWidth += drawableTextRun.Size.Width;
  519. break;
  520. }
  521. default:
  522. {
  523. measuredLength += currentRun.Length;
  524. break;
  525. }
  526. }
  527. }
  528. return measuredLength;
  529. }
  530. /// <summary>
  531. /// Creates an empty text line.
  532. /// </summary>
  533. /// <returns>The empty text line.</returns>
  534. public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, double paragraphWidth,
  535. TextParagraphProperties paragraphProperties)
  536. {
  537. var flowDirection = paragraphProperties.FlowDirection;
  538. var properties = paragraphProperties.DefaultTextRunProperties;
  539. var glyphTypeface = properties.CachedGlyphTypeface;
  540. var glyph = glyphTypeface.GetGlyph(s_empty[0]);
  541. var glyphInfos = new[] { new GlyphInfo(glyph, firstTextSourceIndex, 0.0) };
  542. var shapedBuffer = new ShapedBuffer(s_empty.AsMemory(), glyphInfos, glyphTypeface, properties.FontRenderingEmSize,
  543. (sbyte)flowDirection);
  544. var textRuns = new TextRun[] { new ShapedTextRun(shapedBuffer, properties) };
  545. var line = new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection);
  546. line.FinalizeLine();
  547. return line;
  548. }
  549. /// <summary>
  550. /// Performs text wrapping returns a list of text lines.
  551. /// </summary>
  552. /// <param name="textRuns"></param>
  553. /// <param name="canReuseTextRunList">Whether <see cref="TextRun"/> can be reused to store the split runs.</param>
  554. /// <param name="firstTextSourceIndex">The first text source index.</param>
  555. /// <param name="paragraphWidth">The paragraph width.</param>
  556. /// <param name="paragraphProperties">The text paragraph properties.</param>
  557. /// <param name="resolvedFlowDirection"></param>
  558. /// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
  559. /// <param name="objectPool">A pool used to get reusable formatting objects.</param>
  560. /// <returns>The wrapped text line.</returns>
  561. private static TextLineImpl PerformTextWrapping(List<TextRun> textRuns, bool canReuseTextRunList,
  562. int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties,
  563. FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak, FormattingObjectPool objectPool)
  564. {
  565. if (textRuns.Count == 0)
  566. {
  567. return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties);
  568. }
  569. var measuredLength = MeasureLength(textRuns, paragraphWidth);
  570. if(measuredLength == 0)
  571. {
  572. if(paragraphProperties.TextWrapping == TextWrapping.NoWrap)
  573. {
  574. for (int i = 0; i < textRuns.Count; i++)
  575. {
  576. measuredLength += textRuns[i].Length;
  577. }
  578. }
  579. else
  580. {
  581. var firstRun = textRuns[0];
  582. if(firstRun is ShapedTextRun)
  583. {
  584. var graphemeEnumerator = new GraphemeEnumerator(firstRun.Text.Span);
  585. if(graphemeEnumerator.MoveNext(out var grapheme))
  586. {
  587. measuredLength = grapheme.Length;
  588. }
  589. else
  590. {
  591. measuredLength = 1;
  592. }
  593. }
  594. else
  595. {
  596. measuredLength = firstRun.Length;
  597. }
  598. }
  599. }
  600. var currentLength = 0;
  601. var lastWrapPosition = 0;
  602. var currentPosition = 0;
  603. for (var index = 0; index < textRuns.Count; index++)
  604. {
  605. var breakFound = false;
  606. var currentRun = textRuns[index];
  607. switch (currentRun)
  608. {
  609. case ShapedTextRun:
  610. {
  611. var lineBreaker = new LineBreakEnumerator(currentRun.Text.Span);
  612. while (lineBreaker.MoveNext(out var lineBreak))
  613. {
  614. if (lineBreak.Required &&
  615. currentLength + lineBreak.PositionMeasure <= measuredLength)
  616. {
  617. //Explicit break found
  618. breakFound = true;
  619. currentPosition = currentLength + lineBreak.PositionWrap;
  620. break;
  621. }
  622. if (currentLength + lineBreak.PositionMeasure > measuredLength)
  623. {
  624. if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
  625. {
  626. if (lastWrapPosition > 0)
  627. {
  628. currentPosition = lastWrapPosition;
  629. breakFound = true;
  630. break;
  631. }
  632. //Find next possible wrap position (overflow)
  633. if (index < textRuns.Count - 1)
  634. {
  635. if (lineBreak.PositionWrap != currentRun.Length)
  636. {
  637. //We already found the next possible wrap position.
  638. breakFound = true;
  639. currentPosition = currentLength + lineBreak.PositionWrap;
  640. break;
  641. }
  642. while (lineBreaker.MoveNext(out lineBreak))
  643. {
  644. currentPosition += lineBreak.PositionWrap;
  645. if (lineBreak.PositionWrap != currentRun.Length)
  646. {
  647. break;
  648. }
  649. index++;
  650. if (index >= textRuns.Count)
  651. {
  652. break;
  653. }
  654. currentRun = textRuns[index];
  655. lineBreaker = new LineBreakEnumerator(currentRun.Text.Span);
  656. }
  657. }
  658. else
  659. {
  660. currentPosition = currentLength + lineBreak.PositionWrap;
  661. }
  662. if (currentPosition == 0 && measuredLength > 0)
  663. {
  664. currentPosition = measuredLength;
  665. }
  666. breakFound = true;
  667. break;
  668. }
  669. //We overflowed so we use the last available wrap position.
  670. currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition;
  671. breakFound = true;
  672. break;
  673. }
  674. if (lineBreak.PositionMeasure != lineBreak.PositionWrap || lineBreak.PositionWrap != currentRun.Length)
  675. {
  676. lastWrapPosition = currentLength + lineBreak.PositionWrap;
  677. }
  678. }
  679. break;
  680. }
  681. }
  682. if (!breakFound)
  683. {
  684. currentLength += currentRun.Length;
  685. continue;
  686. }
  687. measuredLength = currentPosition;
  688. break;
  689. }
  690. var (preSplitRuns, postSplitRuns) = SplitTextRuns(textRuns, measuredLength, objectPool);
  691. try
  692. {
  693. TextLineBreak? textLineBreak;
  694. if (postSplitRuns?.Count > 0)
  695. {
  696. List<TextRun> remainingRuns;
  697. // reuse the list as much as possible:
  698. // if canReuseTextRunList == true it's coming from previous remaining runs
  699. if (canReuseTextRunList)
  700. {
  701. remainingRuns = textRuns;
  702. remainingRuns.Clear();
  703. }
  704. else
  705. {
  706. remainingRuns = new List<TextRun>();
  707. }
  708. for (var i = 0; i < postSplitRuns.Count; ++i)
  709. {
  710. remainingRuns.Add(postSplitRuns[i]);
  711. }
  712. textLineBreak = new WrappingTextLineBreak(null, resolvedFlowDirection, remainingRuns);
  713. }
  714. else if (currentLineBreak?.TextEndOfLine is { } textEndOfLine)
  715. {
  716. textLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
  717. }
  718. else
  719. {
  720. textLineBreak = null;
  721. }
  722. var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength,
  723. paragraphWidth, paragraphProperties, resolvedFlowDirection,
  724. textLineBreak);
  725. textLine.FinalizeLine();
  726. return textLine;
  727. }
  728. finally
  729. {
  730. objectPool.TextRunLists.Return(ref preSplitRuns);
  731. objectPool.TextRunLists.Return(ref postSplitRuns);
  732. }
  733. }
  734. private struct TextRunEnumerator
  735. {
  736. private readonly ITextSource _textSource;
  737. private int _pos;
  738. public TextRunEnumerator(ITextSource textSource, int firstTextSourceIndex)
  739. {
  740. _textSource = textSource;
  741. _pos = firstTextSourceIndex;
  742. Current = null;
  743. }
  744. // ReSharper disable once MemberHidesStaticFromOuterClass
  745. public TextRun? Current { get; private set; }
  746. public bool MoveNext()
  747. {
  748. Current = _textSource.GetTextRun(_pos);
  749. if (Current is null)
  750. {
  751. return false;
  752. }
  753. if (Current.Length == 0)
  754. {
  755. return false;
  756. }
  757. _pos += Current.Length;
  758. return true;
  759. }
  760. }
  761. /// <summary>
  762. /// Creates a shaped symbol.
  763. /// </summary>
  764. /// <param name="textRun">The symbol run to shape.</param>
  765. /// <param name="flowDirection">The flow direction.</param>
  766. /// <returns>
  767. /// The shaped symbol.
  768. /// </returns>
  769. internal static ShapedTextRun CreateSymbol(TextRun textRun, FlowDirection flowDirection)
  770. {
  771. var textShaper = TextShaper.Current;
  772. var glyphTypeface = textRun.Properties!.CachedGlyphTypeface;
  773. var fontRenderingEmSize = textRun.Properties.FontRenderingEmSize;
  774. var cultureInfo = textRun.Properties.CultureInfo;
  775. var shaperOptions = new TextShaperOptions(glyphTypeface, textRun.Properties.FontFeatures,
  776. fontRenderingEmSize, (sbyte)flowDirection, cultureInfo);
  777. var shapedBuffer = textShaper.ShapeText(textRun.Text, shaperOptions);
  778. return new ShapedTextRun(shapedBuffer, textRun.Properties);
  779. }
  780. }
  781. }