using System; using System.Collections.Generic; using System.Text; using System.Xml; using CommonLang; using CommonUI.Cell.Game; using CommonUI.Data; using CommonLang.Log; using CommonLang.Xml; namespace CommonUI.Display.Text { /// <summary> /// 文字字符上的属性 /// </summary> public class TextAttribute : ICloneable { /// <summary> /// 字符颜色 0 表示无特性 (RGBA) /// </summary> public uint fontColor = 0xffffffff; /// <summary> /// 子尺寸 0 表示无特性 /// </summary> public float fontSize; /// <summary> /// 字体名字,空表示无特性 /// </summary> public string fontName; /// <summary> /// 字体 /// </summary> public FontStyle fontStyle = FontStyle.STYLE_PLAIN; public bool underline { get { switch (fontStyle) { case FontStyle.STYLE_BOLD_ITALIC_UNDERLINED: case FontStyle.STYLE_ITALIC_UNDERLINED: case FontStyle.STYLE_BOLD_UNDERLINED: case FontStyle.STYLE_UNDERLINED: return true; } return false; } set { if (!value) { switch (fontStyle) { case FontStyle.STYLE_BOLD_ITALIC_UNDERLINED: fontStyle = FontStyle.STYLE_BOLD_ITALIC; break; case FontStyle.STYLE_ITALIC_UNDERLINED: fontStyle = FontStyle.STYLE_ITALIC; break; case FontStyle.STYLE_BOLD_UNDERLINED: fontStyle = FontStyle.STYLE_BOLD; break; case FontStyle.STYLE_UNDERLINED: fontStyle = FontStyle.STYLE_PLAIN; break; } } else { switch (fontStyle) { case FontStyle.STYLE_BOLD_ITALIC: fontStyle = FontStyle.STYLE_BOLD_ITALIC_UNDERLINED; break; case FontStyle.STYLE_ITALIC: fontStyle = FontStyle.STYLE_ITALIC_UNDERLINED; break; case FontStyle.STYLE_BOLD: fontStyle = FontStyle.STYLE_BOLD_UNDERLINED; break; case FontStyle.STYLE_PLAIN: fontStyle = FontStyle.STYLE_UNDERLINED; break; } } } } /// <summary> /// 描边 /// </summary> public TextBorderCount borderCount = TextBorderCount.Null; /// <summary> /// 描边颜色 (RGBA) /// </summary> public uint borderColor; /// <summary> /// 此字符替换成图片,空表示无特性 /// </summary> public string resImage; /// <summary> /// 图片渲染方式 /// </summary> public ImageZoom resImageZoom; /// <summary> /// 此字符替换成动画,空表示无特性 /// </summary> public string resSprite; /// <summary> /// 标记此处可以被点击触发事件,空表示无特性 /// </summary> public string link; /// <summary> /// 行对齐方式 /// </summary> public RichTextAlignment anchor = RichTextAlignment.taNA; /// <summary> /// 扩展的自定义渲染部分 /// </summary> public TextDrawable drawable; public TextAttribute( uint fColor = 0, float fSize = 0, string fName = null, FontStyle fStyle = FontStyle.STYLE_PLAIN, RichTextAlignment ta = RichTextAlignment.taNA, TextBorderCount bCount = TextBorderCount.Null, uint bColor = 0, string rImage = null, string rSprite = null, string pLink = null, ImageZoom rImageZoom = null, TextDrawable drawable = null) { this.fontColor = fColor; this.fontSize = fSize; this.fontName = fName; this.fontStyle = fStyle; this.anchor = ta; this.resImage = rImage; this.resImageZoom = rImageZoom; this.resSprite = rSprite; this.borderCount = bCount; this.borderColor = bColor; this.link = pLink; this.drawable = drawable; } public TextAttribute(TextAttribute other) { this.fontColor = other.fontColor; this.fontSize = other.fontSize; this.fontName = other.fontName; this.fontStyle = other.fontStyle; this.anchor = other.anchor; this.borderCount = other.borderCount; this.borderColor = other.borderColor; this.resImage = other.resImage; this.resImageZoom = CUtils.TryClone(other.resImageZoom); this.resSprite = other.resSprite; this.link = other.link; this.drawable = other.drawable; } public object Clone() { return new TextAttribute(this); } public override bool Equals(object obj) { if (obj is TextAttribute) { return this.Equals(obj as TextAttribute); } return false; } public override int GetHashCode() { return base.GetHashCode(); } public bool Equals(TextAttribute other) { if (other == this) return true; if (other == null) return false; if (this.fontColor != other.fontColor) return false; if (this.fontSize != other.fontSize) return false; if (!string.Equals(this.fontName, other.fontName)) return false; if (this.fontStyle != other.fontStyle) return false; if (this.anchor != other.anchor) return false; if (this.borderCount != other.borderCount) return false; if (this.borderColor != other.borderColor) return false; if (!string.Equals(this.resImage, other.resImage)) return false; if (!string.Equals(this.resSprite, other.resSprite)) return false; if (!ImageZoom.Equals(this.resImageZoom, other.resImageZoom)) return false; if (!string.Equals(this.link, other.link)) return false; if (drawable != null && !drawable.Equals(other.drawable)) return false; if (drawable == null && other.drawable != null) { return false; } return true; } public bool IsValid() { if (fontColor != 0) return true; if (fontSize != 0) return true; if (!string.IsNullOrEmpty(fontName)) return true; if (anchor != RichTextAlignment.taNA) return true; if (borderCount > 0) return true; if (borderColor != 0) return true; if (!string.IsNullOrEmpty(resImage)) return true; if (!string.IsNullOrEmpty(resSprite)) return true; if (resImageZoom != null) return true; if (!string.IsNullOrEmpty(link)) return true; if (drawable != null) return true; return false; } public void Combine(TextAttribute other, bool isCover = true) { if (other.fontColor != 0) { this.fontColor = other.fontColor; } if (other.fontSize != 0) { this.fontSize = other.fontSize; } if (!string.IsNullOrEmpty(other.fontName)) { this.fontName = other.fontName; } if (other.fontStyle != 0) { this.fontStyle = other.fontStyle; } if (other.anchor != RichTextAlignment.taNA) { this.anchor = other.anchor; } if (other.borderCount > 0) { this.borderCount = other.borderCount; } if (other.borderColor != 0) { this.borderColor = other.borderColor; } if (!string.IsNullOrEmpty(other.resImage)) { this.resImage = other.resImage; } if (!string.IsNullOrEmpty(other.resSprite)) { this.resSprite = other.resSprite; } if (other.resImageZoom != null) { this.resImageZoom = CUtils.TryClone(other.resImageZoom); } if (other.drawable != null) { this.drawable = CUtils.TryClone(other.drawable); } if (isCover && !string.IsNullOrEmpty(other.link)) { this.link = other.link; } else if (!isCover) { this.link += other.link; } } public static TextAttribute Combine(TextAttribute src, TextAttribute dst) { TextAttribute ret = new TextAttribute(src); ret.Combine(dst); return ret; } } //------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------ public class ImageZoomParser : CommonLang.Parser.ParserAdapter { public ImageZoomParser() : base(typeof(ImageZoom)) { } public override object StringToObject(string text) { return ImageZoom.FromString(text); } public override string ObjectToString(object obj) { return ImageZoom.ToString(obj as ImageZoom); } } public class ImageZoom : ICloneable { static ImageZoom() { Parser.RegistParser(new ImageZoomParser()); } public enum ImageFill { Clamp, Repeat, } public ImageFill Filling = ImageFill.Clamp; public float Width; public float Height; public object Clone() { ImageZoom ret = new ImageZoom(); ret.Filling = this.Filling; ret.Width = this.Width; ret.Height = this.Height; return ret; } public static bool Equals(ImageZoom a, ImageZoom b) { if (a != null && b != null) { if (a.Filling != b.Filling) return false; if (a.Width != b.Width) return false; if (a.Height != b.Height) return false; return true; } return a == b; } public override string ToString() { return ToString(this); } public static ImageZoom FromString(string text) { string[] kvs = text.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (kvs.Length >= 2) { ImageZoom ret = new ImageZoom(); ret.Width = float.Parse(kvs[0]); ret.Height = float.Parse(kvs[1]); if (kvs.Length >= 3) { ret.Filling = (ImageFill)Enum.Parse(typeof(ImageFill), kvs[2], true); } return ret; } return null; } public static string ToString(ImageZoom obj) { if (obj != null) { return string.Format("{0},{1},{2}", obj.Width, obj.Height, obj.Filling); } return null; } } //------------------------------------------------------------------------------------------------------------------------ ///////////////////////////////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// 含有属性的文字 /// </summary> public class AttributedString : ICloneable { private string text = ""; private List<TextAttribute> attributes = new List<TextAttribute>(); public AttributedString() { } public AttributedString(string other, TextAttribute ta) { Append(other, ta); } public object Clone() { AttributedString ret = new AttributedString(); ret.text = this.text; ret.attributes = CUtils.CloneList<TextAttribute>(this.attributes); return ret; } public override bool Equals(object obj) { if (obj is AttributedString) { AttributedString other = obj as AttributedString; if (other.text.Equals(this.text)) { for (int i = text.Length - 1; i >= 0; --i) { if (!other.attributes[i].Equals(this.attributes[i])) { return false; } } return true; } } return false; } public override int GetHashCode() { return base.GetHashCode(); } public bool SetAttribute(int index, int len, TextAttribute ta) { if (index + len <= attributes.Count) { int end = index + len; for (int i = index; i < end; ++i) { attributes[i] = ta; } return true; } return false; } public AttributedString AddAttribute(TextAttribute attribute, bool isCover = true) { return AddAttribute(attribute, 0, text.Length, isCover); } public AttributedString AddAttribute(TextAttribute attribute, int beginIndex, int count, bool isCover = true) { int endIndex = beginIndex + count; for (int i = beginIndex; i < endIndex; ++i) { attributes[i].Combine(attribute, isCover); } return this; } public AttributedString Append(AttributedString other) { if (other != null && !string.IsNullOrEmpty(other.text)) { text += other.text; for (int i = 0; i < other.text.Length; ++i) { attributes.Add(other.attributes[i]); } } return this; } public AttributedString Append(string other) { if (attributes.Count > 0) { TextAttribute lastAttr = attributes[attributes.Count - 1]; Append(other, lastAttr); } else { Append(other, new TextAttribute(Color.COLOR_WHITE, 12)); } return this; } public AttributedString Append(string other, TextAttribute ta) { if (!string.IsNullOrEmpty(other)) { text += other; for (int i = 0; i < other.Length; ++i) { attributes.Add(ta); } } return this; } public AttributedString DeleteString(int beginIndex, int count) { text = text.Remove(beginIndex, count); attributes.RemoveRange(beginIndex, count); return this; } public AttributedString ClearString() { text = ""; attributes.Clear(); return this; } public int Length { get { return text.Length; } } override public string ToString() { return text; } public char GetChar(int index) { if (index < text.Length) { return text[index]; } return default(char); } public TextAttribute GetAttribute(int index) { if (index < attributes.Count) { return attributes[index]; } return null; } } //------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------ public class AttributedStringDecoder { protected Logger log = LoggerFactory.GetLogger("AttributedStringDecoder"); public const string TEXT_XML_KEY_COLOR = "color"; public const string TEXT_XML_KEY_SIZE = "size"; public const string TEXT_XML_KEY_FACE = "face"; public const string TEXT_XML_KEY_STYLE = "style"; public const string TEXT_XML_KEY_B_COUNT = "border"; public const string TEXT_XML_KEY_B_COLOR = "bcolor"; public const string TEXT_XML_KEY_RES_IMG = "img"; /// <summary> /// @"宽,高" /// img_zoom = "width,height" /// </summary> public const string TEXT_XML_KEY_RES_IMAGE_ZOOM = "img_zoom"; /// <summary> /// @"资源名,精灵名,动画ID" /// spr = "res/sprite.xml,sprite_name,anim" /// </summary> public const string TEXT_XML_KEY_RES_SPR = "spr"; public const string TEXT_XML_KEY_LINK = "link"; public const string TEXT_XML_KEY_LINE_ANCHOR = "anchor"; public const string TEXT_XML_NODE_BREAK = "br"; public const string TEXT_XML_NODE_SPACE = "p"; public virtual AttributedString CreateFromXML(XmlDocument xml, TextAttribute defaultTA = null) { AttributedString attr = new AttributedString(); TextAttribute curAttr = (defaultTA != null) ? defaultTA : new TextAttribute(Color.COLOR_WHITE, 16); try { internalBuildXML(attr, xml.DocumentElement, curAttr); } catch (Exception err) { log.Error(err.Message, err); } return attr; } public virtual AttributedString CreateFromXML(string text, TextAttribute defaultTA = null) { XmlDocument xml = XmlUtil.FromString(text); return CreateFromXML(xml, defaultTA); } protected virtual void DecodeAttribute(XmlElement node, XmlAttribute x_attr, TextAttribute attr) { switch (x_attr.Name) { case TEXT_XML_KEY_COLOR: { string kv = node.GetAttribute(TEXT_XML_KEY_COLOR); uint argb = uint.Parse(kv, System.Globalization.NumberStyles.HexNumber); attr.fontColor = Color.toRGBA(argb); } break; case TEXT_XML_KEY_SIZE: { attr.fontSize = int.Parse(node.GetAttribute(TEXT_XML_KEY_SIZE)); } break; case TEXT_XML_KEY_STYLE: { string style = node.GetAttribute(TEXT_XML_KEY_STYLE); int value; if (int.TryParse(style, out value)) { attr.fontStyle = (FontStyle)value; } else { attr.fontStyle = (FontStyle) Enum.Parse(typeof(FontStyle), style); } } break; case TEXT_XML_KEY_FACE: { attr.fontName = node.GetAttribute(TEXT_XML_KEY_FACE); } break; case TEXT_XML_KEY_B_COUNT: { attr.borderCount = (Data.TextBorderCount)int.Parse(node.GetAttribute(TEXT_XML_KEY_B_COUNT)); } break; case TEXT_XML_KEY_B_COLOR: { string kv = node.GetAttribute(TEXT_XML_KEY_B_COLOR); uint argb = uint.Parse(kv, System.Globalization.NumberStyles.HexNumber); attr.borderColor = Color.toRGBA(argb); } break; case TEXT_XML_KEY_RES_IMG: { attr.resImage = node.GetAttribute(TEXT_XML_KEY_RES_IMG); } break; case TEXT_XML_KEY_RES_SPR: { attr.resSprite = node.GetAttribute(TEXT_XML_KEY_RES_SPR); } break; case TEXT_XML_KEY_RES_IMAGE_ZOOM: { attr.resImageZoom = ImageZoom.FromString(node.GetAttribute(TEXT_XML_KEY_RES_IMAGE_ZOOM)); } break; case TEXT_XML_KEY_LINK: { attr.link = node.GetAttribute(TEXT_XML_KEY_LINK); } break; case TEXT_XML_KEY_LINE_ANCHOR: { string value = node.GetAttribute(TEXT_XML_KEY_LINE_ANCHOR); try { attr.anchor = (RichTextAlignment)Enum.Parse(typeof(RichTextAlignment), value, true); } catch (Exception err) { log.Error(err.Message, err); } } break; } TextDrawable drawable = ITextDrawableFactory.Instance.CreateTextDrawable(x_attr.Name, x_attr.Value); if (drawable != null) { attr.drawable = drawable; } } private void internalBuildXML(AttributedString atext, XmlElement node, TextAttribute parentAttr) { string nname = node.Name; TextAttribute attr = new TextAttribute(); foreach (XmlAttribute x_attr in node.Attributes) { DecodeAttribute(node, x_attr, attr); } attr = TextAttribute.Combine(parentAttr, attr); if (!attr.IsValid()) { attr = parentAttr; } if (node.ChildNodes.Count > 0) { foreach (XmlNode e in node.ChildNodes) { if (e is XmlText || e is XmlCDataSection) { atext.Append(e.Value, attr); } else if (e is XmlElement) { internalBuildXML(atext, e as XmlElement, attr); } } } if (string.Equals(nname, TEXT_XML_NODE_BREAK)) { atext.Append("\n", attr); } else if (string.Equals(nname, TEXT_XML_NODE_SPACE)) { atext.Append(" ", attr); } } } }