FontRun.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. #define USE_SKTEXTBLOB
  2. // RichTextKit
  3. // Copyright © 2019-2020 Topten Software. All Rights Reserved.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. // not use this product except in compliance with the License. You may obtain
  7. // a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. // License for the specific language governing permissions and limitations
  15. // under the License.
  16. // Modifications by Alexander Tsyganeko: marked by ATZ
  17. using HarfBuzzSharp;
  18. using SkiaSharp;
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Threading;
  22. using Topten.RichTextKit.Utils;
  23. namespace Topten.RichTextKit
  24. {
  25. /// <summary>
  26. /// Represents a font run - a physical sequence of laid glyphs all with
  27. /// the same font and style attributes.
  28. /// </summary>
  29. public class FontRun
  30. {
  31. /// <summary>
  32. /// The kind of font run.
  33. /// </summary>
  34. public FontRunKind RunKind = FontRunKind.Normal;
  35. /// <summary>
  36. /// The style run this typeface run was derived from.
  37. /// </summary>
  38. public StyleRun StyleRun;
  39. /// <summary>
  40. /// Get the code points of this run
  41. /// </summary>
  42. public Slice<int> CodePoints => CodePointBuffer.SubSlice(Start, Length);
  43. /// <summary>
  44. /// Code point index of the start of this run
  45. /// </summary>
  46. public int Start;
  47. /// <summary>
  48. /// The length of this run in codepoints
  49. /// </summary>
  50. public int Length;
  51. /// <summary>
  52. /// The index of the first character after this run
  53. /// </summary>
  54. public int End => Start + Length;
  55. /// <summary>
  56. /// The user supplied style for this run
  57. /// </summary>
  58. public IStyle Style;
  59. /// <summary>
  60. /// The direction of this run
  61. /// </summary>
  62. public TextDirection Direction;
  63. /// <summary>
  64. /// The typeface of this run (use this over Style.Fontface)
  65. /// </summary>
  66. public SKTypeface Typeface;
  67. /// <summary>
  68. /// The glyph indicies
  69. /// </summary>
  70. public Slice<ushort> Glyphs;
  71. /// <summary>
  72. /// The glyph positions (relative to the entire text block)
  73. /// </summary>
  74. public Slice<SKPoint> GlyphPositions;
  75. /// <summary>
  76. /// The cluster numbers for each glyph
  77. /// </summary>
  78. public Slice<int> Clusters;
  79. /// <summary>
  80. /// The x-coords of each code point, relative to this text run
  81. /// </summary>
  82. public Slice<float> RelativeCodePointXCoords;
  83. /// <summary>
  84. /// Get the x-coord of a code point
  85. /// </summary>
  86. /// <remarks>
  87. /// For LTR runs this will be the x-coordinate to the left, or RTL
  88. /// runs it will be the x-coordinate to the right.
  89. /// </remarks>
  90. /// <param name="codePointIndex">The code point index (relative to the entire text block)</param>
  91. /// <returns>The x-coord relative to the entire text block</returns>
  92. public float GetXCoordOfCodePointIndex(int codePointIndex)
  93. {
  94. if (this.RunKind == FontRunKind.Ellipsis)
  95. codePointIndex = 0;
  96. // Check in range
  97. if (codePointIndex < Start || codePointIndex > End)
  98. throw new ArgumentOutOfRangeException(nameof(codePointIndex));
  99. // End of run?
  100. if (codePointIndex == End)
  101. return XCoord + (Direction == TextDirection.LTR ? Width : 0);
  102. // Lookup
  103. return XCoord + RelativeCodePointXCoords[codePointIndex - Start];
  104. }
  105. /// <summary>
  106. /// The ascent of the font used in this run
  107. /// </summary>
  108. public float Ascent;
  109. /// <summary>
  110. /// The descent of the font used in this run
  111. /// </summary>
  112. public float Descent;
  113. /// <summary>
  114. /// The leading of the font used in this run
  115. /// </summary>
  116. public float Leading;
  117. /// <summary>
  118. /// The height of text in this run (ascent + descent)
  119. /// </summary>
  120. public float TextHeight => -Ascent + Descent;
  121. /// <summary>
  122. /// Calculate the half leading height for text in this run
  123. /// </summary>
  124. public float HalfLeading => (TextHeight * (Style.LineHeight - 1) + Leading) / 2;
  125. /// <summary>
  126. /// Width of this typeface run
  127. /// </summary>
  128. public float Width;
  129. /// <summary>
  130. /// Horizontal position of this run, relative to the left margin
  131. /// </summary>
  132. public float XCoord;
  133. /// <summary>
  134. /// The line that owns this font run
  135. /// </summary>
  136. public TextLine Line { get; internal set; }
  137. /// <summary>
  138. /// Get the next font run from this one
  139. /// </summary>
  140. public FontRun NextRun
  141. {
  142. get
  143. {
  144. var allRuns = Line.TextBlock.FontRuns as List<FontRun>;
  145. int index = allRuns.IndexOf(this);
  146. if (index < 0 || index + 1 >= Line.Runs.Count)
  147. return null;
  148. return Line.Runs[index + 1];
  149. }
  150. }
  151. /// <summary>
  152. /// Get the previous font run from this one
  153. /// </summary>
  154. public FontRun PreviousRun
  155. {
  156. get
  157. {
  158. var allRuns = Line.TextBlock.FontRuns as List<FontRun>;
  159. int index = allRuns.IndexOf(this);
  160. if (index - 1 < 0)
  161. return null;
  162. return Line.Runs[index - 1];
  163. }
  164. }
  165. /// <summary>
  166. /// For debugging
  167. /// </summary>
  168. /// <returns>Debug string</returns>
  169. public override string ToString()
  170. {
  171. switch (RunKind)
  172. {
  173. case FontRunKind.Normal:
  174. return $"{Start} - {End} @ {XCoord} - {XCoord + Width} = '{Utf32Utils.FromUtf32(CodePoints)}'";
  175. default:
  176. return $"{Start} - {End} @ {XCoord} - {XCoord + Width} {RunKind}'";
  177. }
  178. }
  179. /// <summary>
  180. /// Moves all glyphs by the specified offset amount
  181. /// </summary>
  182. /// <param name="dx">The x-delta to move glyphs by</param>
  183. /// <param name="dy">The y-delta to move glyphs by</param>
  184. public void MoveGlyphs(float dx, float dy)
  185. {
  186. for (int i = 0; i < GlyphPositions.Length; i++)
  187. {
  188. GlyphPositions[i].X += dx;
  189. GlyphPositions[i].Y += dy;
  190. }
  191. _textBlob?.Dispose();
  192. _textBlob = null;
  193. }
  194. /// <summary>
  195. /// Calculates the leading width of all character from the start of the run (either
  196. /// the left or right depending on run direction) to the specified code point
  197. /// </summary>
  198. /// <param name="codePoint">The code point index to measure to</param>
  199. /// <returns>The distance from the start to the specified code point</returns>
  200. public float LeadingWidth(int codePoint)
  201. {
  202. // At either end?
  203. if (codePoint == this.End)
  204. return this.Width;
  205. if (codePoint == 0)
  206. return 0;
  207. // Internal, calculate the leading width (ie from code point 0 to code point N)
  208. int codePointIndex = codePoint - this.Start;
  209. if (this.Direction == TextDirection.LTR)
  210. {
  211. return this.RelativeCodePointXCoords[codePointIndex];
  212. }
  213. else
  214. {
  215. return this.Width - this.RelativeCodePointXCoords[codePointIndex];
  216. }
  217. }
  218. /// <summary>
  219. /// Calculate the position at which to break a text run
  220. /// </summary>
  221. /// <param name="maxWidth">The max width available</param>
  222. /// <param name="force">Whether to force the use of at least one glyph</param>
  223. /// <returns>The code point position to break at</returns>
  224. internal int FindBreakPosition(float maxWidth, bool force)
  225. {
  226. int lastFittingCodePoint = this.Start;
  227. int firstNonZeroWidthCodePoint = -1;
  228. var prevWidth = 0f;
  229. for (int i = this.Start; i < this.End; i++)
  230. {
  231. var width = this.LeadingWidth(i);
  232. if (prevWidth != width)
  233. {
  234. if (firstNonZeroWidthCodePoint < 0)
  235. firstNonZeroWidthCodePoint = i;
  236. if (width < maxWidth)
  237. {
  238. lastFittingCodePoint = i;
  239. }
  240. else
  241. {
  242. break;
  243. }
  244. }
  245. prevWidth = width;
  246. }
  247. if (lastFittingCodePoint > this.Start || !force)
  248. return lastFittingCodePoint;
  249. if (firstNonZeroWidthCodePoint > this.Start)
  250. return firstNonZeroWidthCodePoint;
  251. // Split at the end
  252. return this.End;
  253. }
  254. /// <summary>
  255. /// Split a typeface run into two separate runs, truncating this run at
  256. /// the specified code point index and returning a new run containing the
  257. /// split off part.
  258. /// </summary>
  259. /// <param name="splitAtCodePoint">The code point index to split at</param>
  260. /// <returns>A new typeface run for the split off part</returns>
  261. internal FontRun Split(int splitAtCodePoint)
  262. {
  263. if (this.Direction == TextDirection.LTR)
  264. {
  265. return SplitLTR(splitAtCodePoint);
  266. }
  267. else
  268. {
  269. return SplitRTL(splitAtCodePoint);
  270. }
  271. }
  272. /// <summary>
  273. /// Split a LTR typeface run into two separate runs, truncating the passed
  274. /// run (LHS) and returning a new run containing the split off part (RHS)
  275. /// </summary>
  276. /// <param name="splitAtCodePoint">To code point position to split at</param>
  277. /// <returns>The RHS run after splitting</returns>
  278. private FontRun SplitLTR(int splitAtCodePoint)
  279. {
  280. // Check split point is internal to the run
  281. System.Diagnostics.Debug.Assert(this.Direction == TextDirection.LTR);
  282. System.Diagnostics.Debug.Assert(splitAtCodePoint > this.Start);
  283. System.Diagnostics.Debug.Assert(splitAtCodePoint < this.End);
  284. // Work out the split position
  285. int codePointSplitPos = splitAtCodePoint - this.Start;
  286. // Work out the width that we're slicing off
  287. float sliceLeftWidth = this.RelativeCodePointXCoords[codePointSplitPos];
  288. float sliceRightWidth = this.Width - sliceLeftWidth;
  289. // Work out the glyph split position
  290. int glyphSplitPos = 0;
  291. for (glyphSplitPos = 0; glyphSplitPos < this.Clusters.Length; glyphSplitPos++)
  292. {
  293. if (this.Clusters[glyphSplitPos] >= splitAtCodePoint)
  294. break;
  295. }
  296. // Create the other run
  297. var newRun = FontRun.Pool.Value.Get();
  298. newRun.StyleRun = this.StyleRun;
  299. newRun.CodePointBuffer = this.CodePointBuffer;
  300. newRun.Direction = this.Direction;
  301. newRun.Ascent = this.Ascent;
  302. newRun.Descent = this.Descent;
  303. newRun.Leading = this.Leading;
  304. newRun.Style = this.Style;
  305. newRun.Typeface = this.Typeface;
  306. newRun.Start = splitAtCodePoint;
  307. newRun.Length = this.End - splitAtCodePoint;
  308. newRun.Width = sliceRightWidth;
  309. newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
  310. newRun.GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos);
  311. newRun.Glyphs = this.Glyphs.SubSlice(glyphSplitPos);
  312. newRun.Clusters = this.Clusters.SubSlice(glyphSplitPos);
  313. // Adjust code point positions
  314. for (int i = 0; i < newRun.RelativeCodePointXCoords.Length; i++)
  315. {
  316. newRun.RelativeCodePointXCoords[i] -= sliceLeftWidth;
  317. }
  318. // Adjust glyph positions
  319. for (int i = 0; i < newRun.GlyphPositions.Length; i++)
  320. {
  321. newRun.GlyphPositions[i].X -= sliceLeftWidth;
  322. }
  323. // ATZ: remove \r symbol
  324. if (codePointSplitPos > 0 && this.CodePoints[codePointSplitPos - 1] == '\r')
  325. {
  326. codePointSplitPos--;
  327. glyphSplitPos--;
  328. sliceLeftWidth = this.RelativeCodePointXCoords[codePointSplitPos];
  329. if (codePointSplitPos == 0 || glyphSplitPos == 0)
  330. this.RunKind = FontRunKind.TrailingWhitespace;
  331. }
  332. // Update this run
  333. this.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(0, codePointSplitPos);
  334. this.Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos);
  335. this.GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos);
  336. this.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
  337. this.Width = sliceLeftWidth;
  338. this.Length = codePointSplitPos;
  339. this._textBlob?.Dispose();
  340. this._textBlob = null;
  341. // Return the new run
  342. return newRun;
  343. }
  344. /// <summary>
  345. /// Split a RTL typeface run into two separate runs, truncating the passed
  346. /// run (RHS) and returning a new run containing the split off part (LHS)
  347. /// </summary>
  348. /// <param name="splitAtCodePoint">To code point position to split at</param>
  349. /// <returns>The LHS run after splitting</returns>
  350. private FontRun SplitRTL(int splitAtCodePoint)
  351. {
  352. // Check split point is internal to the run
  353. System.Diagnostics.Debug.Assert(this.Direction == TextDirection.RTL);
  354. System.Diagnostics.Debug.Assert(splitAtCodePoint > this.Start);
  355. System.Diagnostics.Debug.Assert(splitAtCodePoint < this.End);
  356. // Work out the split position
  357. int codePointSplitPos = splitAtCodePoint - this.Start;
  358. // Work out the width that we're slicing off
  359. float sliceLeftWidth = this.RelativeCodePointXCoords[codePointSplitPos];
  360. float sliceRightWidth = this.Width - sliceLeftWidth;
  361. // Work out the glyph split position
  362. int glyphSplitPos = 0;
  363. for (glyphSplitPos = this.Clusters.Length; glyphSplitPos > 0; glyphSplitPos--)
  364. {
  365. if (this.Clusters[glyphSplitPos - 1] >= splitAtCodePoint)
  366. break;
  367. }
  368. // Create the other run
  369. var newRun = FontRun.Pool.Value.Get();
  370. newRun.StyleRun = this.StyleRun;
  371. newRun.CodePointBuffer = this.CodePointBuffer;
  372. newRun.Direction = this.Direction;
  373. newRun.Ascent = this.Ascent;
  374. newRun.Descent = this.Descent;
  375. newRun.Leading = this.Leading;
  376. newRun.Style = this.Style;
  377. newRun.Typeface = this.Typeface;
  378. newRun.Start = splitAtCodePoint;
  379. newRun.Length = this.End - splitAtCodePoint;
  380. newRun.Width = sliceLeftWidth;
  381. newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
  382. newRun.GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos);
  383. newRun.Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos);
  384. newRun.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
  385. // ATZ: remove \r symbol
  386. if (codePointSplitPos > 0 && this.CodePoints[codePointSplitPos - 1] == '\r')
  387. {
  388. codePointSplitPos++;
  389. glyphSplitPos++;
  390. sliceLeftWidth = this.RelativeCodePointXCoords[codePointSplitPos];
  391. sliceRightWidth = this.Width - sliceLeftWidth;
  392. if (codePointSplitPos >= this.CodePoints.Length || glyphSplitPos >= this.Glyphs.Length)
  393. this.RunKind = FontRunKind.TrailingWhitespace;
  394. }
  395. // Update this run
  396. this.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(0, codePointSplitPos);
  397. this.Glyphs = this.Glyphs.SubSlice(glyphSplitPos);
  398. this.GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos);
  399. this.Clusters = this.Clusters.SubSlice(glyphSplitPos);
  400. this.Width = sliceRightWidth;
  401. this.Length = codePointSplitPos;
  402. this._textBlob?.Dispose();
  403. this._textBlob = null;
  404. // Adjust code point positions
  405. for (int i = 0; i < this.RelativeCodePointXCoords.Length; i++)
  406. {
  407. this.RelativeCodePointXCoords[i] -= sliceLeftWidth;
  408. }
  409. // Adjust glyph positions
  410. for (int i = 0; i < this.GlyphPositions.Length; i++)
  411. {
  412. this.GlyphPositions[i].X -= sliceLeftWidth;
  413. }
  414. // Return the new run
  415. return newRun;
  416. }
  417. /// <summary>
  418. /// The global list of code points
  419. /// </summary>
  420. internal Buffer<int> CodePointBuffer;
  421. /// <summary>
  422. /// Calculate any overhang for this text line
  423. /// </summary>
  424. /// <param name="right"></param>
  425. /// <param name="leftOverhang"></param>
  426. /// <param name="rightOverhang"></param>
  427. internal void UpdateOverhang(float right, ref float leftOverhang, ref float rightOverhang)
  428. {
  429. if (RunKind == FontRunKind.TrailingWhitespace)
  430. return;
  431. if (Glyphs.Length == 0)
  432. return;
  433. using (var paint = new SKPaint())
  434. {
  435. float glyphScale = 1;
  436. if (Style.FontVariant == FontVariant.SuperScript)
  437. {
  438. glyphScale = 0.65f;
  439. }
  440. if (Style.FontVariant == FontVariant.SubScript)
  441. {
  442. glyphScale = 0.65f;
  443. }
  444. paint.TextEncoding = SKTextEncoding.GlyphId;
  445. paint.Typeface = Typeface;
  446. paint.TextSize = Style.FontSize * glyphScale;
  447. paint.SubpixelText = true;
  448. paint.IsAntialias = true;
  449. paint.LcdRenderText = false;
  450. unsafe
  451. {
  452. fixed (ushort* pGlyphs = Glyphs.Underlying)
  453. {
  454. // ATZ: should it be Glyphs.Start here? VVVVV
  455. paint.GetGlyphWidths((IntPtr)(pGlyphs + Start), sizeof(ushort) * Glyphs.Length, out var bounds);
  456. if (bounds != null)
  457. {
  458. for (int i = 0; i < bounds.Length; i++)
  459. {
  460. float gx = GlyphPositions[i].X;
  461. var loh = -(gx + bounds[i].Left);
  462. if (loh > leftOverhang)
  463. leftOverhang = loh;
  464. var roh = (gx + bounds[i].Right + 1) - right;
  465. if (roh > rightOverhang)
  466. rightOverhang = roh;
  467. }
  468. }
  469. }
  470. }
  471. }
  472. }
  473. /// <summary>
  474. /// Paint this font run
  475. /// </summary>
  476. /// <param name="ctx"></param>
  477. internal void Paint(PaintTextContext ctx)
  478. {
  479. // Paint selection?
  480. if (ctx.PaintSelectionBackground != null && RunKind != FontRunKind.Ellipsis)
  481. {
  482. bool paintStartHandle = false;
  483. bool paintEndHandle = false;
  484. float selStartXCoord;
  485. if (ctx.SelectionStart < Start)
  486. selStartXCoord = Direction == TextDirection.LTR ? 0 : Width;
  487. else if (ctx.SelectionStart >= End)
  488. selStartXCoord = Direction == TextDirection.LTR ? Width : 0;
  489. else
  490. {
  491. paintStartHandle = true;
  492. selStartXCoord = RelativeCodePointXCoords[ctx.SelectionStart - this.Start];
  493. }
  494. float selEndXCoord;
  495. if (ctx.SelectionEnd < Start)
  496. selEndXCoord = Direction == TextDirection.LTR ? 0 : Width;
  497. else if (ctx.SelectionEnd >= End)
  498. {
  499. selEndXCoord = Direction == TextDirection.LTR ? Width : 0;
  500. paintEndHandle = ctx.SelectionEnd == End;
  501. }
  502. else
  503. {
  504. selEndXCoord = RelativeCodePointXCoords[ctx.SelectionEnd - this.Start];
  505. paintEndHandle = true;
  506. }
  507. if (selStartXCoord != selEndXCoord)
  508. {
  509. var tl = new SKPoint(selStartXCoord + this.XCoord, Line.YCoord);
  510. var br = new SKPoint(selEndXCoord + this.XCoord, Line.YCoord + Line.Height);
  511. // Align coords to pixel boundaries
  512. // Not needed - disabled antialias on SKPaint instead
  513. /*
  514. if (ctx.Canvas.TotalMatrix.TryInvert(out var inverse))
  515. {
  516. tl = ctx.Canvas.TotalMatrix.MapPoint(tl);
  517. br = ctx.Canvas.TotalMatrix.MapPoint(br);
  518. tl = new SKPoint((float)Math.Round(tl.X), (float)Math.Round(tl.Y));
  519. br = new SKPoint((float)Math.Round(br.X), (float)Math.Round(br.Y));
  520. tl = inverse.MapPoint(tl);
  521. br = inverse.MapPoint(br);
  522. }
  523. */
  524. var rect = new SKRect(tl.X, tl.Y, br.X, br.Y);
  525. ctx.Canvas.DrawRect(rect, ctx.PaintSelectionBackground);
  526. // Paint selection handles?
  527. if (ctx.PaintSelectionHandle != null)
  528. {
  529. if (paintStartHandle)
  530. {
  531. rect = new SKRect(tl.X - 1 * ctx.SelectionHandleScale, tl.Y, tl.X + 1 * ctx.SelectionHandleScale, br.Y);
  532. ctx.Canvas.DrawRect(rect, ctx.PaintSelectionHandle);
  533. ctx.Canvas.DrawCircle(new SKPoint(tl.X, tl.Y), 5 * ctx.SelectionHandleScale, ctx.PaintSelectionHandle);
  534. }
  535. if (paintEndHandle)
  536. {
  537. rect = new SKRect(br.X - 1 * ctx.SelectionHandleScale, tl.Y, br.X + 1 * ctx.SelectionHandleScale, br.Y);
  538. ctx.Canvas.DrawRect(rect, ctx.PaintSelectionHandle);
  539. ctx.Canvas.DrawCircle(new SKPoint(br.X, br.Y), 5 * ctx.SelectionHandleScale, ctx.PaintSelectionHandle);
  540. }
  541. }
  542. }
  543. }
  544. // Don't paint trailing whitespace runs
  545. if (RunKind == FontRunKind.TrailingWhitespace)
  546. return;
  547. // Text
  548. using (var paint = new SKPaint())
  549. using (var paintHalo = new SKPaint())
  550. {
  551. // Work out font variant adjustments
  552. float glyphScale = 1;
  553. float glyphVOffset = 0;
  554. if (Style.FontVariant == FontVariant.SuperScript)
  555. {
  556. glyphScale = 0.65f;
  557. glyphVOffset = -Style.FontSize * 0.35f;
  558. }
  559. if (Style.FontVariant == FontVariant.SubScript)
  560. {
  561. glyphScale = 0.65f;
  562. glyphVOffset = Style.FontSize * 0.1f;
  563. }
  564. // Setup SKPaint
  565. paint.Color = Style.TextColor;
  566. if (Style.HaloColor != SKColor.Empty)
  567. {
  568. paintHalo.Color = Style.HaloColor;
  569. paintHalo.Style = SKPaintStyle.Stroke;
  570. paintHalo.StrokeWidth = Style.HaloWidth;
  571. paintHalo.StrokeCap = SKStrokeCap.Square;
  572. if (Style.HaloBlur > 0)
  573. paintHalo.MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, Style.HaloBlur);
  574. }
  575. unsafe
  576. {
  577. fixed (ushort* pGlyphs = Glyphs.Underlying)
  578. {
  579. // Get glyph positions
  580. var glyphPositions = GlyphPositions.ToArray();
  581. // Create the font
  582. if (_font == null)
  583. {
  584. _font = new SKFont(this.Typeface, this.Style.FontSize * glyphScale);
  585. }
  586. _font.Hinting = ctx.Options.Hinting;
  587. _font.Edging = ctx.Options.Edging;
  588. _font.Subpixel = ctx.Options.SubpixelPositioning;
  589. // Create the SKTextBlob (if necessary)
  590. if (_textBlob == null)
  591. {
  592. _textBlob = SKTextBlob.CreatePositioned(
  593. (IntPtr)(pGlyphs + Glyphs.Start),
  594. Glyphs.Length * sizeof(ushort),
  595. SKTextEncoding.GlyphId,
  596. _font,
  597. GlyphPositions.AsSpan());
  598. }
  599. // Paint underline
  600. if (Style.Underline != UnderlineStyle.None && RunKind == FontRunKind.Normal)
  601. {
  602. // Work out underline metrics
  603. float underlineYPos = Line.YCoord + Line.BaseLine + (_font.Metrics.UnderlinePosition ?? 0);
  604. paint.StrokeWidth = _font.Metrics.UnderlineThickness ?? 1;
  605. paintHalo.StrokeWidth = paint.StrokeWidth + Style.HaloWidth;
  606. if (Style.Underline == UnderlineStyle.Gapped)
  607. {
  608. // Get intercept positions
  609. var interceptPositions = _textBlob.GetIntercepts(underlineYPos - paint.StrokeWidth / 2, underlineYPos + paint.StrokeWidth);
  610. // Paint gapped underlinline
  611. float x = XCoord;
  612. for (int i = 0; i < interceptPositions.Length; i += 2)
  613. {
  614. float b = interceptPositions[i] - paint.StrokeWidth;
  615. if (x < b)
  616. {
  617. if (Style.HaloColor != SKColor.Empty)
  618. ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(b, underlineYPos), paintHalo);
  619. // ATZ : draw to path
  620. if (ctx.Options.DrawToPath != null)
  621. ctx.Options.DrawToPath.AddRect(new SKRect(x, underlineYPos, b, underlineYPos + 1));
  622. else
  623. //
  624. ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(b, underlineYPos), paint);
  625. }
  626. x = interceptPositions[i + 1] + paint.StrokeWidth;
  627. }
  628. if (x < XCoord + Width)
  629. {
  630. if (Style.HaloColor != SKColor.Empty)
  631. ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paintHalo);
  632. // ATZ : draw to path
  633. if (ctx.Options.DrawToPath != null)
  634. ctx.Options.DrawToPath.AddRect(new SKRect(x, underlineYPos, XCoord + Width, underlineYPos + paint.StrokeWidth));
  635. else
  636. //
  637. ctx.Canvas.DrawLine(new SKPoint(x, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paint);
  638. }
  639. }
  640. else
  641. {
  642. switch (Style.Underline)
  643. {
  644. case UnderlineStyle.ImeInput:
  645. paint.PathEffect = SKPathEffect.CreateDash(new float[] { paint.StrokeWidth, paint.StrokeWidth }, paint.StrokeWidth);
  646. paintHalo.PathEffect = SKPathEffect.CreateDash(new float[] { paintHalo.StrokeWidth, paintHalo.StrokeWidth }, paintHalo.StrokeWidth);
  647. break;
  648. case UnderlineStyle.ImeConverted:
  649. paint.PathEffect = SKPathEffect.CreateDash(new float[] { paint.StrokeWidth, paint.StrokeWidth }, paint.StrokeWidth);
  650. paintHalo.PathEffect = SKPathEffect.CreateDash(new float[] { paintHalo.StrokeWidth, paintHalo.StrokeWidth }, paintHalo.StrokeWidth);
  651. break;
  652. case UnderlineStyle.ImeTargetConverted:
  653. paint.StrokeWidth *= 2;
  654. paintHalo.StrokeWidth *= 2;
  655. break;
  656. case UnderlineStyle.ImeTargetNonConverted:
  657. break;
  658. }
  659. // Paint solid underline
  660. if (Style.HaloColor != SKColor.Empty)
  661. ctx.Canvas.DrawLine(new SKPoint(XCoord, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paintHalo);
  662. // ATZ : draw to path
  663. if (ctx.Options.DrawToPath != null)
  664. ctx.Options.DrawToPath.AddRect(new SKRect(XCoord, underlineYPos, XCoord + Width, underlineYPos + paint.StrokeWidth));
  665. else
  666. //
  667. ctx.Canvas.DrawLine(new SKPoint(XCoord, underlineYPos), new SKPoint(XCoord + Width, underlineYPos), paint);
  668. paint.PathEffect = null;
  669. paintHalo.PathEffect = null;
  670. }
  671. }
  672. if (Style.HaloColor != SKColor.Empty)
  673. {
  674. // Paint strikethrough
  675. if (Style.StrikeThrough != StrikeThroughStyle.None && RunKind == FontRunKind.Normal)
  676. {
  677. paint.StrokeWidth = _font.Metrics.StrikeoutThickness ?? 0;
  678. float strikeYPos = Line.YCoord + Line.BaseLine + (_font.Metrics.StrikeoutPosition ?? 0) + glyphVOffset;
  679. ctx.Canvas.DrawLine(new SKPoint(XCoord, strikeYPos), new SKPoint(XCoord + Width, strikeYPos), paintHalo);
  680. }
  681. ctx.Canvas.DrawText(_textBlob, 0, 0, paintHalo);
  682. }
  683. // ATZ : draw to path
  684. if (ctx.Options.DrawToPath != null)
  685. {
  686. paint.Typeface = _font.Typeface;
  687. paint.TextSize = _font.Size;
  688. paint.TextEncoding = SKTextEncoding.GlyphId;
  689. ctx.Options.DrawToPath.AddPath(
  690. paint.GetTextPath((IntPtr)(pGlyphs + Glyphs.Start), Glyphs.Length * sizeof(ushort), GlyphPositions.AsSpan()));
  691. }
  692. else
  693. //
  694. ctx.Canvas.DrawText(_textBlob, 0, 0, paint);
  695. }
  696. }
  697. // Paint strikethrough
  698. if (Style.StrikeThrough != StrikeThroughStyle.None && RunKind == FontRunKind.Normal)
  699. {
  700. paint.StrokeWidth = _font.Metrics.StrikeoutThickness ?? 0;
  701. float strikeYPos = Line.YCoord + Line.BaseLine + (_font.Metrics.StrikeoutPosition ?? 0) + glyphVOffset;
  702. // ATZ : draw to path
  703. if (ctx.Options.DrawToPath != null)
  704. ctx.Options.DrawToPath.AddRect(new SKRect(XCoord, strikeYPos, XCoord + Width, strikeYPos + paint.StrokeWidth));
  705. else
  706. //
  707. ctx.Canvas.DrawLine(new SKPoint(XCoord, strikeYPos), new SKPoint(XCoord + Width, strikeYPos), paint);
  708. }
  709. }
  710. }
  711. /// <summary>
  712. /// Paint background of this font run
  713. /// </summary>
  714. /// <param name="ctx"></param>
  715. internal void PaintBackground(PaintTextContext ctx)
  716. {
  717. if (Style.BackgroundColor != SKColor.Empty && RunKind == FontRunKind.Normal)
  718. {
  719. var rect = new SKRect(XCoord , Line.YCoord,
  720. XCoord + Width, Line.YCoord + Line.Height);
  721. using (var skPaint = new SKPaint {Style = SKPaintStyle.Fill, Color = Style.BackgroundColor})
  722. {
  723. ctx.Canvas.DrawRect(rect, skPaint);
  724. }
  725. }
  726. }
  727. SKTextBlob _textBlob;
  728. SKFont _font;
  729. void Reset()
  730. {
  731. RunKind = FontRunKind.Normal;
  732. CodePointBuffer = null;
  733. Style = null;
  734. Typeface = null;
  735. Line = null;
  736. _textBlob = null;
  737. _font = null;
  738. }
  739. internal static ThreadLocal<ObjectPool<FontRun>> Pool = new ThreadLocal<ObjectPool<FontRun>>(() => new ObjectPool<FontRun>()
  740. {
  741. Cleaner = (r) => r.Reset()
  742. });
  743. }
  744. }