// RichTextKit // Copyright © 2019-2020 Topten Software. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this product except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. using SkiaSharp; using System.Collections.Generic; using System.Threading; namespace Topten.RichTextKit { /// /// Helper class for managing RichTextKit styles. /// /// /// The StyleManager can be used to simplify the creation of styles by /// maintaining a current style that incremental changes can be made to. /// /// eg: turn bold on, underline off, change font family etc... /// /// The StyleManager also implements an internal stack to simplify applying /// a particular style and then popping back to the previous style. /// public class StyleManager { /// /// A per-thread style manager /// public static ThreadLocal Default = new ThreadLocal(() => new StyleManager()); /// /// Constructs a new StyleManager /// public StyleManager() { _currentStyle = FromStyle(new Style()); _defaultStyle = _currentStyle; } /// /// The current style /// public IStyle CurrentStyle { get => _currentStyle; set => _currentStyle = FromStyle(value); } /// /// The default style to be be used when Reset is called /// public IStyle DefaultStyle { get => _defaultStyle; set => _defaultStyle = FromStyle(value); } /// /// Get a style that matches all the style attributes of the supplied style /// /// /// This method creates a style owned by this style manager with all the same /// attributes as the passed style. /// /// The style to copy /// public IStyle FromStyle(IStyle value) { // Is it a style we already own? Just re-use it. if (IsOwned(value)) return value; return Update(value.FontFamily, value.FontSize, value.FontWeight, value.FontWidth, value.FontItalic, value.Underline, value.StrikeThrough, value.LineHeight, value.TextColor, value.BackgroundColor, value.HaloColor, value.HaloWidth, value.HaloBlur, value.LetterSpacing, value.FontVariant, value.TextDirection, value.ReplacementCharacter); } /// /// Resets the current style to the default style and resets the internal /// Push/Pop style stack to empty. /// public void Reset() { _currentStyle = _defaultStyle; _userStack.Clear(); } /// /// Saves the current state on an internal stack /// public void Push() { _userStack.Push(_currentStyle); } /// /// Restores the current state on an internal stack /// public void Pop() { _currentStyle = _userStack.Pop(); } /// /// Changes the font family and returns an updated IStyle /// /// The new font family /// An IStyle for the new style public IStyle FontFamily(string fontFamily) => Update(fontFamily: fontFamily); /// /// Changes the font size and returns an updated IStyle /// /// The new font size /// An IStyle for the new style public IStyle FontSize(float fontSize) => Update(fontSize: fontSize); /// /// Changes the font weight and returns an updated IStyle /// /// The new font weight /// An IStyle for the new style public IStyle FontWeight(int fontWeight) => Update(fontWeight: fontWeight); /// /// Changes the font weight and returns an update IStyle (short cut to FontWeight) /// /// The new font weight /// An IStyle for the new style public IStyle Bold(bool bold) => Update(fontWeight: bold ? 700 : 400); /// /// Changes the font width and returns an updated IStyle /// /// The new font width /// An IStyle for the new style public IStyle FontWidth(SKFontStyleWidth fontWidth) => Update(fontWidth: fontWidth); /// /// Changes the font italic setting and returns an updated IStyle /// /// The new font italic setting /// An IStyle for the new style public IStyle FontItalic(bool fontItalic) => Update(fontItalic: fontItalic); /// /// Changes the underline style and returns an updated IStyle /// /// The new underline style /// An IStyle for the new style public IStyle Underline(UnderlineStyle underline) => Update(underline: underline); /// /// Changes the strikethrough style and returns an updated IStyle /// /// The new strikethrough style /// An IStyle for the new style public IStyle StrikeThrough(StrikeThroughStyle strikeThrough) => Update(strikeThrough: strikeThrough); /// /// Changes the line height and returns an updated IStyle /// /// The new line height /// An IStyle for the new style public IStyle LineHeight(float lineHeight) => Update(lineHeight: lineHeight); /// /// Changes the text color and returns an updated IStyle /// /// The new text color /// An IStyle for the new style public IStyle TextColor(SKColor textColor) => Update(textColor: textColor); /// /// Changes the background color and returns an updated IStyle /// /// The new background color /// An IStyle for the new style public IStyle BackgroundColor(SKColor backgroundColor) => Update(backgroundColor: backgroundColor); /// /// Changes the halo color and returns an updated IStyle /// /// The new halo color /// An IStyle for the new style public IStyle HaloColor(SKColor haloColor) => Update(haloColor: haloColor); /// /// Changes the halo width and returns an updated IStyle /// /// The new halo width /// An IStyle for the new style public IStyle HaloWidth(float haloWidth) => Update(haloWidth: haloWidth); /// /// Changes the halo blur width and returns an updated IStyle /// /// The new halo blur width /// An IStyle for the new style public IStyle HaloBlur(float haloBlur) => Update(haloBlur: haloBlur); /// /// Changes the character spacing and returns an updated IStyle /// /// The new character spacing /// An IStyle for the new style public IStyle LetterSpacing(float letterSpacing) => Update(letterSpacing: letterSpacing); /// /// Changes the font variant and returns an updated IStyle /// /// The new font variant /// An IStyle for the new style public IStyle FontVariant(FontVariant fontVariant) => Update(fontVariant: fontVariant); /// /// Changes the text direction and returns an updated IStyle /// /// The new text direction /// An IStyle for the new style public IStyle TextDirection(TextDirection textDirection) => Update(textDirection: textDirection); /// /// Changes the text direction and returns an updated IStyle /// /// The new replacement character /// An IStyle for the new style public IStyle ReplacementCharacter(char character) => Update(replacementCharacter: character); /// /// Update the current style by applying one or more changes to the current /// style. /// /// The new font family /// The new font size /// The new font weight /// The new font width /// The new font italic /// The new underline style /// The new strike-through style /// The new line height /// The new text color /// The new text color /// The new text color /// The new halo width /// The new halo blur width /// The new letterSpacing /// The new font variant /// The new text direction /// The new replacement character /// An IStyle for the new style public IStyle Update( string fontFamily = null, float? fontSize = null, int? fontWeight = null, SKFontStyleWidth? fontWidth = 0, bool? fontItalic = null, UnderlineStyle? underline = null, StrikeThroughStyle? strikeThrough = null, float? lineHeight = null, SKColor? textColor = null, SKColor? backgroundColor = null, SKColor? haloColor = null, float? haloWidth = null, float? haloBlur = null, float? letterSpacing = null, FontVariant? fontVariant = null, TextDirection? textDirection = null, char? replacementCharacter = null ) { // Resolve new style against current style var rFontFamily = fontFamily ?? _currentStyle.FontFamily; var rFontSize = fontSize ?? _currentStyle.FontSize; var rFontWeight = fontWeight ?? _currentStyle.FontWeight; var rFontWidth = fontWidth ?? _currentStyle.FontWidth; var rFontItalic = fontItalic ?? _currentStyle.FontItalic; var rUnderline = underline ?? _currentStyle.Underline; var rStrikeThrough = strikeThrough ?? _currentStyle.StrikeThrough; var rLineHeight = lineHeight ?? _currentStyle.LineHeight; var rTextColor = textColor ?? _currentStyle.TextColor; var rBackgroundColor = backgroundColor ?? _currentStyle.BackgroundColor; var rHaloColor = haloColor ?? _currentStyle.HaloColor; var rHaloWidth = haloWidth ?? _currentStyle.HaloWidth; var rHaloBlur = haloBlur ?? _currentStyle.HaloBlur; var rLetterSpacing = letterSpacing ?? _currentStyle.LetterSpacing; var rFontVariant = fontVariant ?? _currentStyle.FontVariant; var rTextDirection = textDirection ?? _currentStyle.TextDirection; var rReplacementCharacter = replacementCharacter ?? _currentStyle.ReplacementCharacter; // Format key var key = $"{rFontFamily}.{rFontSize}.{rFontWeight}.{fontWidth}.{rFontItalic}.{rUnderline}.{rStrikeThrough}.{rLineHeight}.{rTextColor}.{rBackgroundColor}.{rHaloColor}.{rHaloWidth}.{rHaloBlur}.{rLetterSpacing}.{rFontVariant}.{rTextDirection}.{rReplacementCharacter}"; // Look up... if (!_styleMap.TryGetValue(key, out var style)) { // Create a new style style = new StyleManagerStyle() { Owner = this, FontFamily = rFontFamily, FontSize = rFontSize, FontWeight = rFontWeight, FontWidth = rFontWidth, FontItalic = rFontItalic, Underline = rUnderline, StrikeThrough = rStrikeThrough, LineHeight = rLineHeight, TextColor = rTextColor, BackgroundColor = rBackgroundColor, HaloColor = rHaloColor, HaloWidth = rHaloWidth, HaloBlur = rHaloBlur, LetterSpacing = rLetterSpacing, FontVariant = rFontVariant, TextDirection = rTextDirection, ReplacementCharacter = rReplacementCharacter, }; // Seal it style.Seal(); // Add to map _styleMap.Add(key, style); } // Set the new current style and return it return _currentStyle = style; } // Check is a style is owned by this style manager bool IsOwned(IStyle style) { return style is StyleManagerStyle sms && sms.Owner == this; } /// /// Internal wrapper around Style to attach our owner reference check /// class StyleManagerStyle : Style { public StyleManager Owner; } Dictionary _styleMap = new Dictionary(); Stack _userStack = new Stack(); IStyle _defaultStyle = new Style(); IStyle _currentStyle; } }