/*
* Copyright (C) 2013 FooProject
* * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FooEditEngine;
using SharpDX;
using D2D = SharpDX.Direct2D1;
using DW = SharpDX.DirectWrite;
using DXGI = SharpDX.DXGI;
using System.Runtime.InteropServices;
namespace FooEditEngine
{
delegate void PreDrawOneLineHandler(MyTextLayout layout);
delegate void GetDpiHandler(out float dpix,out float dpiy);
///
/// 文字列のアンチエイリアシングモードを指定する
///
public enum TextAntialiasMode
{
///
/// 最適なものが選択されます
///
Default = D2D.TextAntialiasMode.Default,
///
/// ClearTypeでアンチエイリアシングを行います
///
ClearType = D2D.TextAntialiasMode.Cleartype,
///
/// グレイスケールモードでアンチエイリアシングを行います
///
GrayScale = D2D.TextAntialiasMode.Grayscale,
///
/// アンチエイリアシングを行いません
///
Aliased = D2D.TextAntialiasMode.Aliased,
}
sealed class ColorBrushCollection
{
ResourceManager cache = new ResourceManager();
D2D.RenderTarget _render;
public event EventHandler RenderChanged;
public D2D.RenderTarget Render
{
get
{
return this._render;
}
set
{
this._render = value;
if (this.RenderChanged != null)
this.RenderChanged(this, null);
}
}
public D2D.SolidColorBrush Get(Color4 key)
{
if (this.Render == null || this.Render.IsDisposed)
throw new InvalidOperationException();
D2D.SolidColorBrush brush;
bool result = cache.TryGetValue(key, out brush);
if (!result)
{
brush = new D2D.SolidColorBrush(this.Render, key);
cache.Add(key, brush);
}
return brush;
}
public void Clear()
{
cache.Clear();
}
}
sealed class StrokeCollection
{
ResourceManager cache = new ResourceManager();
public D2D.Factory Factory;
public D2D.StrokeStyle Get(HilightType type)
{
if(this.Factory == null || this.Factory.IsDisposed)
throw new InvalidOperationException();
D2D.StrokeStyle stroke;
if (this.cache.TryGetValue(type, out stroke))
return stroke;
D2D.StrokeStyleProperties prop = new D2D.StrokeStyleProperties();
prop.DashCap = D2D.CapStyle.Flat;
prop.DashOffset = 0;
prop.DashStyle = D2D.DashStyle.Solid;
prop.EndCap = D2D.CapStyle.Flat;
prop.LineJoin = D2D.LineJoin.Miter;
prop.MiterLimit = 0;
prop.StartCap = D2D.CapStyle.Flat;
switch (type)
{
case HilightType.Sold:
case HilightType.Url:
case HilightType.Squiggle:
prop.DashStyle = D2D.DashStyle.Solid;
break;
case HilightType.Dash:
prop.DashStyle = D2D.DashStyle.Dash;
break;
case HilightType.DashDot:
prop.DashStyle = D2D.DashStyle.DashDot;
break;
case HilightType.DashDotDot:
prop.DashStyle = D2D.DashStyle.DashDotDot;
break;
case HilightType.Dot:
prop.DashStyle = D2D.DashStyle.Dot;
break;
}
stroke = new D2D.StrokeStyle(this.Factory, prop);
this.cache.Add(type, stroke);
return stroke;
}
public void Clear()
{
cache.Clear();
}
}
sealed class EffectCollection
{
ResourceManager> cache = new ResourceManager>();
public DrawingEffect Get(Color4 color, HilightType type)
{
ResourceManager hilights;
DrawingEffect effect;
if (this.cache.TryGetValue(color, out hilights))
{
if (hilights.TryGetValue(type, out effect))
return effect;
effect = new DrawingEffect(type, color);
hilights.Add(type, effect);
return effect;
}
effect = new DrawingEffect(type, color);
hilights = new ResourceManager();
hilights.Add(type, effect);
this.cache.Add(color, hilights);
return effect;
}
public void Clear()
{
foreach (ResourceManager hilights in this.cache.Values)
hilights.Clear();
cache.Clear();
}
}
class D2DRenderCommon : IDisposable
{
ColorBrushCollection Brushes = new ColorBrushCollection();
StrokeCollection Strokes = new StrokeCollection();
EffectCollection Effects = new EffectCollection();
InlineManager HiddenChars;
TextAntialiasMode _TextAntialiasMode;
Color4 _ControlChar,_Forground,_URL,_Hilight;
DW.Factory DWFactory;
#if METRO
D2D.Factory1 D2DFactory;
#else
D2D.Factory D2DFactory;
#endif
DW.TextFormat format;
D2D.Bitmap bitmap;
D2D.RenderTarget render;
CustomTextRenderer textRender;
int tabLength = 8;
bool hasCache, _ShowLineBreak;
Size renderSize = new Size();
Color4 _Comment, _Literal, _Keyword1, _Keyword2;
public D2DRenderCommon()
{
this.DWFactory = new DW.Factory(DW.FactoryType.Shared);
#if METRO
this.D2DFactory = new D2D.Factory1(D2D.FactoryType.MultiThreaded);
#else
this.D2DFactory = new D2D.Factory(D2D.FactoryType.MultiThreaded);
#endif
this.Strokes.Factory = this.D2DFactory;
this.ChangedRenderResource += (s, e) => { };
this.ChangedRightToLeft += (s, e) => { };
}
public event ChangedRenderResourceEventHandler ChangedRenderResource;
public event EventHandler ChangedRightToLeft;
public const int MiniumeWidth = 40; //これ以上ないと誤操作が起こる
public void InitTextFormat(string fontName, float fontSize, DW.FontWeight fontWeigth = DW.FontWeight.Normal,DW.FontStyle fontStyle = DW.FontStyle.Normal)
{
if(this.format != null)
this.format.Dispose();
float dpix, dpiy;
this.GetDpi(out dpix, out dpiy);
this.format = new DW.TextFormat(this.DWFactory, fontName, fontWeigth, fontStyle, fontSize);
this.format.WordWrapping = DW.WordWrapping.NoWrap;
if (this.HiddenChars == null)
this.HiddenChars = new InlineManager(this.DWFactory, this.format, this.ControlChar, this.Brushes);
else
this.HiddenChars.Format = this.format;
this.TabWidthChar = this.TabWidthChar;
this.hasCache = false;
MyTextLayout layout = new MyTextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue, dpix, false);
layout.RightToLeft = false;
this.emSize = new Size(layout.Width, layout.Height);
layout.Dispose();
layout = new MyTextLayout(this.DWFactory, "+", this.format, float.MaxValue, float.MaxValue, dpix, false);
layout.RightToLeft = false;
#if METRO
this.FoldingWidth = Math.Max(D2DRenderCommon.MiniumeWidth, layout.Width);
#else
this.FoldingWidth = layout.Width;
#endif
layout.Dispose();
this.ChangedRenderResource(this,new ChangedRenderRsourceEventArgs(ResourceType.Font));
}
public bool RightToLeft
{
get
{
return this.format.ReadingDirection == DW.ReadingDirection.RightToLeft;
}
set
{
this.format.ReadingDirection = value ? DW.ReadingDirection.RightToLeft : DW.ReadingDirection.LeftToRight;
this.ChangedRightToLeft(this, null);
}
}
public TextAntialiasMode TextAntialiasMode
{
get
{
return this._TextAntialiasMode;
}
set
{
if (this.render == null)
throw new InvalidOperationException();
this._TextAntialiasMode = value;
this.render.TextAntialiasMode = (D2D.TextAntialiasMode)value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Antialias));
}
}
public bool ShowFullSpace
{
get
{
if (this.HiddenChars == null)
return false;
else
return this.HiddenChars.ContainsSymbol(' ');
}
set
{
if (this.HiddenChars == null)
throw new InvalidOperationException();
if (value)
this.HiddenChars.AddSymbol(' ', '□');
else
this.HiddenChars.RemoveSymbol(' ');
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
}
}
public bool ShowHalfSpace
{
get
{
if (this.HiddenChars == null)
return false;
else
return this.HiddenChars.ContainsSymbol(' ');
}
set
{
if (this.HiddenChars == null)
throw new InvalidOperationException();
if (value)
this.HiddenChars.AddSymbol(' ', 'ロ');
else
this.HiddenChars.RemoveSymbol(' ');
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
}
}
public bool ShowTab
{
get
{
if (this.HiddenChars == null)
return false;
else
return this.HiddenChars.ContainsSymbol('\t');
}
set
{
if (this.HiddenChars == null)
throw new InvalidOperationException();
if (value)
this.HiddenChars.AddSymbol('\t', '>');
else
this.HiddenChars.RemoveSymbol('\t');
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
}
}
public bool ShowLineBreak
{
get
{
return this._ShowLineBreak;
}
set
{
this._ShowLineBreak = value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
}
}
public Color4 Foreground
{
get
{
return this._Forground;
}
set
{
if (this.render == null)
return;
this._Forground = value;
if (this.textRender != null)
this.textRender.DefaultFore = value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
}
}
public Color4 Background
{
get;
set;
}
public Color4 InsertCaret
{
get;
set;
}
public Color4 OverwriteCaret
{
get;
set;
}
public Color4 LineMarker
{
get;
set;
}
public Color4 UpdateArea
{
get;
set;
}
public Color4 LineNumber
{
get;
set;
}
public Color4 ControlChar
{
get
{
return this._ControlChar;
}
set
{
this._ControlChar = value;
if (this.HiddenChars != null)
this.HiddenChars.Fore = value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
}
}
public Color4 Url
{
get
{
return this._URL;
}
set
{
this._URL = value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
}
}
public Color4 Hilight
{
get
{
return this._Hilight;
}
set
{
this._Hilight = value;
}
}
public Color4 Comment
{
get
{
return this._Comment;
}
set
{
this._Comment = value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
}
}
public Color4 Literal
{
get
{
return this._Literal;
}
set
{
this._Literal = value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
}
}
public Color4 Keyword1
{
get
{
return this._Keyword1;
}
set
{
this._Keyword1 = value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
}
}
public Color4 Keyword2
{
get
{
return this._Keyword2;
}
set
{
this._Keyword2 = value;
this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
}
}
public Rectangle TextArea
{
get;
set;
}
public double LineNemberWidth
{
get
{
return this.emSize.Width * EditView.LineNumberLength;
}
}
public double FoldingWidth
{
get;
private set;
}
public int TabWidthChar
{
get { return this.tabLength; }
set
{
if (value == 0)
return;
this.tabLength = value;
DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
float width = (float)(layout.Metrics.Width * value);
this.HiddenChars.TabWidth = width;
this.format.IncrementalTabStop = width;
}
}
public Size emSize
{
get;
private set;
}
public void DrawGripper(Point p, double radius)
{
D2D.Ellipse ellipse = new D2D.Ellipse();
ellipse.Point = p;
ellipse.RadiusX = (float)radius;
ellipse.RadiusY = (float)radius;
this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
}
public void ReConstructDeviceResource()
{
this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height);
}
public void ReConstructDeviceResource(double width, double height)
{
this.DestructDeviceResource();
this.ConstructDeviceResource(width, height);
}
public virtual void DrawCachedBitmap(Rectangle rect)
{
if (this.render == null || this.render.IsDisposed)
return;
render.DrawBitmap(this.bitmap, rect, 1.0f, D2D.BitmapInterpolationMode.Linear, rect);
}
public virtual void CacheContent()
{
if (this.render == null || this.bitmap == null || this.bitmap.IsDisposed || this.render.IsDisposed)
return;
render.Flush();
this.bitmap.CopyFromRenderTarget(this.render, new SharpDX.Point(), new SharpDX.Rectangle(0, 0, (int)this.renderSize.Width, (int)this.renderSize.Height));
this.hasCache = true;
}
public virtual bool IsVaildCache()
{
return this.hasCache;
}
public virtual void BegineDraw()
{
if (this.render == null || this.render.IsDisposed)
return;
this.render.BeginDraw();
this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
}
public virtual void EndDraw()
{
if (this.render == null || this.render.IsDisposed)
return;
this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
this.render.EndDraw();
}
public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground)
{
if (this.render == null || this.render.IsDisposed)
return;
float dpix, dpiy;
D2D.SolidColorBrush brush;
switch (colorType)
{
case StringColorType.Forground:
brush = this.Brushes.Get(this.Foreground);
break;
case StringColorType.LineNumber:
brush = this.Brushes.Get(this.LineNumber);
break;
default:
throw new ArgumentOutOfRangeException();
}
this.GetDpi(out dpix, out dpiy);
MyTextLayout layout = new MyTextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
layout.StringAlignment = align;
layout.Draw(this.render, (float)x, (float)y, brush);
layout.Dispose();
}
public void DrawFoldingMark(bool expand, double x, double y)
{
string mark = expand ? "-" : "+";
this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
}
public void FillRectangle(Rectangle rect,FillRectType type)
{
if (this.render == null || this.render.IsDisposed)
return;
D2D.SolidColorBrush brush = null;
switch(type)
{
case FillRectType.OverwriteCaret:
brush = this.Brushes.Get(this.OverwriteCaret);
this.render.FillRectangle(rect, brush);
break;
case FillRectType.InsertCaret:
brush = this.Brushes.Get(this.InsertCaret);
this.render.FillRectangle(rect, brush);
break;
case FillRectType.InsertPoint:
brush = this.Brushes.Get(this.Hilight);
this.render.FillRectangle(rect, brush);
break;
case FillRectType.LineMarker:
brush = this.Brushes.Get(this.LineMarker);
this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness);
break;
case FillRectType.UpdateArea:
brush = this.Brushes.Get(this.UpdateArea);
this.render.FillRectangle(rect, brush);
break;
}
}
public void FillBackground(Rectangle rect)
{
if (this.render == null || this.render.IsDisposed)
return;
this.render.Clear(this.Background);
}
public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable SelectRanges,PreDrawOneLineHandler PreDrawOneLine)
{
int lineLength = lti.GetLengthFromLineNumber(row);
if (lineLength == 0 || this.render == null || this.render.IsDisposed)
return;
MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
this.render.PushAxisAlignedClip(this.TextArea, D2D.AntialiasMode.Aliased);
if(PreDrawOneLine != null)
PreDrawOneLine(layout);
if (SelectRanges != null)
{
foreach (Selection sel in SelectRanges)
{
if (sel.length == 0 || sel.start == -1)
continue;
this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
}
}
layout.Draw(textRender, (float)x, (float)y);
this.render.PopAxisAlignedClip();
}
public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
{
if (color == null)
return;
layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
}
public void DrawLine(Point from, Point to)
{
D2D.Brush brush = this.Brushes.Get(this.Foreground);
D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold);
this.render.DrawLine(from, to, brush, 1.0f, stroke);
}
public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
{
if (type == HilightType.None)
return;
float thickness = isBold ? 2 : 1;
Color4 color;
if (effectColor != null)
color = (Color4)effectColor;
else if (type == HilightType.Select)
color = this.Hilight;
else
color = this.Foreground;
IMarkerEffecter effecter = null;
D2D.SolidColorBrush brush = this.Brushes.Get(color);
if (type == HilightType.Squiggle)
effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness);
else if (type == HilightType.Select)
effecter = new HilightMarker(this.render, brush);
else if (type == HilightType.None)
effecter = null;
else
effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness);
if (effecter != null)
{
bool isUnderline = type != HilightType.Select;
DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
foreach (DW.HitTestMetrics metric in metrics)
{
float offset = isUnderline ? metric.Height : 0;
effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
}
}
}
public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable MarkerRanges)
{
float dpix,dpiy;
this.GetDpi(out dpix,out dpiy);
bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
MyTextLayout newLayout = new MyTextLayout(this.DWFactory,
str,
this.format,
this.TextArea.Width,
this.TextArea.Height,
dpiy,
hasNewLine && this._ShowLineBreak);
ParseLayout(newLayout, str);
if (syntaxCollection != null)
{
foreach (SyntaxInfo s in syntaxCollection)
{
D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
switch (s.type)
{
case TokenType.Comment:
brush = this.Brushes.Get(this.Comment);
break;
case TokenType.Keyword1:
brush = this.Brushes.Get(this.Keyword1);
break;
case TokenType.Keyword2:
brush = this.Brushes.Get(this.Keyword2);
break;
case TokenType.Literal:
brush = this.Brushes.Get(this.Literal);
break;
}
newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
}
}
if (MarkerRanges != null)
{
foreach (Marker m in MarkerRanges)
{
if (m.start == -1 || m.length == 0)
continue;
Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f);
if (m.hilight == HilightType.Url)
color = this.Url;
newLayout.SetDrawingEffect(this.Effects.Get(color, m.hilight), new DW.TextRange(m.start, m.length));
}
}
return newLayout;
}
public List BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
{
List output = new List();
this.format.WordWrapping = DW.WordWrapping.Wrap;
foreach (string str in doc.GetLines(startIndex, endIndex))
{
DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
int i = startIndex;
foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
{
if (metrics.Length == 0 && metrics.NewlineLength == 0)
continue;
bool lineend = false;
if (metrics.NewlineLength > 0)
lineend = true;
output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
i += Util.RoundUp(metrics.Length);
}
layout.Dispose();
startIndex += str.Length;
}
this.format.WordWrapping = DW.WordWrapping.NoWrap;
if (output.Count > 0)
output.Last().LineEnd = true;
return output;
}
public virtual void Dispose()
{
this.DestructDeviceResource();
this.HiddenChars.Clear();
if (this.format != null)
this.format.Dispose();
if(this.DWFactory != null)
this.DWFactory.Dispose();
if(this.D2DFactory != null)
this.D2DFactory.Dispose();
}
public void ConstructDeviceResource(double width, double height)
{
float dpiX, dpiY;
this.GetDpi(out dpiX, out dpiY);
D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
D2D.RenderTargetType.Default,
new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore),
dpiX,
dpiY,
D2D.RenderTargetUsage.None,
D2D.FeatureLevel.Level_DEFAULT);
this.render = this.ConstructRender(this.D2DFactory,prop,width,height);
this.Brushes.Render = this.render;
D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
bmpProp.DpiX = dpiX;
bmpProp.DpiY = dpiY;
bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore);
this.bitmap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
this.hasCache = false;
this.ConstrctedResource();
this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground);
this.renderSize.Width = width;
this.renderSize.Height = height;
this.TextAntialiasMode = this._TextAntialiasMode;
}
#if METRO
protected virtual D2D.RenderTarget ConstructRender(D2D.Factory1 factory, D2D.RenderTargetProperties prop, double width, double height)
#else
protected virtual D2D.RenderTarget ConstructRender(D2D.Factory factory, D2D.RenderTargetProperties prop, double width, double height)
#endif
{
throw new NotImplementedException();
}
protected virtual void ConstrctedResource()
{
throw new NotImplementedException();
}
public virtual void GetDpi(out float dpix,out float dpiy)
{
throw new NotImplementedException();
}
protected virtual void DestructRender()
{
throw new NotImplementedException();
}
protected virtual void ReCreateTarget()
{
throw new NotImplementedException();
}
public void DestructDeviceResource()
{
this.hasCache = false;
if (this.bitmap != null)
this.bitmap.Dispose();
this.Brushes.Clear();
this.Strokes.Clear();
this.Effects.Clear();
if (this.textRender != null)
this.textRender.Dispose();
this.DestructRender();
}
void ParseLayout(MyTextLayout layout, string str)
{
for (int i = 0; i < str.Length; i++)
{
DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str);
if (inlineObject != null)
{
layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1));
layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1));
}
}
layout.SetLineBreakBrush(this.Brushes.Get(this.ControlChar));
}
}
}