StyleManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. // RichTextKit
  2. // Copyright © 2019-2020 Topten Software. All Rights Reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. // not use this product except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. using SkiaSharp;
  16. using System.Collections.Generic;
  17. using System.Threading;
  18. namespace Topten.RichTextKit
  19. {
  20. /// <summary>
  21. /// Helper class for managing RichTextKit styles.
  22. /// </summary>
  23. /// <remarks>
  24. /// The StyleManager can be used to simplify the creation of styles by
  25. /// maintaining a current style that incremental changes can be made to.
  26. ///
  27. /// eg: turn bold on, underline off, change font family etc...
  28. ///
  29. /// The StyleManager also implements an internal stack to simplify applying
  30. /// a particular style and then popping back to the previous style.
  31. /// </remarks>
  32. public class StyleManager
  33. {
  34. /// <summary>
  35. /// A per-thread style manager
  36. /// </summary>
  37. public static ThreadLocal<StyleManager> Default = new ThreadLocal<StyleManager>(() => new StyleManager());
  38. /// <summary>
  39. /// Constructs a new StyleManager
  40. /// </summary>
  41. public StyleManager()
  42. {
  43. _currentStyle = FromStyle(new Style());
  44. _defaultStyle = _currentStyle;
  45. }
  46. /// <summary>
  47. /// The current style
  48. /// </summary>
  49. public IStyle CurrentStyle
  50. {
  51. get => _currentStyle;
  52. set => _currentStyle = FromStyle(value);
  53. }
  54. /// <summary>
  55. /// The default style to be be used when Reset is called
  56. /// </summary>
  57. public IStyle DefaultStyle
  58. {
  59. get => _defaultStyle;
  60. set => _defaultStyle = FromStyle(value);
  61. }
  62. /// <summary>
  63. /// Get a style that matches all the style attributes of the supplied style
  64. /// </summary>
  65. /// <remarks>
  66. /// This method creates a style owned by this style manager with all the same
  67. /// attributes as the passed style.
  68. /// </remarks>
  69. /// <param name="value">The style to copy</param>
  70. /// <returns></returns>
  71. public IStyle FromStyle(IStyle value)
  72. {
  73. // Is it a style we already own? Just re-use it.
  74. if (IsOwned(value))
  75. return value;
  76. return Update(value.FontFamily, value.FontSize, value.FontWeight, value.FontWidth, value.FontItalic,
  77. value.Underline, value.StrikeThrough, value.LineHeight, value.TextColor, value.BackgroundColor,
  78. value.HaloColor, value.HaloWidth, value.HaloBlur,
  79. value.LetterSpacing, value.FontVariant, value.TextDirection, value.ReplacementCharacter);
  80. }
  81. /// <summary>
  82. /// Resets the current style to the default style and resets the internal
  83. /// Push/Pop style stack to empty.
  84. /// </summary>
  85. public void Reset()
  86. {
  87. _currentStyle = _defaultStyle;
  88. _userStack.Clear();
  89. }
  90. /// <summary>
  91. /// Saves the current state on an internal stack
  92. /// </summary>
  93. public void Push()
  94. {
  95. _userStack.Push(_currentStyle);
  96. }
  97. /// <summary>
  98. /// Restores the current state on an internal stack
  99. /// </summary>
  100. public void Pop()
  101. {
  102. _currentStyle = _userStack.Pop();
  103. }
  104. /// <summary>
  105. /// Changes the font family and returns an updated IStyle
  106. /// </summary>
  107. /// <param name="fontFamily">The new font family</param>
  108. /// <returns>An IStyle for the new style</returns>
  109. public IStyle FontFamily(string fontFamily) => Update(fontFamily: fontFamily);
  110. /// <summary>
  111. /// Changes the font size and returns an updated IStyle
  112. /// </summary>
  113. /// <param name="fontSize">The new font size</param>
  114. /// <returns>An IStyle for the new style</returns>
  115. public IStyle FontSize(float fontSize) => Update(fontSize: fontSize);
  116. /// <summary>
  117. /// Changes the font weight and returns an updated IStyle
  118. /// </summary>
  119. /// <param name="fontWeight">The new font weight</param>
  120. /// <returns>An IStyle for the new style</returns>
  121. public IStyle FontWeight(int fontWeight) => Update(fontWeight: fontWeight);
  122. /// <summary>
  123. /// Changes the font weight and returns an update IStyle (short cut to FontWeight)
  124. /// </summary>
  125. /// <param name="bold">The new font weight</param>
  126. /// <returns>An IStyle for the new style</returns>
  127. public IStyle Bold(bool bold) => Update(fontWeight: bold ? 700 : 400);
  128. /// <summary>
  129. /// Changes the font width and returns an updated IStyle
  130. /// </summary>
  131. /// <param name="fontWidth">The new font width</param>
  132. /// <returns>An IStyle for the new style</returns>
  133. public IStyle FontWidth(SKFontStyleWidth fontWidth) => Update(fontWidth: fontWidth);
  134. /// <summary>
  135. /// Changes the font italic setting and returns an updated IStyle
  136. /// </summary>
  137. /// <param name="fontItalic">The new font italic setting</param>
  138. /// <returns>An IStyle for the new style</returns>
  139. public IStyle FontItalic(bool fontItalic) => Update(fontItalic: fontItalic);
  140. /// <summary>
  141. /// Changes the underline style and returns an updated IStyle
  142. /// </summary>
  143. /// <param name="underline">The new underline style</param>
  144. /// <returns>An IStyle for the new style</returns>
  145. public IStyle Underline(UnderlineStyle underline) => Update(underline: underline);
  146. /// <summary>
  147. /// Changes the strikethrough style and returns an updated IStyle
  148. /// </summary>
  149. /// <param name="strikeThrough">The new strikethrough style</param>
  150. /// <returns>An IStyle for the new style</returns>
  151. public IStyle StrikeThrough(StrikeThroughStyle strikeThrough) => Update(strikeThrough: strikeThrough);
  152. /// <summary>
  153. /// Changes the line height and returns an updated IStyle
  154. /// </summary>
  155. /// <param name="lineHeight">The new line height</param>
  156. /// <returns>An IStyle for the new style</returns>
  157. public IStyle LineHeight(float lineHeight) => Update(lineHeight: lineHeight);
  158. /// <summary>
  159. /// Changes the text color and returns an updated IStyle
  160. /// </summary>
  161. /// <param name="textColor">The new text color</param>
  162. /// <returns>An IStyle for the new style</returns>
  163. public IStyle TextColor(SKColor textColor) => Update(textColor: textColor);
  164. /// <summary>
  165. /// Changes the background color and returns an updated IStyle
  166. /// </summary>
  167. /// <param name="backgroundColor">The new background color</param>
  168. /// <returns>An IStyle for the new style</returns>
  169. public IStyle BackgroundColor(SKColor backgroundColor) => Update(backgroundColor: backgroundColor);
  170. /// <summary>
  171. /// Changes the halo color and returns an updated IStyle
  172. /// </summary>
  173. /// <param name="haloColor">The new halo color</param>
  174. /// <returns>An IStyle for the new style</returns>
  175. public IStyle HaloColor(SKColor haloColor) => Update(haloColor: haloColor);
  176. /// <summary>
  177. /// Changes the halo width and returns an updated IStyle
  178. /// </summary>
  179. /// <param name="haloWidth">The new halo width</param>
  180. /// <returns>An IStyle for the new style</returns>
  181. public IStyle HaloWidth(float haloWidth) => Update(haloWidth: haloWidth);
  182. /// <summary>
  183. /// Changes the halo blur width and returns an updated IStyle
  184. /// </summary>
  185. /// <param name="haloBlur">The new halo blur width</param>
  186. /// <returns>An IStyle for the new style</returns>
  187. public IStyle HaloBlur(float haloBlur) => Update(haloBlur: haloBlur);
  188. /// <summary>
  189. /// Changes the character spacing and returns an updated IStyle
  190. /// </summary>
  191. /// <param name="letterSpacing">The new character spacing</param>
  192. /// <returns>An IStyle for the new style</returns>
  193. public IStyle LetterSpacing(float letterSpacing) => Update(letterSpacing: letterSpacing);
  194. /// <summary>
  195. /// Changes the font variant and returns an updated IStyle
  196. /// </summary>
  197. /// <param name="fontVariant">The new font variant</param>
  198. /// <returns>An IStyle for the new style</returns>
  199. public IStyle FontVariant(FontVariant fontVariant) => Update(fontVariant: fontVariant);
  200. /// <summary>
  201. /// Changes the text direction and returns an updated IStyle
  202. /// </summary>
  203. /// <param name="textDirection">The new text direction</param>
  204. /// <returns>An IStyle for the new style</returns>
  205. public IStyle TextDirection(TextDirection textDirection) => Update(textDirection: textDirection);
  206. /// <summary>
  207. /// Changes the text direction and returns an updated IStyle
  208. /// </summary>
  209. /// <param name="character">The new replacement character</param>
  210. /// <returns>An IStyle for the new style</returns>
  211. public IStyle ReplacementCharacter(char character) => Update(replacementCharacter: character);
  212. /// <summary>
  213. /// Update the current style by applying one or more changes to the current
  214. /// style.
  215. /// </summary>
  216. /// <param name="fontFamily">The new font family</param>
  217. /// <param name="fontSize">The new font size</param>
  218. /// <param name="fontWeight">The new font weight</param>
  219. /// <param name="fontWidth">The new font width</param>
  220. /// <param name="fontItalic">The new font italic</param>
  221. /// <param name="underline">The new underline style</param>
  222. /// <param name="strikeThrough">The new strike-through style</param>
  223. /// <param name="lineHeight">The new line height</param>
  224. /// <param name="textColor">The new text color</param>
  225. /// <param name="backgroundColor">The new text color</param>
  226. /// <param name="haloColor">The new text color</param>
  227. /// <param name="haloWidth">The new halo width</param>
  228. /// <param name="haloBlur">The new halo blur width</param>
  229. /// <param name="letterSpacing">The new letterSpacing</param>
  230. /// <param name="fontVariant">The new font variant</param>
  231. /// <param name="textDirection">The new text direction</param>
  232. /// <param name="replacementCharacter">The new replacement character</param>
  233. /// <returns>An IStyle for the new style</returns>
  234. public IStyle Update(
  235. string fontFamily = null,
  236. float? fontSize = null,
  237. int? fontWeight = null,
  238. SKFontStyleWidth? fontWidth = 0,
  239. bool? fontItalic = null,
  240. UnderlineStyle? underline = null,
  241. StrikeThroughStyle? strikeThrough = null,
  242. float? lineHeight = null,
  243. SKColor? textColor = null,
  244. SKColor? backgroundColor = null,
  245. SKColor? haloColor = null,
  246. float? haloWidth = null,
  247. float? haloBlur = null,
  248. float? letterSpacing = null,
  249. FontVariant? fontVariant = null,
  250. TextDirection? textDirection = null,
  251. char? replacementCharacter = null
  252. )
  253. {
  254. // Resolve new style against current style
  255. var rFontFamily = fontFamily ?? _currentStyle.FontFamily;
  256. var rFontSize = fontSize ?? _currentStyle.FontSize;
  257. var rFontWeight = fontWeight ?? _currentStyle.FontWeight;
  258. var rFontWidth = fontWidth ?? _currentStyle.FontWidth;
  259. var rFontItalic = fontItalic ?? _currentStyle.FontItalic;
  260. var rUnderline = underline ?? _currentStyle.Underline;
  261. var rStrikeThrough = strikeThrough ?? _currentStyle.StrikeThrough;
  262. var rLineHeight = lineHeight ?? _currentStyle.LineHeight;
  263. var rTextColor = textColor ?? _currentStyle.TextColor;
  264. var rBackgroundColor = backgroundColor ?? _currentStyle.BackgroundColor;
  265. var rHaloColor = haloColor ?? _currentStyle.HaloColor;
  266. var rHaloWidth = haloWidth ?? _currentStyle.HaloWidth;
  267. var rHaloBlur = haloBlur ?? _currentStyle.HaloBlur;
  268. var rLetterSpacing = letterSpacing ?? _currentStyle.LetterSpacing;
  269. var rFontVariant = fontVariant ?? _currentStyle.FontVariant;
  270. var rTextDirection = textDirection ?? _currentStyle.TextDirection;
  271. var rReplacementCharacter = replacementCharacter ?? _currentStyle.ReplacementCharacter;
  272. // Format key
  273. var key = $"{rFontFamily}.{rFontSize}.{rFontWeight}.{fontWidth}.{rFontItalic}.{rUnderline}.{rStrikeThrough}.{rLineHeight}.{rTextColor}.{rBackgroundColor}.{rHaloColor}.{rHaloWidth}.{rHaloBlur}.{rLetterSpacing}.{rFontVariant}.{rTextDirection}.{rReplacementCharacter}";
  274. // Look up...
  275. if (!_styleMap.TryGetValue(key, out var style))
  276. {
  277. // Create a new style
  278. style = new StyleManagerStyle()
  279. {
  280. Owner = this,
  281. FontFamily = rFontFamily,
  282. FontSize = rFontSize,
  283. FontWeight = rFontWeight,
  284. FontWidth = rFontWidth,
  285. FontItalic = rFontItalic,
  286. Underline = rUnderline,
  287. StrikeThrough = rStrikeThrough,
  288. LineHeight = rLineHeight,
  289. TextColor = rTextColor,
  290. BackgroundColor = rBackgroundColor,
  291. HaloColor = rHaloColor,
  292. HaloWidth = rHaloWidth,
  293. HaloBlur = rHaloBlur,
  294. LetterSpacing = rLetterSpacing,
  295. FontVariant = rFontVariant,
  296. TextDirection = rTextDirection,
  297. ReplacementCharacter = rReplacementCharacter,
  298. };
  299. // Seal it
  300. style.Seal();
  301. // Add to map
  302. _styleMap.Add(key, style);
  303. }
  304. // Set the new current style and return it
  305. return _currentStyle = style;
  306. }
  307. // Check is a style is owned by this style manager
  308. bool IsOwned(IStyle style)
  309. {
  310. return style is StyleManagerStyle sms && sms.Owner == this;
  311. }
  312. /// <summary>
  313. /// Internal wrapper around Style to attach our owner reference check
  314. /// </summary>
  315. class StyleManagerStyle : Style
  316. {
  317. public StyleManager Owner;
  318. }
  319. Dictionary<string, Style> _styleMap = new Dictionary<string, Style>();
  320. Stack<IStyle> _userStack = new Stack<IStyle>();
  321. IStyle _defaultStyle = new Style();
  322. IStyle _currentStyle;
  323. }
  324. }