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;
190 D2D.RenderTarget render;
191 CustomTextRenderer textRender;
193 bool hasCache, _ShowLineBreak;
194 Size renderSize = new Size();
195 Color4 _Comment, _Literal, _Keyword1, _Keyword2;
197 public D2DRenderCommon()
199 this.DWFactory = new DW.Factory(DW.FactoryType.Shared);
201 this.D2DFactory = new D2D.Factory1(D2D.FactoryType.MultiThreaded);
203 this.D2DFactory = new D2D.Factory(D2D.FactoryType.MultiThreaded);
205 this.Strokes.Factory = this.D2DFactory;
206 this.ChangedRenderResource += (s, e) => { };
207 this.ChangedRightToLeft += (s, e) => { };
210 public event ChangedRenderResourceEventHandler ChangedRenderResource;
212 public event EventHandler ChangedRightToLeft;
214 public const int MiniumeWidth = 40; //これ以上ないと誤操作が起こる
216 public void InitTextFormat(string fontName, float fontSize, DW.FontWeight fontWeigth = DW.FontWeight.Normal,DW.FontStyle fontStyle = DW.FontStyle.Normal)
218 if(this.format != null)
219 this.format.Dispose();
222 this.GetDpi(out dpix, out dpiy);
224 this.format = new DW.TextFormat(this.DWFactory, fontName, fontWeigth, fontStyle, fontSize);
225 this.format.WordWrapping = DW.WordWrapping.NoWrap;
227 if (this.HiddenChars == null)
228 this.HiddenChars = new InlineManager(this.DWFactory, this.format, this.ControlChar, this.Brushes);
230 this.HiddenChars.Format = this.format;
232 this.TabWidthChar = this.TabWidthChar;
234 this.hasCache = false;
236 MyTextLayout layout = new MyTextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue, dpix, false);
237 layout.RightToLeft = false;
238 this.emSize = new Size(layout.Width, layout.Height);
241 layout = new MyTextLayout(this.DWFactory, "+", this.format, float.MaxValue, float.MaxValue, dpix, false);
242 layout.RightToLeft = false;
244 this.FoldingWidth = Math.Max(D2DRenderCommon.MiniumeWidth, layout.Width);
246 this.FoldingWidth = layout.Width;
250 this.ChangedRenderResource(this,new ChangedRenderRsourceEventArgs(ResourceType.Font));
253 public bool RightToLeft
257 return this.format.ReadingDirection == DW.ReadingDirection.RightToLeft;
261 this.format.ReadingDirection = value ? DW.ReadingDirection.RightToLeft : DW.ReadingDirection.LeftToRight;
262 this.ChangedRightToLeft(this, null);
266 public TextAntialiasMode TextAntialiasMode
270 return this._TextAntialiasMode;
274 if (this.render == null)
275 throw new InvalidOperationException();
276 this._TextAntialiasMode = value;
277 this.render.TextAntialiasMode = (D2D.TextAntialiasMode)value;
278 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Antialias));
282 public bool ShowFullSpace
286 if (this.HiddenChars == null)
289 return this.HiddenChars.ContainsSymbol(' ');
293 if (this.HiddenChars == null)
294 throw new InvalidOperationException();
296 this.HiddenChars.AddSymbol(' ', '□');
298 this.HiddenChars.RemoveSymbol(' ');
299 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
303 public bool ShowHalfSpace
307 if (this.HiddenChars == null)
310 return this.HiddenChars.ContainsSymbol(' ');
314 if (this.HiddenChars == null)
315 throw new InvalidOperationException();
317 this.HiddenChars.AddSymbol(' ', 'ロ');
319 this.HiddenChars.RemoveSymbol(' ');
320 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
328 if (this.HiddenChars == null)
331 return this.HiddenChars.ContainsSymbol('\t');
335 if (this.HiddenChars == null)
336 throw new InvalidOperationException();
338 this.HiddenChars.AddSymbol('\t', '>');
340 this.HiddenChars.RemoveSymbol('\t');
341 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
345 public bool ShowLineBreak
349 return this._ShowLineBreak;
353 this._ShowLineBreak = value;
354 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
358 public Color4 Foreground
362 return this._Forground;
366 if (this.render == null)
368 this._Forground = value;
369 if (this.textRender != null)
370 this.textRender.DefaultFore = value;
371 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
375 public Color4 Background
381 public Color4 InsertCaret
387 public Color4 OverwriteCaret
393 public Color4 LineMarker
399 public Color4 UpdateArea
405 public Color4 LineNumber
411 public Color4 ControlChar
415 return this._ControlChar;
419 this._ControlChar = value;
420 if (this.HiddenChars != null)
421 this.HiddenChars.Fore = value;
422 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
435 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
439 public Color4 Hilight
443 return this._Hilight;
447 this._Hilight = value;
451 public Color4 Comment
455 return this._Comment;
459 this._Comment = value;
460 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
464 public Color4 Literal
468 return this._Literal;
472 this._Literal = value;
473 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
477 public Color4 Keyword1
481 return this._Keyword1;
485 this._Keyword1 = value;
486 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
490 public Color4 Keyword2
494 return this._Keyword2;
498 this._Keyword2 = value;
499 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
503 public Rectangle TextArea
509 public double LineNemberWidth
513 return this.emSize.Width * EditView.LineNumberLength;
517 public double FoldingWidth
523 public int TabWidthChar
525 get { return this.tabLength; }
530 this.tabLength = value;
531 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
532 float width = (float)(layout.Metrics.Width * value);
533 this.HiddenChars.TabWidth = width;
534 this.format.IncrementalTabStop = width;
544 public void DrawGripper(Point p, double radius)
546 D2D.Ellipse ellipse = new D2D.Ellipse();
548 ellipse.RadiusX = (float)radius;
549 ellipse.RadiusY = (float)radius;
550 this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
551 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
554 public void ReConstructDeviceResource()
556 this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height);
559 public void ReConstructDeviceResource(double width, double height)
561 this.DestructDeviceResource();
562 this.ConstructDeviceResource(width, height);
565 public virtual void DrawCachedBitmap(Rectangle rect)
567 if (this.render == null || this.render.IsDisposed)
569 render.DrawBitmap(this.bitmap, rect, 1.0f, D2D.BitmapInterpolationMode.Linear, rect);
572 public virtual void CacheContent()
574 if (this.render == null || this.bitmap == null || this.bitmap.IsDisposed || this.render.IsDisposed)
577 this.bitmap.CopyFromRenderTarget(this.render, new SharpDX.Point(), new SharpDX.Rectangle(0, 0, (int)this.renderSize.Width, (int)this.renderSize.Height));
578 this.hasCache = true;
581 public virtual bool IsVaildCache()
583 return this.hasCache;
586 public virtual void BegineDraw()
588 if (this.render == null || this.render.IsDisposed)
590 this.render.BeginDraw();
591 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
594 public virtual void EndDraw()
596 if (this.render == null || this.render.IsDisposed)
598 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
599 this.render.EndDraw();
602 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground)
604 if (this.render == null || this.render.IsDisposed)
607 D2D.SolidColorBrush brush;
610 case StringColorType.Forground:
611 brush = this.Brushes.Get(this.Foreground);
613 case StringColorType.LineNumber:
614 brush = this.Brushes.Get(this.LineNumber);
617 throw new ArgumentOutOfRangeException();
619 this.GetDpi(out dpix, out dpiy);
620 MyTextLayout layout = new MyTextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
621 layout.StringAlignment = align;
622 layout.Draw(this.render, (float)x, (float)y, brush);
626 public void DrawFoldingMark(bool expand, double x, double y)
628 string mark = expand ? "-" : "+";
629 this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
632 public void FillRectangle(Rectangle rect,FillRectType type)
634 if (this.render == null || this.render.IsDisposed)
636 D2D.SolidColorBrush brush = null;
639 case FillRectType.OverwriteCaret:
640 brush = this.Brushes.Get(this.OverwriteCaret);
641 this.render.FillRectangle(rect, brush);
643 case FillRectType.InsertCaret:
644 brush = this.Brushes.Get(this.InsertCaret);
645 this.render.FillRectangle(rect, brush);
647 case FillRectType.InsertPoint:
648 brush = this.Brushes.Get(this.Hilight);
649 this.render.FillRectangle(rect, brush);
651 case FillRectType.LineMarker:
652 brush = this.Brushes.Get(this.LineMarker);
653 this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness);
655 case FillRectType.UpdateArea:
656 brush = this.Brushes.Get(this.UpdateArea);
657 this.render.FillRectangle(rect, brush);
662 public void FillBackground(Rectangle rect)
664 if (this.render == null || this.render.IsDisposed)
666 this.render.Clear(this.Background);
669 public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable<Selection> SelectRanges,PreDrawOneLineHandler PreDrawOneLine)
671 int lineLength = lti.GetLengthFromLineNumber(row);
673 if (lineLength == 0 || this.render == null || this.render.IsDisposed)
676 MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
678 this.render.PushAxisAlignedClip(this.TextArea, D2D.AntialiasMode.Aliased);
680 if(PreDrawOneLine != null)
681 PreDrawOneLine(layout);
683 if (SelectRanges != null)
685 foreach (Selection sel in SelectRanges)
687 if (sel.length == 0 || sel.start == -1)
690 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
694 layout.Draw(textRender, (float)x, (float)y);
696 this.render.PopAxisAlignedClip();
699 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
703 layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
706 public void DrawLine(Point from, Point to)
708 D2D.Brush brush = this.Brushes.Get(this.Foreground);
709 D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold);
710 this.render.DrawLine(from, to, brush, 1.0f, stroke);
713 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
715 if (type == HilightType.None)
718 float thickness = isBold ? 2 : 1;
721 if (effectColor != null)
722 color = (Color4)effectColor;
723 else if (type == HilightType.Select)
724 color = this.Hilight;
726 color = this.Foreground;
728 IMarkerEffecter effecter = null;
729 D2D.SolidColorBrush brush = this.Brushes.Get(color);
731 if (type == HilightType.Squiggle)
732 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness);
733 else if (type == HilightType.Select)
734 effecter = new HilightMarker(this.render, brush);
735 else if (type == HilightType.None)
738 effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness);
740 if (effecter != null)
742 bool isUnderline = type != HilightType.Select;
744 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
745 foreach (DW.HitTestMetrics metric in metrics)
747 float offset = isUnderline ? metric.Height : 0;
748 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
753 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges)
756 this.GetDpi(out dpix,out dpiy);
758 bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
759 MyTextLayout newLayout = new MyTextLayout(this.DWFactory,
763 this.TextArea.Height,
765 hasNewLine && this._ShowLineBreak);
766 ParseLayout(newLayout, str);
767 if (syntaxCollection != null)
769 foreach (SyntaxInfo s in syntaxCollection)
771 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
774 case TokenType.Comment:
775 brush = this.Brushes.Get(this.Comment);
777 case TokenType.Keyword1:
778 brush = this.Brushes.Get(this.Keyword1);
780 case TokenType.Keyword2:
781 brush = this.Brushes.Get(this.Keyword2);
783 case TokenType.Literal:
784 brush = this.Brushes.Get(this.Literal);
787 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
791 if (MarkerRanges != null)
793 foreach (Marker m in MarkerRanges)
795 if (m.start == -1 || m.length == 0)
797 Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f);
798 if (m.hilight == HilightType.Url)
800 newLayout.SetDrawingEffect(this.Effects.Get(color, m.hilight), new DW.TextRange(m.start, m.length));
807 public List<LineToIndexTableData> BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
809 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
811 this.format.WordWrapping = DW.WordWrapping.Wrap;
813 foreach (string str in doc.GetLines(startIndex, endIndex))
815 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
818 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
820 if (metrics.Length == 0 && metrics.NewlineLength == 0)
823 bool lineend = false;
824 if (metrics.NewlineLength > 0)
827 output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
828 i += Util.RoundUp(metrics.Length);
833 startIndex += str.Length;
836 this.format.WordWrapping = DW.WordWrapping.NoWrap;
838 if (output.Count > 0)
839 output.Last().LineEnd = true;
844 public virtual void Dispose()
846 this.DestructDeviceResource();
847 this.HiddenChars.Clear();
848 if (this.format != null)
849 this.format.Dispose();
850 if(this.DWFactory != null)
851 this.DWFactory.Dispose();
852 if(this.D2DFactory != null)
853 this.D2DFactory.Dispose();
856 public void ConstructDeviceResource(double width, double height)
859 this.GetDpi(out dpiX, out dpiY);
860 D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
861 D2D.RenderTargetType.Default,
862 new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore),
865 D2D.RenderTargetUsage.None,
866 D2D.FeatureLevel.Level_DEFAULT);
868 this.render = this.ConstructRender(this.D2DFactory,prop,width,height);
870 this.Brushes.Render = this.render;
872 D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
875 bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore);
876 this.bitmap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
877 this.hasCache = false;
879 this.ConstrctedResource();
881 this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground);
883 this.renderSize.Width = width;
884 this.renderSize.Height = height;
886 this.TextAntialiasMode = this._TextAntialiasMode;
890 protected virtual D2D.RenderTarget ConstructRender(D2D.Factory1 factory, D2D.RenderTargetProperties prop, double width, double height)
892 protected virtual D2D.RenderTarget ConstructRender(D2D.Factory factory, D2D.RenderTargetProperties prop, double width, double height)
895 throw new NotImplementedException();
898 protected virtual void ConstrctedResource()
900 throw new NotImplementedException();
903 public virtual void GetDpi(out float dpix,out float dpiy)
905 throw new NotImplementedException();
908 protected virtual void DestructRender()
910 throw new NotImplementedException();
914 protected virtual void ReCreateTarget()
916 throw new NotImplementedException();
919 public void DestructDeviceResource()
921 this.hasCache = false;
922 if (this.bitmap != null)
923 this.bitmap.Dispose();
924 this.Brushes.Clear();
925 this.Strokes.Clear();
926 this.Effects.Clear();
927 if (this.textRender != null)
928 this.textRender.Dispose();
929 this.DestructRender();
932 void ParseLayout(MyTextLayout layout, string str)
934 for (int i = 0; i < str.Length; i++)
936 DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str);
937 if (inlineObject != null)
939 layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1));
940 layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1));
943 layout.SetLineBreakBrush(this.Brushes.Get(this.ControlChar));