GlyphRun.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using Avalonia.Platform;
  6. using Avalonia.Utility;
  7. namespace Avalonia.Media
  8. {
  9. /// <summary>
  10. /// Represents a sequence of glyphs from a single face of a single font at a single size, and with a single rendering style.
  11. /// </summary>
  12. public sealed class GlyphRun : IDisposable
  13. {
  14. private static readonly IComparer<ushort> s_ascendingComparer = Comparer<ushort>.Default;
  15. private static readonly IComparer<ushort> s_descendingComparer = new ReverseComparer<ushort>();
  16. private IGlyphRunImpl _glyphRunImpl;
  17. private GlyphTypeface _glyphTypeface;
  18. private double _fontRenderingEmSize;
  19. private Rect? _bounds;
  20. private int _biDiLevel;
  21. private ReadOnlySlice<ushort> _glyphIndices;
  22. private ReadOnlySlice<double> _glyphAdvances;
  23. private ReadOnlySlice<Vector> _glyphOffsets;
  24. private ReadOnlySlice<ushort> _glyphClusters;
  25. private ReadOnlySlice<char> _characters;
  26. /// <summary>
  27. /// Initializes a new instance of the <see cref="GlyphRun"/> class.
  28. /// </summary>
  29. public GlyphRun()
  30. {
  31. }
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
  34. /// </summary>
  35. /// <param name="glyphTypeface">The glyph typeface.</param>
  36. /// <param name="fontRenderingEmSize">The rendering em size.</param>
  37. /// <param name="glyphIndices">The glyph indices.</param>
  38. /// <param name="glyphAdvances">The glyph advances.</param>
  39. /// <param name="glyphOffsets">The glyph offsets.</param>
  40. /// <param name="characters">The characters.</param>
  41. /// <param name="glyphClusters">The glyph clusters.</param>
  42. /// <param name="biDiLevel">The bidi level.</param>
  43. /// <param name="bounds">The bound.</param>
  44. public GlyphRun(
  45. GlyphTypeface glyphTypeface,
  46. double fontRenderingEmSize,
  47. ReadOnlySlice<ushort> glyphIndices,
  48. ReadOnlySlice<double> glyphAdvances = default,
  49. ReadOnlySlice<Vector> glyphOffsets = default,
  50. ReadOnlySlice<char> characters = default,
  51. ReadOnlySlice<ushort> glyphClusters = default,
  52. int biDiLevel = 0,
  53. Rect? bounds = null)
  54. {
  55. GlyphTypeface = glyphTypeface;
  56. FontRenderingEmSize = fontRenderingEmSize;
  57. GlyphIndices = glyphIndices;
  58. GlyphAdvances = glyphAdvances;
  59. GlyphOffsets = glyphOffsets;
  60. Characters = characters;
  61. GlyphClusters = glyphClusters;
  62. BiDiLevel = biDiLevel;
  63. Initialize(bounds);
  64. }
  65. /// <summary>
  66. /// Gets or sets the <see cref="Media.GlyphTypeface"/> for the <see cref="GlyphRun"/>.
  67. /// </summary>
  68. public GlyphTypeface GlyphTypeface
  69. {
  70. get => _glyphTypeface;
  71. set => Set(ref _glyphTypeface, value);
  72. }
  73. /// <summary>
  74. /// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>.
  75. /// </summary>
  76. public double FontRenderingEmSize
  77. {
  78. get => _fontRenderingEmSize;
  79. set => Set(ref _fontRenderingEmSize, value);
  80. }
  81. /// <summary>
  82. /// Gets or sets an array of <see cref="ushort"/> values that represent the glyph indices in the rendering physical font.
  83. /// </summary>
  84. public ReadOnlySlice<ushort> GlyphIndices
  85. {
  86. get => _glyphIndices;
  87. set => Set(ref _glyphIndices, value);
  88. }
  89. /// <summary>
  90. /// Gets or sets an array of <see cref="double"/> values that represent the advances corresponding to the glyph indices.
  91. /// </summary>
  92. public ReadOnlySlice<double> GlyphAdvances
  93. {
  94. get => _glyphAdvances;
  95. set => Set(ref _glyphAdvances, value);
  96. }
  97. /// <summary>
  98. /// Gets or sets an array of <see cref="Vector"/> values representing the offsets of the glyphs in the <see cref="GlyphRun"/>.
  99. /// </summary>
  100. public ReadOnlySlice<Vector> GlyphOffsets
  101. {
  102. get => _glyphOffsets;
  103. set => Set(ref _glyphOffsets, value);
  104. }
  105. /// <summary>
  106. /// Gets or sets the list of UTF16 code points that represent the Unicode content of the <see cref="GlyphRun"/>.
  107. /// </summary>
  108. public ReadOnlySlice<char> Characters
  109. {
  110. get => _characters;
  111. set => Set(ref _characters, value);
  112. }
  113. /// <summary>
  114. /// Gets or sets a list of <see cref="int"/> values representing a mapping from character index to glyph index.
  115. /// </summary>
  116. public ReadOnlySlice<ushort> GlyphClusters
  117. {
  118. get => _glyphClusters;
  119. set => Set(ref _glyphClusters, value);
  120. }
  121. /// <summary>
  122. /// Gets or sets the bidirectional nesting level of the <see cref="GlyphRun"/>.
  123. /// </summary>
  124. public int BiDiLevel
  125. {
  126. get => _biDiLevel;
  127. set => Set(ref _biDiLevel, value);
  128. }
  129. /// <summary>
  130. /// Gets the scale of the current <see cref="Media.GlyphTypeface"/>
  131. /// </summary>
  132. internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight;
  133. /// <summary>
  134. /// Returns <c>true</c> if the text direction is left-to-right. Otherwise, returns <c>false</c>.
  135. /// </summary>
  136. public bool IsLeftToRight => ((BiDiLevel & 1) == 0);
  137. /// <summary>
  138. /// Gets or sets the conservative bounding box of the <see cref="GlyphRun"/>.
  139. /// </summary>
  140. public Rect Bounds
  141. {
  142. get
  143. {
  144. if (_bounds == null)
  145. {
  146. _bounds = CalculateBounds();
  147. }
  148. return _bounds.Value;
  149. }
  150. }
  151. /// <summary>
  152. /// The platform implementation of the <see cref="GlyphRun"/>.
  153. /// </summary>
  154. public IGlyphRunImpl GlyphRunImpl
  155. {
  156. get
  157. {
  158. if (_glyphRunImpl == null)
  159. {
  160. Initialize(null);
  161. }
  162. return _glyphRunImpl;
  163. }
  164. }
  165. /// <summary>
  166. /// Retrieves the offset from the leading edge of the <see cref="GlyphRun"/>
  167. /// to the leading or trailing edge of a caret stop containing the specified character hit.
  168. /// </summary>
  169. /// <param name="characterHit">The <see cref="CharacterHit"/> to use for computing the offset.</param>
  170. /// <returns>
  171. /// A <see cref="double"/> that represents the offset from the leading edge of the <see cref="GlyphRun"/>
  172. /// to the leading or trailing edge of a caret stop containing the character hit.
  173. /// </returns>
  174. public double GetDistanceFromCharacterHit(CharacterHit characterHit)
  175. {
  176. var distance = 0.0;
  177. if (characterHit.FirstCharacterIndex + characterHit.TrailingLength > Characters.End)
  178. {
  179. return Bounds.Width;
  180. }
  181. var glyphIndex = FindGlyphIndex(characterHit.FirstCharacterIndex);
  182. var currentCluster = _glyphClusters[glyphIndex];
  183. if (characterHit.TrailingLength > 0)
  184. {
  185. while (glyphIndex < _glyphClusters.Length && _glyphClusters[glyphIndex] == currentCluster)
  186. {
  187. glyphIndex++;
  188. }
  189. }
  190. for (var i = 0; i < glyphIndex; i++)
  191. {
  192. if (GlyphAdvances.IsEmpty)
  193. {
  194. var glyph = GlyphIndices[i];
  195. distance += GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
  196. }
  197. else
  198. {
  199. distance += GlyphAdvances[i];
  200. }
  201. }
  202. return distance;
  203. }
  204. /// <summary>
  205. /// Retrieves the <see cref="CharacterHit"/> value that represents the character hit of the caret of the <see cref="GlyphRun"/>.
  206. /// </summary>
  207. /// <param name="distance">Offset to use for computing the caret character hit.</param>
  208. /// <param name="isInside">Determines whether the character hit is inside the <see cref="GlyphRun"/>.</param>
  209. /// <returns>
  210. /// A <see cref="CharacterHit"/> value that represents the character hit that is closest to the distance value.
  211. /// The out parameter <c>isInside</c> returns <c>true</c> if the character hit is inside the <see cref="GlyphRun"/>; otherwise, <c>false</c>.
  212. /// </returns>
  213. public CharacterHit GetCharacterHitFromDistance(double distance, out bool isInside)
  214. {
  215. // Before
  216. if (distance < 0)
  217. {
  218. isInside = false;
  219. var firstCharacterHit = FindNearestCharacterHit(_glyphClusters[0], out _);
  220. return IsLeftToRight ? new CharacterHit(firstCharacterHit.FirstCharacterIndex) : firstCharacterHit;
  221. }
  222. //After
  223. if (distance > Bounds.Size.Width)
  224. {
  225. isInside = false;
  226. var lastCharacterHit = FindNearestCharacterHit(_glyphClusters[_glyphClusters.Length - 1], out _);
  227. return IsLeftToRight ? lastCharacterHit : new CharacterHit(lastCharacterHit.FirstCharacterIndex);
  228. }
  229. //Within
  230. var currentX = 0.0;
  231. var index = 0;
  232. for (; index < GlyphIndices.Length; index++)
  233. {
  234. double advance;
  235. if (GlyphAdvances.IsEmpty)
  236. {
  237. var glyph = GlyphIndices[index];
  238. advance = GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
  239. }
  240. else
  241. {
  242. advance = GlyphAdvances[index];
  243. }
  244. if (currentX + advance >= distance)
  245. {
  246. break;
  247. }
  248. currentX += advance;
  249. }
  250. var characterHit = FindNearestCharacterHit(GlyphClusters[index], out var width);
  251. var offset = GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex));
  252. isInside = true;
  253. var isTrailing = distance > offset + width / 2;
  254. return isTrailing ? characterHit : new CharacterHit(characterHit.FirstCharacterIndex);
  255. }
  256. /// <summary>
  257. /// Retrieves the next valid caret character hit in the logical direction in the <see cref="GlyphRun"/>.
  258. /// </summary>
  259. /// <param name="characterHit">The <see cref="CharacterHit"/> to use for computing the next hit value.</param>
  260. /// <returns>
  261. /// A <see cref="CharacterHit"/> that represents the next valid caret character hit in the logical direction.
  262. /// If the return value is equal to <c>characterHit</c>, no further navigation is possible in the <see cref="GlyphRun"/>.
  263. /// </returns>
  264. public CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
  265. {
  266. if (characterHit.TrailingLength == 0)
  267. {
  268. return FindNearestCharacterHit(characterHit.FirstCharacterIndex, out _);
  269. }
  270. var nextCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
  271. return new CharacterHit(nextCharacterHit.FirstCharacterIndex);
  272. }
  273. /// <summary>
  274. /// Retrieves the previous valid caret character hit in the logical direction in the <see cref="GlyphRun"/>.
  275. /// </summary>
  276. /// <param name="characterHit">The <see cref="CharacterHit"/> to use for computing the previous hit value.</param>
  277. /// <returns>
  278. /// A cref="CharacterHit"/> that represents the previous valid caret character hit in the logical direction.
  279. /// If the return value is equal to <c>characterHit</c>, no further navigation is possible in the <see cref="GlyphRun"/>.
  280. /// </returns>
  281. public CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
  282. {
  283. if (characterHit.TrailingLength != 0)
  284. {
  285. return new CharacterHit(characterHit.FirstCharacterIndex);
  286. }
  287. return characterHit.FirstCharacterIndex == Characters.Start ?
  288. new CharacterHit(Characters.Start) :
  289. FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
  290. }
  291. private class ReverseComparer<T> : IComparer<T>
  292. {
  293. public int Compare(T x, T y)
  294. {
  295. return Comparer<T>.Default.Compare(y, x);
  296. }
  297. }
  298. /// <summary>
  299. /// Finds a glyph index for given character index.
  300. /// </summary>
  301. /// <param name="characterIndex">The character index.</param>
  302. /// <returns>
  303. /// The glyph index.
  304. /// </returns>
  305. public int FindGlyphIndex(int characterIndex)
  306. {
  307. if (IsLeftToRight)
  308. {
  309. if (characterIndex < _glyphClusters[0])
  310. {
  311. return 0;
  312. }
  313. if (characterIndex > _glyphClusters[_glyphClusters.Length - 1])
  314. {
  315. return _glyphClusters.End;
  316. }
  317. }
  318. else
  319. {
  320. if (characterIndex < _glyphClusters[_glyphClusters.Length - 1])
  321. {
  322. return _glyphClusters.End;
  323. }
  324. if (characterIndex > _glyphClusters[0])
  325. {
  326. return 0;
  327. }
  328. }
  329. var comparer = IsLeftToRight ? s_ascendingComparer : s_descendingComparer;
  330. var clusters = _glyphClusters.Buffer.Span;
  331. // Find the start of the cluster at the character index.
  332. var start = clusters.BinarySearch((ushort)characterIndex, comparer);
  333. // No cluster found.
  334. if (start < 0)
  335. {
  336. while (characterIndex > 0 && start < 0)
  337. {
  338. characterIndex--;
  339. start = clusters.BinarySearch((ushort)characterIndex, comparer);
  340. }
  341. if (start < 0)
  342. {
  343. return -1;
  344. }
  345. }
  346. while (start > 0 && clusters[start - 1] == clusters[start])
  347. {
  348. start--;
  349. }
  350. return start;
  351. }
  352. /// <summary>
  353. /// Finds the nearest <see cref="CharacterHit"/> at given index.
  354. /// </summary>
  355. /// <param name="index">The index.</param>
  356. /// <param name="width">The width of found cluster.</param>
  357. /// <returns>
  358. /// The nearest <see cref="CharacterHit"/>.
  359. /// </returns>
  360. public CharacterHit FindNearestCharacterHit(int index, out double width)
  361. {
  362. width = 0.0;
  363. var start = FindGlyphIndex(index);
  364. var currentCluster = _glyphClusters[start];
  365. var trailingLength = 0;
  366. while (start < _glyphClusters.Length && _glyphClusters[start] == currentCluster)
  367. {
  368. if (GlyphAdvances.IsEmpty)
  369. {
  370. var glyph = GlyphIndices[start];
  371. width += GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
  372. }
  373. else
  374. {
  375. width += GlyphAdvances[start];
  376. }
  377. trailingLength++;
  378. start++;
  379. }
  380. if (start == _glyphClusters.Length &&
  381. currentCluster + trailingLength != Characters.Start + Characters.Length)
  382. {
  383. trailingLength = Characters.Start + Characters.Length - currentCluster;
  384. }
  385. return new CharacterHit(currentCluster, trailingLength);
  386. }
  387. /// <summary>
  388. /// Calculates the bounds of the <see cref="GlyphRun"/>.
  389. /// </summary>
  390. /// <returns>
  391. /// The calculated bounds.
  392. /// </returns>
  393. private Rect CalculateBounds()
  394. {
  395. var scale = FontRenderingEmSize / GlyphTypeface.DesignEmHeight;
  396. var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * scale;
  397. var width = 0.0;
  398. if (GlyphAdvances.IsEmpty)
  399. {
  400. foreach (var glyph in GlyphIndices)
  401. {
  402. width += GlyphTypeface.GetGlyphAdvance(glyph) * Scale;
  403. }
  404. }
  405. else
  406. {
  407. foreach (var advance in GlyphAdvances)
  408. {
  409. width += advance;
  410. }
  411. }
  412. return new Rect(0, 0, width, height);
  413. }
  414. private void Set<T>(ref T field, T value)
  415. {
  416. if (_glyphRunImpl != null)
  417. {
  418. throw new InvalidOperationException("GlyphRun can't be changed after it has been initialized.'");
  419. }
  420. field = value;
  421. }
  422. /// <summary>
  423. /// Initializes the <see cref="GlyphRun"/>.
  424. /// </summary>
  425. /// <param name="bounds">Optional pre computed bounds.</param>
  426. private void Initialize(Rect? bounds)
  427. {
  428. if (GlyphIndices.Length == 0)
  429. {
  430. throw new InvalidOperationException();
  431. }
  432. var glyphCount = GlyphIndices.Length;
  433. if (GlyphAdvances.Length > 0 && GlyphAdvances.Length != glyphCount)
  434. {
  435. throw new InvalidOperationException();
  436. }
  437. if (GlyphOffsets.Length > 0 && GlyphOffsets.Length != glyphCount)
  438. {
  439. throw new InvalidOperationException();
  440. }
  441. var platformRenderInterface = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
  442. _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);
  443. if (bounds.HasValue)
  444. {
  445. _bounds = bounds;
  446. }
  447. else
  448. {
  449. var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
  450. _bounds = new Rect(0, 0, width, height);
  451. }
  452. }
  453. void IDisposable.Dispose()
  454. {
  455. _glyphRunImpl?.Dispose();
  456. }
  457. }
  458. }