using System;
|
|
using UnityEngine;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
|
|
namespace TMPro
|
|
{
|
|
|
|
/// <summary>
|
|
/// Contains the font asset for the specified font weight styles.
|
|
/// </summary>
|
|
[Serializable]
|
|
public struct TMP_FontWeights
|
|
{
|
|
public TMP_FontAsset regularTypeface;
|
|
public TMP_FontAsset italicTypeface;
|
|
}
|
|
|
|
|
|
[Serializable]
|
|
public class TMP_FontAsset : TMP_Asset
|
|
{
|
|
/// <summary>
|
|
/// Default Font Asset used as last resort when glyphs are missing.
|
|
/// </summary>
|
|
public static TMP_FontAsset defaultFontAsset
|
|
{
|
|
get
|
|
{
|
|
if (s_defaultFontAsset == null)
|
|
{
|
|
s_defaultFontAsset = Resources.Load<TMP_FontAsset>("Fonts & Materials/LiberationSans SDF");
|
|
}
|
|
|
|
return s_defaultFontAsset;
|
|
}
|
|
}
|
|
private static TMP_FontAsset s_defaultFontAsset;
|
|
|
|
|
|
public enum FontAssetTypes { None = 0, SDF = 1, Bitmap = 2 };
|
|
public FontAssetTypes fontAssetType;
|
|
|
|
/// <summary>
|
|
/// The general information about the font.
|
|
/// </summary>
|
|
public FaceInfo fontInfo
|
|
{ get { return m_fontInfo; } }
|
|
|
|
[SerializeField]
|
|
private FaceInfo m_fontInfo;
|
|
|
|
[SerializeField]
|
|
public Texture2D atlas; // Should add a property to make this read-only.
|
|
|
|
|
|
// Glyph Info
|
|
[SerializeField]
|
|
private List<TMP_Glyph> m_glyphInfoList;
|
|
|
|
public Dictionary<int, TMP_Glyph> characterDictionary
|
|
{
|
|
get
|
|
{
|
|
if (m_characterDictionary == null)
|
|
ReadFontDefinition();
|
|
|
|
return m_characterDictionary;
|
|
}
|
|
}
|
|
private Dictionary<int, TMP_Glyph> m_characterDictionary;
|
|
|
|
/// <summary>
|
|
/// Dictionary containing the kerning data
|
|
/// </summary>
|
|
public Dictionary<int, KerningPair> kerningDictionary
|
|
{
|
|
get { return m_kerningDictionary; }
|
|
}
|
|
private Dictionary<int, KerningPair> m_kerningDictionary;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public KerningTable kerningInfo
|
|
{
|
|
get { return m_kerningInfo; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private KerningTable m_kerningInfo;
|
|
|
|
[SerializeField]
|
|
#pragma warning disable 0169 // Property is used to create an empty Kerning Pair in the editor.
|
|
private KerningPair m_kerningPair; // Used for creating a new kerning pair in Editor Panel.
|
|
|
|
/// <summary>
|
|
/// List containing the Fallback font assets for this font.
|
|
/// </summary>
|
|
[SerializeField]
|
|
public List<TMP_FontAsset> fallbackFontAssets;
|
|
|
|
/// <summary>
|
|
/// The settings used in the Font Asset Creator when this font asset was created or edited.
|
|
/// </summary>
|
|
public FontAssetCreationSettings creationSettings
|
|
{
|
|
get { return m_CreationSettings; }
|
|
set { m_CreationSettings = value; }
|
|
}
|
|
[SerializeField]
|
|
public FontAssetCreationSettings m_CreationSettings;
|
|
|
|
// FONT WEIGHTS
|
|
[SerializeField]
|
|
public TMP_FontWeights[] fontWeights = new TMP_FontWeights[10];
|
|
|
|
private int[] m_characterSet; // Array containing all the characters in this font asset.
|
|
|
|
public float normalStyle = 0;
|
|
public float normalSpacingOffset = 0;
|
|
|
|
public float boldStyle = 0.75f;
|
|
public float boldSpacing = 7f;
|
|
public byte italicStyle = 35;
|
|
public byte tabSize = 10;
|
|
|
|
private byte m_oldTabSize;
|
|
|
|
void OnEnable()
|
|
{
|
|
//Debug.Log("OnEnable has been called on " + this.name);
|
|
}
|
|
|
|
|
|
void OnDisable()
|
|
{
|
|
//Debug.Log("TextMeshPro Font Asset [" + this.name + "] has been disabled!");
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
void OnValidate()
|
|
{
|
|
if (m_oldTabSize != tabSize)
|
|
{
|
|
m_oldTabSize = tabSize;
|
|
ReadFontDefinition();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="faceInfo"></param>
|
|
public void AddFaceInfo(FaceInfo faceInfo)
|
|
{
|
|
m_fontInfo = faceInfo;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="glyphInfo"></param>
|
|
public void AddGlyphInfo(TMP_Glyph[] glyphInfo)
|
|
{
|
|
m_glyphInfoList = new List<TMP_Glyph>();
|
|
int characterCount = glyphInfo.Length;
|
|
|
|
m_fontInfo.CharacterCount = characterCount;
|
|
m_characterSet = new int[characterCount];
|
|
|
|
for (int i = 0; i < characterCount; i++)
|
|
{
|
|
TMP_Glyph g = new TMP_Glyph();
|
|
g.id = glyphInfo[i].id;
|
|
g.x = glyphInfo[i].x;
|
|
g.y = glyphInfo[i].y;
|
|
g.width = glyphInfo[i].width;
|
|
g.height = glyphInfo[i].height;
|
|
g.xOffset = glyphInfo[i].xOffset;
|
|
g.yOffset = (glyphInfo[i].yOffset);
|
|
g.xAdvance = glyphInfo[i].xAdvance;
|
|
g.scale = 1;
|
|
|
|
m_glyphInfoList.Add(g);
|
|
|
|
// While iterating through list of glyphs, find the Descender & Ascender for this GlyphSet.
|
|
//m_fontInfo.Ascender = Mathf.Max(m_fontInfo.Ascender, glyphInfo[i].yOffset);
|
|
//m_fontInfo.Descender = Mathf.Min(m_fontInfo.Descender, glyphInfo[i].yOffset - glyphInfo[i].height);
|
|
//Debug.Log(m_fontInfo.Ascender + " " + m_fontInfo.Descender);
|
|
m_characterSet[i] = g.id; // Add Character ID to Array to make it easier to get the kerning pairs.
|
|
}
|
|
|
|
// Sort List by ID.
|
|
m_glyphInfoList = m_glyphInfoList.OrderBy(s => s.id).ToList();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="kerningTable"></param>
|
|
public void AddKerningInfo(KerningTable kerningTable)
|
|
{
|
|
m_kerningInfo = kerningTable;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public void ReadFontDefinition()
|
|
{
|
|
//Debug.Log("Reading Font Definition for " + this.name + ".");
|
|
// Make sure that we have a Font Asset file assigned.
|
|
if (m_fontInfo == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check Font Asset type
|
|
//Debug.Log(name + " " + fontAssetType);
|
|
|
|
// Create new instance of GlyphInfo Dictionary for fast access to glyph info.
|
|
m_characterDictionary = new Dictionary<int, TMP_Glyph>();
|
|
for (int i = 0; i < m_glyphInfoList.Count; i++)
|
|
{
|
|
TMP_Glyph glyph = m_glyphInfoList[i];
|
|
|
|
if (!m_characterDictionary.ContainsKey(glyph.id))
|
|
m_characterDictionary.Add(glyph.id, glyph);
|
|
|
|
// Compatibility
|
|
if (glyph.scale == 0) glyph.scale = 1;
|
|
}
|
|
|
|
|
|
//Debug.Log("PRE: BaseLine:" + m_fontInfo.Baseline + " Ascender:" + m_fontInfo.Ascender + " Descender:" + m_fontInfo.Descender); // + " Centerline:" + m_fontInfo.CenterLine);
|
|
|
|
TMP_Glyph temp_charInfo = new TMP_Glyph();
|
|
|
|
// Add Character (10) LineFeed, (13) Carriage Return & Space (32) to Dictionary if they don't exists.
|
|
if (m_characterDictionary.ContainsKey(32))
|
|
{
|
|
m_characterDictionary[32].width = m_characterDictionary[32].xAdvance; // m_fontInfo.Ascender / 5;
|
|
m_characterDictionary[32].height = m_fontInfo.Ascender - m_fontInfo.Descender;
|
|
m_characterDictionary[32].yOffset= m_fontInfo.Ascender;
|
|
m_characterDictionary[32].scale = 1;
|
|
}
|
|
else
|
|
{
|
|
//Debug.Log("Adding Character 32 (Space) to Dictionary for Font (" + m_fontInfo.Name + ").");
|
|
temp_charInfo = new TMP_Glyph();
|
|
temp_charInfo.id = 32;
|
|
temp_charInfo.x = 0;
|
|
temp_charInfo.y = 0;
|
|
temp_charInfo.width = m_fontInfo.Ascender / 5;
|
|
temp_charInfo.height = m_fontInfo.Ascender - m_fontInfo.Descender;
|
|
temp_charInfo.xOffset = 0;
|
|
temp_charInfo.yOffset = m_fontInfo.Ascender;
|
|
temp_charInfo.xAdvance = m_fontInfo.PointSize / 4;
|
|
temp_charInfo.scale = 1;
|
|
m_characterDictionary.Add(32, temp_charInfo);
|
|
}
|
|
|
|
// Add Non-Breaking Space (160)
|
|
if (!m_characterDictionary.ContainsKey(160))
|
|
{
|
|
temp_charInfo = TMP_Glyph.Clone(m_characterDictionary[32]);
|
|
m_characterDictionary.Add(160, temp_charInfo);
|
|
}
|
|
|
|
// Add Zero Width Space (8203)
|
|
if (!m_characterDictionary.ContainsKey(8203))
|
|
{
|
|
temp_charInfo = TMP_Glyph.Clone(m_characterDictionary[32]);
|
|
temp_charInfo.width = 0;
|
|
temp_charInfo.xAdvance = 0;
|
|
m_characterDictionary.Add(8203, temp_charInfo);
|
|
}
|
|
|
|
//Add Zero Width no-break space (8288)
|
|
if (!m_characterDictionary.ContainsKey(8288))
|
|
{
|
|
temp_charInfo = TMP_Glyph.Clone(m_characterDictionary[32]);
|
|
temp_charInfo.width = 0;
|
|
temp_charInfo.xAdvance = 0;
|
|
m_characterDictionary.Add(8288, temp_charInfo);
|
|
}
|
|
|
|
// Add Linefeed (10)
|
|
if (m_characterDictionary.ContainsKey(10) == false)
|
|
{
|
|
//Debug.Log("Adding Character 10 (Linefeed) to Dictionary for Font (" + m_fontInfo.Name + ").");
|
|
|
|
temp_charInfo = new TMP_Glyph();
|
|
temp_charInfo.id = 10;
|
|
temp_charInfo.x = 0; // m_characterDictionary[32].x;
|
|
temp_charInfo.y = 0; // m_characterDictionary[32].y;
|
|
temp_charInfo.width = 10; // m_characterDictionary[32].width;
|
|
temp_charInfo.height = m_characterDictionary[32].height;
|
|
temp_charInfo.xOffset = 0; // m_characterDictionary[32].xOffset;
|
|
temp_charInfo.yOffset = m_characterDictionary[32].yOffset;
|
|
temp_charInfo.xAdvance = 0;
|
|
temp_charInfo.scale = 1;
|
|
m_characterDictionary.Add(10, temp_charInfo);
|
|
|
|
if (!m_characterDictionary.ContainsKey(13))
|
|
m_characterDictionary.Add(13, temp_charInfo);
|
|
}
|
|
|
|
// Add Tab Character to Dictionary. Tab is Tab Size * Space Character Width.
|
|
if (m_characterDictionary.ContainsKey(9) == false)
|
|
{
|
|
//Debug.Log("Adding Character 9 (Tab) to Dictionary for Font (" + m_fontInfo.Name + ").");
|
|
|
|
temp_charInfo = new TMP_Glyph();
|
|
temp_charInfo.id = 9;
|
|
temp_charInfo.x = m_characterDictionary[32].x;
|
|
temp_charInfo.y = m_characterDictionary[32].y;
|
|
temp_charInfo.width = m_characterDictionary[32].width * tabSize + (m_characterDictionary[32].xAdvance - m_characterDictionary[32].width) * (tabSize - 1);
|
|
temp_charInfo.height = m_characterDictionary[32].height;
|
|
temp_charInfo.xOffset = m_characterDictionary[32].xOffset;
|
|
temp_charInfo.yOffset = m_characterDictionary[32].yOffset;
|
|
temp_charInfo.xAdvance = m_characterDictionary[32].xAdvance * tabSize;
|
|
temp_charInfo.scale = 1;
|
|
m_characterDictionary.Add(9, temp_charInfo);
|
|
}
|
|
|
|
// Centerline is located at the center of character like { or in the middle of the lowercase o.
|
|
//m_fontInfo.CenterLine = m_characterDictionary[111].yOffset - m_characterDictionary[111].height * 0.5f;
|
|
|
|
// Tab Width is using the same xAdvance as space (32).
|
|
m_fontInfo.TabWidth = m_characterDictionary[9].xAdvance;
|
|
|
|
// Set Cap Height
|
|
if (m_fontInfo.CapHeight == 0 && m_characterDictionary.ContainsKey(72))
|
|
m_fontInfo.CapHeight = m_characterDictionary[72].yOffset;
|
|
|
|
// Adjust Font Scale for compatibility reasons
|
|
if (m_fontInfo.Scale == 0)
|
|
m_fontInfo.Scale = 1.0f;
|
|
|
|
// Set Strikethrough Offset (if needed)
|
|
if (m_fontInfo.strikethrough == 0)
|
|
m_fontInfo.strikethrough = m_fontInfo.CapHeight / 2.5f;
|
|
|
|
// Set Padding value for legacy font assets.
|
|
if (m_fontInfo.Padding == 0)
|
|
{
|
|
if (material.HasProperty(ShaderUtilities.ID_GradientScale))
|
|
m_fontInfo.Padding = material.GetFloat(ShaderUtilities.ID_GradientScale) - 1;
|
|
}
|
|
|
|
// Populate Dictionary with Kerning Information
|
|
m_kerningDictionary = new Dictionary<int, KerningPair>();
|
|
List<KerningPair> pairs = m_kerningInfo.kerningPairs;
|
|
|
|
//Debug.Log(m_fontInfo.Name + " has " + pairs.Count + " Kerning Pairs.");
|
|
for (int i = 0; i < pairs.Count; i++)
|
|
{
|
|
KerningPair pair = pairs[i];
|
|
|
|
// Convert legacy kerning data
|
|
if (pair.xOffset != 0)
|
|
pairs[i].ConvertLegacyKerningData();
|
|
|
|
KerningPairKey uniqueKey = new KerningPairKey(pair.firstGlyph, pair.secondGlyph);
|
|
|
|
if (m_kerningDictionary.ContainsKey((int)uniqueKey.key) == false)
|
|
{
|
|
m_kerningDictionary.Add((int)uniqueKey.key, pair);
|
|
}
|
|
else
|
|
{
|
|
if (!TMP_Settings.warningsDisabled)
|
|
Debug.LogWarning("Kerning Key for [" + uniqueKey.ascii_Left + "] and [" + uniqueKey.ascii_Right + "] already exists.");
|
|
}
|
|
}
|
|
|
|
|
|
// Compute Hashcode for the font asset name
|
|
hashCode = TMP_TextUtilities.GetSimpleHashCode(this.name);
|
|
|
|
// Compute Hashcode for the material name
|
|
materialHashCode = TMP_TextUtilities.GetSimpleHashCode(material.name);
|
|
|
|
// Unload font atlas texture
|
|
//ShaderUtilities.GetShaderPropertyIDs();
|
|
//Resources.UnloadAsset(material.GetTexture(ShaderUtilities.ID_MainTex));
|
|
|
|
// Initialize Font Weights if needed
|
|
//InitializeFontWeights();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function to sort the list of glyphs.
|
|
/// </summary>
|
|
public void SortGlyphs()
|
|
{
|
|
if (m_glyphInfoList == null || m_glyphInfoList.Count == 0) return;
|
|
|
|
m_glyphInfoList = m_glyphInfoList.OrderBy(item => item.id).ToList();
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Function to check if a certain character exists in the font asset.
|
|
/// </summary>
|
|
/// <param name="character"></param>
|
|
/// <returns></returns>
|
|
public bool HasCharacter(int character)
|
|
{
|
|
if (m_characterDictionary == null)
|
|
return false;
|
|
|
|
if (m_characterDictionary.ContainsKey(character))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function to check if a certain character exists in the font asset.
|
|
/// </summary>
|
|
/// <param name="character"></param>
|
|
/// <returns></returns>
|
|
public bool HasCharacter(char character)
|
|
{
|
|
if (m_characterDictionary == null)
|
|
return false;
|
|
|
|
if (m_characterDictionary.ContainsKey(character))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function to check if a character is contained in the font asset with the option to also check through fallback font assets.
|
|
/// </summary>
|
|
/// <param name="character"></param>
|
|
/// <param name="searchFallbacks"></param>
|
|
/// <returns></returns>
|
|
public bool HasCharacter(char character, bool searchFallbacks)
|
|
{
|
|
// Read font asset definition if it hasn't already been done.
|
|
if (m_characterDictionary == null)
|
|
{
|
|
ReadFontDefinition();
|
|
|
|
if (m_characterDictionary == null)
|
|
return false;
|
|
}
|
|
|
|
// Check font asset
|
|
if (m_characterDictionary.ContainsKey(character))
|
|
return true;
|
|
|
|
if (searchFallbacks)
|
|
{
|
|
// Check font asset fallbacks
|
|
if (fallbackFontAssets != null && fallbackFontAssets.Count > 0)
|
|
{
|
|
for (int i = 0; i < fallbackFontAssets.Count && fallbackFontAssets[i] != null; i++)
|
|
{
|
|
if (fallbackFontAssets[i].HasCharacter_Internal(character, searchFallbacks))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check general fallback font assets.
|
|
if (TMP_Settings.fallbackFontAssets != null && TMP_Settings.fallbackFontAssets.Count > 0)
|
|
{
|
|
for (int i = 0; i < TMP_Settings.fallbackFontAssets.Count && TMP_Settings.fallbackFontAssets[i] != null; i++)
|
|
{
|
|
if (TMP_Settings.fallbackFontAssets[i].characterDictionary == null)
|
|
TMP_Settings.fallbackFontAssets[i].ReadFontDefinition();
|
|
|
|
if (TMP_Settings.fallbackFontAssets[i].characterDictionary != null && TMP_Settings.fallbackFontAssets[i].HasCharacter_Internal(character, searchFallbacks))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check TMP Settings Default Font Asset
|
|
if (TMP_Settings.defaultFontAsset != null)
|
|
{
|
|
if (TMP_Settings.defaultFontAsset.characterDictionary == null)
|
|
TMP_Settings.defaultFontAsset.ReadFontDefinition();
|
|
|
|
if (TMP_Settings.defaultFontAsset.characterDictionary != null && TMP_Settings.defaultFontAsset.HasCharacter_Internal(character, searchFallbacks))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function to check if a character is contained in a font asset with the option to also check through fallback font assets.
|
|
/// This private implementation does not search the fallback font asset in the TMP Settings file.
|
|
/// </summary>
|
|
/// <param name="character"></param>
|
|
/// <param name="searchFallbacks"></param>
|
|
/// <returns></returns>
|
|
bool HasCharacter_Internal(char character, bool searchFallbacks)
|
|
{
|
|
// Read font asset definition if it hasn't already been done.
|
|
if (m_characterDictionary == null)
|
|
{
|
|
ReadFontDefinition();
|
|
|
|
if (m_characterDictionary == null)
|
|
return false;
|
|
}
|
|
|
|
// Check font asset
|
|
if (m_characterDictionary.ContainsKey(character))
|
|
return true;
|
|
|
|
if (searchFallbacks)
|
|
{
|
|
// Check Font Asset Fallback fonts.
|
|
if (fallbackFontAssets != null && fallbackFontAssets.Count > 0)
|
|
{
|
|
for (int i = 0; i < fallbackFontAssets.Count && fallbackFontAssets[i] != null; i++)
|
|
{
|
|
if (fallbackFontAssets[i].HasCharacter_Internal(character, searchFallbacks))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function to check if certain characters exists in the font asset. Function returns a list of missing characters.
|
|
/// </summary>
|
|
/// <param name="character"></param>
|
|
/// <returns></returns>
|
|
public bool HasCharacters(string text, out List<char> missingCharacters)
|
|
{
|
|
if (m_characterDictionary == null)
|
|
{
|
|
missingCharacters = null;
|
|
return false;
|
|
}
|
|
|
|
missingCharacters = new List<char>();
|
|
|
|
for (int i = 0; i < text.Length; i++)
|
|
{
|
|
if (!m_characterDictionary.ContainsKey(text[i]))
|
|
missingCharacters.Add(text[i]);
|
|
}
|
|
|
|
if (missingCharacters.Count == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function to check if certain characters exists in the font asset. Function returns false if any characters are missing.
|
|
/// </summary>
|
|
/// <param name="text">String containing the characters to check</param>
|
|
/// <returns></returns>
|
|
public bool HasCharacters(string text)
|
|
{
|
|
if (m_characterDictionary == null)
|
|
return false;
|
|
|
|
for (int i = 0; i < text.Length; i++)
|
|
{
|
|
if (!m_characterDictionary.ContainsKey(text[i]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function to extract all the characters from a font asset.
|
|
/// </summary>
|
|
/// <param name="fontAsset"></param>
|
|
/// <returns></returns>
|
|
public static string GetCharacters(TMP_FontAsset fontAsset)
|
|
{
|
|
string characters = string.Empty;
|
|
|
|
for (int i = 0; i < fontAsset.m_glyphInfoList.Count; i++)
|
|
{
|
|
characters += (char)fontAsset.m_glyphInfoList[i].id;
|
|
}
|
|
|
|
return characters;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function which returns an array that contains all the characters from a font asset.
|
|
/// </summary>
|
|
/// <param name="fontAsset"></param>
|
|
/// <returns></returns>
|
|
public static int[] GetCharactersArray(TMP_FontAsset fontAsset)
|
|
{
|
|
int[] characters = new int[fontAsset.m_glyphInfoList.Count];
|
|
|
|
for (int i = 0; i < fontAsset.m_glyphInfoList.Count; i++)
|
|
{
|
|
characters[i] = fontAsset.m_glyphInfoList[i].id;
|
|
}
|
|
|
|
return characters;
|
|
}
|
|
|
|
}
|
|
}
|