// 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;
}
}