2 * Copyright (C) 2013 FooProject
3 * * 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
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
12 using System.Collections.Generic;
17 using D2D = SharpDX.Direct2D1;
18 using DW = SharpDX.DirectWrite;
19 using DXGI = SharpDX.DXGI;
20 using System.Runtime.InteropServices;
22 namespace FooEditEngine
24 delegate void PreDrawOneLineHandler(MyTextLayout layout);
26 delegate void GetDpiHandler(out float dpix,out float dpiy);
29 /// 文字列のアンチエイリアシングモードを指定する
31 public enum TextAntialiasMode
36 Default = D2D.TextAntialiasMode.Default,
38 /// ClearTypeでアンチエイリアシングを行います
40 ClearType = D2D.TextAntialiasMode.Cleartype,
42 /// グレイスケールモードでアンチエイリアシングを行います
44 GrayScale = D2D.TextAntialiasMode.Grayscale,
48 Aliased = D2D.TextAntialiasMode.Aliased,
51 sealed class ColorBrushCollection
53 ResourceManager<Color4, D2D.SolidColorBrush> cache = new ResourceManager<Color4, D2D.SolidColorBrush>();
54 D2D.RenderTarget _render;
56 public event EventHandler RenderChanged;
58 public D2D.RenderTarget Render
67 if (this.RenderChanged != null)
68 this.RenderChanged(this, null);
72 public D2D.SolidColorBrush Get(Color4 key)
74 if (this.Render == null || this.Render.IsDisposed)
75 throw new InvalidOperationException();
76 D2D.SolidColorBrush brush;
77 bool result = cache.TryGetValue(key, out brush);
80 brush = new D2D.SolidColorBrush(this.Render, key);
81 cache.Add(key, brush);
93 sealed class StrokeCollection
95 ResourceManager<HilightType, D2D.StrokeStyle> cache = new ResourceManager<HilightType, D2D.StrokeStyle>();
97 public D2D.Factory Factory;
99 public D2D.StrokeStyle Get(HilightType type)
101 if(this.Factory == null || this.Factory.IsDisposed)
102 throw new InvalidOperationException();
103 D2D.StrokeStyle stroke;
104 if (this.cache.TryGetValue(type, out stroke))
107 D2D.StrokeStyleProperties prop = new D2D.StrokeStyleProperties();
108 prop.DashCap = D2D.CapStyle.Flat;
110 prop.DashStyle = D2D.DashStyle.Solid;
111 prop.EndCap = D2D.CapStyle.Flat;
112 prop.LineJoin = D2D.LineJoin.Miter;
114 prop.StartCap = D2D.CapStyle.Flat;
117 case HilightType.Sold:
118 case HilightType.Url:
119 case HilightType.Squiggle:
120 prop.DashStyle = D2D.DashStyle.Solid;
122 case HilightType.Dash:
123 prop.DashStyle = D2D.DashStyle.Dash;
125 case HilightType.DashDot:
126 prop.DashStyle = D2D.DashStyle.DashDot;
128 case HilightType.DashDotDot:
129 prop.DashStyle = D2D.DashStyle.DashDotDot;
131 case HilightType.Dot:
132 prop.DashStyle = D2D.DashStyle.Dot;
135 stroke = new D2D.StrokeStyle(this.Factory, prop);
136 this.cache.Add(type, stroke);
145 sealed class EffectCollection
147 ResourceManager<Color4, ResourceManager<HilightType, DrawingEffect>> cache = new ResourceManager<Color4, ResourceManager<HilightType, DrawingEffect>>();
148 public DrawingEffect Get(Color4 color, HilightType type)
150 ResourceManager<HilightType, DrawingEffect> hilights;
151 DrawingEffect effect;
152 if (this.cache.TryGetValue(color, out hilights))
154 if (hilights.TryGetValue(type, out effect))
156 effect = new DrawingEffect(type, color);
157 hilights.Add(type, effect);
160 effect = new DrawingEffect(type, color);
161 hilights = new ResourceManager<HilightType, DrawingEffect>();
162 hilights.Add(type, effect);
163 this.cache.Add(color, hilights);
168 foreach (ResourceManager<HilightType, DrawingEffect> hilights in this.cache.Values)
174 class D2DRenderCommon : IDisposable
176 ColorBrushCollection Brushes = new ColorBrushCollection();
177 StrokeCollection Strokes = new StrokeCollection();
178 EffectCollection Effects = new EffectCollection();
179 InlineManager HiddenChars;
180 TextAntialiasMode _TextAntialiasMode;
181 Color4 _ControlChar,_Forground,_URL,_Hilight;
182 DW.Factory DWFactory;
184 D2D.Factory1 D2DFactory;
186 D2D.Factory D2DFactory;
188 DW.TextFormat format;
189 protected D2D.Bitmap CachedBitmap;
190 D2D.RenderTarget render;
191 CustomTextRenderer textRender;
194 Color4 _Comment, _Literal, _Keyword1, _Keyword2;
195 protected bool hasCache;
197 protected Size renderSize { get; private set; }
199 public D2DRenderCommon()
201 this.DWFactory = new DW.Factory(DW.FactoryType.Shared);
203 this.D2DFactory = new D2D.Factory1(D2D.FactoryType.MultiThreaded);
205 this.D2DFactory = new D2D.Factory(D2D.FactoryType.MultiThreaded);
207 this.Strokes.Factory = this.D2DFactory;
208 this.ChangedRenderResource += (s, e) => { };
209 this.ChangedRightToLeft += (s, e) => { };
210 this.renderSize = new Size();
213 public event ChangedRenderResourceEventHandler ChangedRenderResource;
215 public event EventHandler ChangedRightToLeft;
217 public const int MiniumeWidth = 40; //これ以上ないと誤操作が起こる
219 public double GetScale()
222 this.GetDpi(out dpi, out dpi);
226 public void InitTextFormat(string fontName, float fontSize, DW.FontWeight fontWeigth = DW.FontWeight.Normal,DW.FontStyle fontStyle = DW.FontStyle.Normal)
228 if(this.format != null)
229 this.format.Dispose();
232 this.GetDpi(out dpix, out dpiy);
234 this.format = new DW.TextFormat(this.DWFactory, fontName, fontWeigth, fontStyle, fontSize);
235 this.format.WordWrapping = DW.WordWrapping.NoWrap;
237 if (this.HiddenChars == null)
238 this.HiddenChars = new InlineManager(this.DWFactory, this.format, this.ControlChar, this.Brushes);
240 this.HiddenChars.Format = this.format;
242 this.TabWidthChar = this.TabWidthChar;
244 this.hasCache = false;
246 MyTextLayout layout = new MyTextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue, dpix, false);
247 layout.RightToLeft = false;
248 this.emSize = new Size(layout.Width, layout.Height);
251 layout = new MyTextLayout(this.DWFactory, "+", this.format, float.MaxValue, float.MaxValue, dpix, false);
252 layout.RightToLeft = false;
254 this.FoldingWidth = Math.Max(D2DRenderCommon.MiniumeWidth, layout.Width);
256 this.FoldingWidth = layout.Width;
260 this.ChangedRenderResource(this,new ChangedRenderRsourceEventArgs(ResourceType.Font));
263 public bool RightToLeft
267 return this.format.ReadingDirection == DW.ReadingDirection.RightToLeft;
271 this.format.ReadingDirection = value ? DW.ReadingDirection.RightToLeft : DW.ReadingDirection.LeftToRight;
272 this.ChangedRightToLeft(this, null);
276 public TextAntialiasMode TextAntialiasMode
280 return this._TextAntialiasMode;
284 if (this.render == null)
285 throw new InvalidOperationException();
286 this._TextAntialiasMode = value;
287 this.render.TextAntialiasMode = (D2D.TextAntialiasMode)value;
288 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Antialias));
292 public bool ShowFullSpace
296 if (this.HiddenChars == null)
299 return this.HiddenChars.ContainsSymbol(' ');
303 if (this.HiddenChars == null)
304 throw new InvalidOperationException();
306 this.HiddenChars.AddSymbol(' ', '□');
308 this.HiddenChars.RemoveSymbol(' ');
312 public bool ShowHalfSpace
316 if (this.HiddenChars == null)
319 return this.HiddenChars.ContainsSymbol(' ');
323 if (this.HiddenChars == null)
324 throw new InvalidOperationException();
326 this.HiddenChars.AddSymbol(' ', 'ロ');
328 this.HiddenChars.RemoveSymbol(' ');
336 if (this.HiddenChars == null)
339 return this.HiddenChars.ContainsSymbol('\t');
343 if (this.HiddenChars == null)
344 throw new InvalidOperationException();
346 this.HiddenChars.AddSymbol('\t', '>');
348 this.HiddenChars.RemoveSymbol('\t');
352 public bool ShowLineBreak
356 return this._ShowLineBreak;
360 this._ShowLineBreak = value;
364 public Color4 Foreground
368 return this._Forground;
372 if (this.render == null)
374 this._Forground = value;
375 if (this.textRender != null)
376 this.textRender.DefaultFore = value;
377 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
381 public Color4 Background
387 public Color4 InsertCaret
393 public Color4 OverwriteCaret
399 public Color4 LineMarker
405 public Color4 UpdateArea
411 public Color4 LineNumber
417 public Color4 ControlChar
421 return this._ControlChar;
425 this._ControlChar = value;
426 if (this.HiddenChars != null)
427 this.HiddenChars.Fore = value;
428 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
441 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
445 public Color4 Hilight
449 return this._Hilight;
453 this._Hilight = value;
457 public Color4 Comment
461 return this._Comment;
465 this._Comment = value;
466 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
470 public Color4 Literal
474 return this._Literal;
478 this._Literal = value;
479 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
483 public Color4 Keyword1
487 return this._Keyword1;
491 this._Keyword1 = value;
492 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
496 public Color4 Keyword2
500 return this._Keyword2;
504 this._Keyword2 = value;
505 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
509 public Rectangle TextArea
515 public double LineNemberWidth
519 return this.emSize.Width * EditView.LineNumberLength;
523 public double FoldingWidth
529 public int TabWidthChar
531 get { return this.tabLength; }
536 this.tabLength = value;
537 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
538 float width = (float)(layout.Metrics.Width * value);
539 this.HiddenChars.TabWidth = width;
540 this.format.IncrementalTabStop = width;
550 public void DrawGripper(Point p, double radius)
552 D2D.Ellipse ellipse = new D2D.Ellipse();
554 ellipse.RadiusX = (float)radius;
555 ellipse.RadiusY = (float)radius;
556 this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
557 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
560 public void ReConstructDeviceResource()
562 this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height);
565 public void ReConstructDeviceResource(double width, double height)
567 this.DestructDeviceResource();
568 this.ConstructDeviceResource(width, height);
570 public virtual void DrawCachedBitmap(Rectangle rect)
574 public virtual void CacheContent()
578 public virtual bool IsVaildCache()
580 return this.hasCache;
583 public virtual void BegineDraw()
585 if (this.render == null || this.render.IsDisposed)
587 this.render.BeginDraw();
588 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
591 public virtual void EndDraw()
593 if (this.render == null || this.render.IsDisposed)
595 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
596 this.render.EndDraw();
599 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground)
601 if (this.render == null || this.render.IsDisposed)
604 D2D.SolidColorBrush brush;
607 case StringColorType.Forground:
608 brush = this.Brushes.Get(this.Foreground);
610 case StringColorType.LineNumber:
611 brush = this.Brushes.Get(this.LineNumber);
614 throw new ArgumentOutOfRangeException();
616 this.GetDpi(out dpix, out dpiy);
617 MyTextLayout layout = new MyTextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
618 layout.StringAlignment = align;
619 layout.Draw(this.render, (float)x, (float)y, brush);
623 public void DrawFoldingMark(bool expand, double x, double y)
625 string mark = expand ? "-" : "+";
626 this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
629 public void FillRectangle(Rectangle rect,FillRectType type)
631 if (this.render == null || this.render.IsDisposed)
633 D2D.SolidColorBrush brush = null;
636 case FillRectType.OverwriteCaret:
637 brush = this.Brushes.Get(this.OverwriteCaret);
638 this.render.FillRectangle(rect, brush);
640 case FillRectType.InsertCaret:
641 brush = this.Brushes.Get(this.InsertCaret);
642 this.render.FillRectangle(rect, brush);
644 case FillRectType.InsertPoint:
645 brush = this.Brushes.Get(this.Hilight);
646 this.render.FillRectangle(rect, brush);
648 case FillRectType.LineMarker:
649 brush = this.Brushes.Get(this.LineMarker);
650 this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness);
652 case FillRectType.UpdateArea:
653 brush = this.Brushes.Get(this.UpdateArea);
654 this.render.FillRectangle(rect, brush);
659 public void FillBackground(Rectangle rect)
661 if (this.render == null || this.render.IsDisposed)
663 this.render.Clear(this.Background);
666 public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable<Selection> SelectRanges,PreDrawOneLineHandler PreDrawOneLine)
668 int lineLength = lti.GetLengthFromLineNumber(row);
670 if (lineLength == 0 || this.render == null || this.render.IsDisposed)
673 MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
675 this.render.PushAxisAlignedClip(this.TextArea, D2D.AntialiasMode.Aliased);
677 if(PreDrawOneLine != null)
678 PreDrawOneLine(layout);
680 if (SelectRanges != null)
682 foreach (Selection sel in SelectRanges)
684 if (sel.length == 0 || sel.start == -1)
687 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
691 layout.Draw(textRender, (float)x, (float)y);
693 this.render.PopAxisAlignedClip();
696 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
700 layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
703 public void DrawLine(Point from, Point to)
705 D2D.Brush brush = this.Brushes.Get(this.Foreground);
706 D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold);
707 this.render.DrawLine(from, to, brush, 1.0f, stroke);
710 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
712 if (type == HilightType.None)
715 float thickness = isBold ? 2 : 1;
718 if (effectColor != null)
719 color = (Color4)effectColor;
720 else if (type == HilightType.Select)
721 color = this.Hilight;
723 color = this.Foreground;
725 IMarkerEffecter effecter = null;
726 D2D.SolidColorBrush brush = this.Brushes.Get(color);
728 if (type == HilightType.Squiggle)
729 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness);
730 else if (type == HilightType.Select)
731 effecter = new HilightMarker(this.render, brush);
732 else if (type == HilightType.None)
735 effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness);
737 if (effecter != null)
739 bool isUnderline = type != HilightType.Select;
741 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
742 foreach (DW.HitTestMetrics metric in metrics)
744 float offset = isUnderline ? metric.Height : 0;
745 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
750 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges)
753 this.GetDpi(out dpix,out dpiy);
755 bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
756 MyTextLayout newLayout = new MyTextLayout(this.DWFactory,
760 this.TextArea.Height,
762 hasNewLine && this._ShowLineBreak);
763 ParseLayout(newLayout, str);
764 if (syntaxCollection != null)
766 foreach (SyntaxInfo s in syntaxCollection)
768 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
771 case TokenType.Comment:
772 brush = this.Brushes.Get(this.Comment);
774 case TokenType.Keyword1:
775 brush = this.Brushes.Get(this.Keyword1);
777 case TokenType.Keyword2:
778 brush = this.Brushes.Get(this.Keyword2);
780 case TokenType.Literal:
781 brush = this.Brushes.Get(this.Literal);
784 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
788 if (MarkerRanges != null)
790 foreach (Marker m in MarkerRanges)
792 if (m.start == -1 || m.length == 0)
794 Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f);
795 if (m.hilight == HilightType.Url)
797 newLayout.SetDrawingEffect(this.Effects.Get(color, m.hilight), new DW.TextRange(m.start, m.length));
804 public List<LineToIndexTableData> BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
806 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
808 this.format.WordWrapping = DW.WordWrapping.Wrap;
810 foreach (string str in doc.GetLines(startIndex, endIndex))
812 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
815 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
817 if (metrics.Length == 0 && metrics.NewlineLength == 0)
820 bool lineend = false;
821 if (metrics.NewlineLength > 0)
824 output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
825 i += Util.RoundUp(metrics.Length);
830 startIndex += str.Length;
833 this.format.WordWrapping = DW.WordWrapping.NoWrap;
835 if (output.Count > 0)
836 output.Last().LineEnd = true;
841 public virtual void Dispose()
843 this.DestructDeviceResource();
844 this.HiddenChars.Clear();
845 if (this.format != null)
846 this.format.Dispose();
847 if(this.DWFactory != null)
848 this.DWFactory.Dispose();
849 if(this.D2DFactory != null)
850 this.D2DFactory.Dispose();
853 public void ConstructDeviceResource(double width, double height)
856 this.GetDpi(out dpiX, out dpiY);
857 D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
858 D2D.RenderTargetType.Default,
859 new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Premultiplied),
862 D2D.RenderTargetUsage.None,
863 D2D.FeatureLevel.Level_DEFAULT);
865 this.render = this.ConstructRender(this.D2DFactory,prop,width,height);
867 this.Brushes.Render = this.render;
869 D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
872 bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Premultiplied);
873 this.CachedBitmap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
874 this.hasCache = false;
876 this.ConstrctedResource();
878 this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground);
880 this.renderSize = new Size(width,height);
882 this.TextAntialiasMode = this._TextAntialiasMode;
886 protected virtual D2D.RenderTarget ConstructRender(D2D.Factory1 factory, D2D.RenderTargetProperties prop, double width, double height)
888 protected virtual D2D.RenderTarget ConstructRender(D2D.Factory factory, D2D.RenderTargetProperties prop, double width, double height)
891 throw new NotImplementedException();
894 protected virtual void ConstrctedResource()
896 throw new NotImplementedException();
899 public virtual void GetDpi(out float dpix,out float dpiy)
901 throw new NotImplementedException();
904 protected virtual void DestructRender()
906 throw new NotImplementedException();
910 protected virtual void ReCreateTarget()
912 throw new NotImplementedException();
915 public void DestructDeviceResource()
917 this.hasCache = false;
918 if (this.CachedBitmap != null)
919 this.CachedBitmap.Dispose();
920 this.Brushes.Clear();
921 this.Strokes.Clear();
922 this.Effects.Clear();
923 if (this.textRender != null)
924 this.textRender.Dispose();
925 this.DestructRender();
928 void ParseLayout(MyTextLayout layout, string str)
930 for (int i = 0; i < str.Length; i++)
932 DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str);
933 if (inlineObject != null)
935 layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1));
936 layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1));
939 layout.SetLineBreakBrush(this.Brushes.Get(this.ControlChar));