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,LineToIndexTable lti,int row,double x,double y);
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 class D2DRenderCommon : IDisposable
147 ColorBrushCollection Brushes = new ColorBrushCollection();
148 StrokeCollection Strokes = new StrokeCollection();
149 InlineManager HiddenChars;
150 TextAntialiasMode _TextAntialiasMode;
151 Color4 _ControlChar,_Forground,_URL,_Hilight;
152 DW.Factory DWFactory;
153 #if METRO || WINDOWS_UWP
154 D2D.Factory1 D2DFactory;
156 D2D.Factory D2DFactory;
158 DW.TextFormat format;
159 protected D2D.Bitmap cachedBitMap;
160 CustomTextRenderer textRender;
163 Color4 _Comment, _Literal, _Keyword1, _Keyword2;
164 protected bool hasCache;
166 protected Size renderSize
172 protected D2D.RenderTarget render
178 public D2DRenderCommon()
180 this.DWFactory = new DW.Factory(DW.FactoryType.Shared);
181 #if METRO || WINDOWS_UWP
182 this.D2DFactory = new D2D.Factory1(D2D.FactoryType.MultiThreaded);
184 this.D2DFactory = new D2D.Factory(D2D.FactoryType.MultiThreaded);
186 this.Strokes.Factory = this.D2DFactory;
187 this.ChangedRenderResource += (s, e) => { };
188 this.ChangedRightToLeft += (s, e) => { };
189 this.renderSize = new Size();
192 public event ChangedRenderResourceEventHandler ChangedRenderResource;
194 public event EventHandler ChangedRightToLeft;
196 public const int MiniumeWidth = 40; //これ以上ないと誤操作が起こる
198 public void InitTextFormat(string fontName, float fontSize, DW.FontWeight fontWeigth = DW.FontWeight.Normal,DW.FontStyle fontStyle = DW.FontStyle.Normal)
200 if(this.format != null)
201 this.format.Dispose();
204 this.GetDpi(out dpix, out dpiy);
206 this.format = new DW.TextFormat(this.DWFactory, fontName, fontWeigth, fontStyle, fontSize);
207 this.format.WordWrapping = DW.WordWrapping.NoWrap;
209 if (this.HiddenChars == null)
210 this.HiddenChars = new InlineManager(this.DWFactory, this.format, this.ControlChar, this.Brushes);
212 this.HiddenChars.Format = this.format;
214 this.TabWidthChar = this.TabWidthChar;
216 this.hasCache = false;
218 MyTextLayout layout = new MyTextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue, dpix, false);
219 layout.RightToLeft = false;
220 this.emSize = new Size(layout.Width, layout.Height);
223 layout = new MyTextLayout(this.DWFactory, "+", this.format, float.MaxValue, float.MaxValue, dpix, false);
224 layout.RightToLeft = false;
226 this.FoldingWidth = Math.Max(D2DRenderCommon.MiniumeWidth, layout.Width);
228 this.FoldingWidth = layout.Width;
232 this.ChangedRenderResource(this,new ChangedRenderRsourceEventArgs(ResourceType.Font));
235 public bool RightToLeft
239 return this.format.ReadingDirection == DW.ReadingDirection.RightToLeft;
243 this.format.ReadingDirection = value ? DW.ReadingDirection.RightToLeft : DW.ReadingDirection.LeftToRight;
244 this.ChangedRightToLeft(this, null);
248 public TextAntialiasMode TextAntialiasMode
252 return this._TextAntialiasMode;
256 if (this.render == null)
257 throw new InvalidOperationException();
258 this._TextAntialiasMode = value;
259 this.render.TextAntialiasMode = (D2D.TextAntialiasMode)value;
260 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Antialias));
264 public bool ShowFullSpace
268 if (this.HiddenChars == null)
271 return this.HiddenChars.ContainsSymbol(' ');
275 if (this.HiddenChars == null)
276 throw new InvalidOperationException();
278 this.HiddenChars.AddSymbol(' ', '□');
280 this.HiddenChars.RemoveSymbol(' ');
284 public bool ShowHalfSpace
288 if (this.HiddenChars == null)
291 return this.HiddenChars.ContainsSymbol(' ');
295 if (this.HiddenChars == null)
296 throw new InvalidOperationException();
298 this.HiddenChars.AddSymbol(' ', 'ロ');
300 this.HiddenChars.RemoveSymbol(' ');
308 if (this.HiddenChars == null)
311 return this.HiddenChars.ContainsSymbol('\t');
315 if (this.HiddenChars == null)
316 throw new InvalidOperationException();
318 this.HiddenChars.AddSymbol('\t', '>');
320 this.HiddenChars.RemoveSymbol('\t');
324 public bool ShowLineBreak
328 return this._ShowLineBreak;
332 this._ShowLineBreak = value;
336 public Color4 Foreground
340 return this._Forground;
344 if (this.render == null)
346 this._Forground = value;
347 if (this.textRender != null)
348 this.textRender.DefaultFore = value;
349 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
353 public Color4 HilightForeground
359 public Color4 Background
365 public Color4 InsertCaret
371 public Color4 OverwriteCaret
377 public Color4 LineMarker
383 public Color4 UpdateArea
389 public Color4 LineNumber
395 public Color4 ControlChar
399 return this._ControlChar;
403 this._ControlChar = value;
404 if (this.HiddenChars != null)
405 this.HiddenChars.Fore = value;
406 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
419 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
423 public Color4 Hilight
427 return this._Hilight;
431 this._Hilight = value;
435 public Color4 Comment
439 return this._Comment;
443 this._Comment = value;
444 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
448 public Color4 Literal
452 return this._Literal;
456 this._Literal = value;
457 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
461 public Color4 Keyword1
465 return this._Keyword1;
469 this._Keyword1 = value;
470 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
474 public Color4 Keyword2
478 return this._Keyword2;
482 this._Keyword2 = value;
483 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
487 public Rectangle TextArea
493 public double LineNemberWidth
497 return this.emSize.Width * EditView.LineNumberLength;
501 public double FoldingWidth
507 public int TabWidthChar
509 get { return this.tabLength; }
514 this.tabLength = value;
515 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
516 float width = (float)(layout.Metrics.Width * value);
517 this.HiddenChars.TabWidth = width;
518 this.format.IncrementalTabStop = width;
529 public void DrawGripper(Point p, double radius)
531 D2D.Ellipse ellipse = new D2D.Ellipse();
533 ellipse.RadiusX = (float)radius;
534 ellipse.RadiusY = (float)radius;
535 this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
536 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
539 public void ReConstructDeviceResource()
541 this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height);
544 public void ReConstructDeviceResource(double width, double height)
546 this.DestructDeviceResource();
547 this.ConstructDeviceResource(width, height);
550 public virtual void DrawCachedBitmap(Rectangle rect)
554 public virtual void CacheContent()
558 public virtual bool IsVaildCache()
560 return this.hasCache;
563 protected void BegineDraw()
565 if (this.render == null || this.render.IsDisposed)
567 this.render.BeginDraw();
568 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
571 protected void EndDraw()
573 if (this.render == null || this.render.IsDisposed)
575 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
576 this.render.EndDraw();
579 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground)
581 if (this.render == null || this.render.IsDisposed)
584 D2D.SolidColorBrush brush;
587 case StringColorType.Forground:
588 brush = this.Brushes.Get(this.Foreground);
590 case StringColorType.LineNumber:
591 brush = this.Brushes.Get(this.LineNumber);
594 throw new ArgumentOutOfRangeException();
596 this.GetDpi(out dpix, out dpiy);
597 MyTextLayout layout = new MyTextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
598 layout.StringAlignment = align;
599 layout.Draw(this.render, (float)x, (float)y, brush);
603 public void DrawFoldingMark(bool expand, double x, double y)
605 string mark = expand ? "-" : "+";
606 this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
609 public void FillRectangle(Rectangle rect,FillRectType type)
611 if (this.render == null || this.render.IsDisposed)
613 D2D.SolidColorBrush brush = null;
616 case FillRectType.OverwriteCaret:
617 brush = this.Brushes.Get(this.OverwriteCaret);
618 this.render.FillRectangle(rect, brush);
620 case FillRectType.InsertCaret:
621 brush = this.Brushes.Get(this.InsertCaret);
622 this.render.FillRectangle(rect, brush);
624 case FillRectType.InsertPoint:
625 brush = this.Brushes.Get(this.Hilight);
626 this.render.FillRectangle(rect, brush);
628 case FillRectType.LineMarker:
629 brush = this.Brushes.Get(this.LineMarker);
630 this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness);
632 case FillRectType.UpdateArea:
633 brush = this.Brushes.Get(this.UpdateArea);
634 this.render.FillRectangle(rect, brush);
639 public void FillBackground(Rectangle rect)
641 if (this.render == null || this.render.IsDisposed)
643 this.render.Clear(this.Background);
646 public void DrawOneLine(Document doc,LineToIndexTable lti, int row, double x, double y, PreDrawOneLineHandler PreDrawOneLine)
648 int lineLength = lti.GetLengthFromLineNumber(row);
650 if (lineLength == 0 || this.render == null || this.render.IsDisposed)
653 MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
655 if(PreDrawOneLine != null)
656 PreDrawOneLine(layout,lti,row,x,y);
658 if(this.HilightForeground.Alpha == 0.0f)
660 int lineIndex = lti.GetIndexFromLineNumber(row);
661 var SelectRanges = from s in doc.Selections.Get(lineIndex, lineLength)
662 let n = Util.ConvertAbsIndexToRelIndex(s, lineIndex, lineLength)
665 if (SelectRanges != null)
667 foreach (Selection sel in SelectRanges)
669 if (sel.length == 0 || sel.start == -1)
672 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
677 layout.Draw(textRender, (float)x, (float)y);
681 public void BeginClipRect(Rectangle rect)
683 this.render.PushAxisAlignedClip(rect, D2D.AntialiasMode.Aliased);
686 public void EndClipRect()
688 this.render.PopAxisAlignedClip();
691 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
695 layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
698 public void DrawLine(Point from, Point to)
700 D2D.Brush brush = this.Brushes.Get(this.Foreground);
701 D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold);
702 this.render.DrawLine(from, to, brush, 1.0f, stroke);
705 public const int BoldThickness = 2;
706 public const int NormalThickness = 1;
708 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
710 if (type == HilightType.None)
713 float thickness = isBold ? BoldThickness : NormalThickness;
716 if (effectColor != null)
717 color = (Color4)effectColor;
718 else if (type == HilightType.Select)
719 color = this.Hilight;
721 color = this.Foreground;
723 IMarkerEffecter effecter = null;
724 D2D.SolidColorBrush brush = this.Brushes.Get(color);
726 if (type == HilightType.Squiggle)
727 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness);
728 else if (type == HilightType.Select)
729 effecter = new HilightMarker(this.render, brush);
730 else if (type == HilightType.None)
733 effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness);
735 if (effecter != null)
737 bool isUnderline = type != HilightType.Select;
739 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
740 foreach (DW.HitTestMetrics metric in metrics)
742 float offset = isUnderline ? metric.Height : 0;
743 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
748 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges, IEnumerable<Selection> SelectRanges)
751 this.GetDpi(out dpix,out dpiy);
753 bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
754 MyTextLayout newLayout = new MyTextLayout(this.DWFactory,
758 this.TextArea.Height,
760 hasNewLine && this._ShowLineBreak);
761 ParseLayout(newLayout, str);
762 if (syntaxCollection != null)
764 foreach (SyntaxInfo s in syntaxCollection)
766 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
769 case TokenType.Comment:
770 brush = this.Brushes.Get(this.Comment);
772 case TokenType.Keyword1:
773 brush = this.Brushes.Get(this.Keyword1);
775 case TokenType.Keyword2:
776 brush = this.Brushes.Get(this.Keyword2);
778 case TokenType.Literal:
779 brush = this.Brushes.Get(this.Literal);
782 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
786 if (MarkerRanges != null)
788 foreach (Marker m in MarkerRanges)
790 if (m.start == -1 || m.length == 0)
792 Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f);
793 if (m.hilight == HilightType.Url)
795 if (m.hilight == HilightType.Select)
796 newLayout.SetDrawingEffect(new SelectedEffect(this.HilightForeground, color, m.isBoldLine), new DW.TextRange(m.start, m.length));
798 newLayout.SetDrawingEffect(new DrawingEffect(m.hilight, color,m.isBoldLine), new DW.TextRange(m.start, m.length));
799 if (m.hilight != HilightType.None && m.hilight != HilightType.Select)
800 newLayout.SetUnderline(true, new DW.TextRange(m.start, m.length));
804 if (SelectRanges != null && this.HilightForeground.Alpha > 0.0)
806 foreach (Selection sel in SelectRanges)
808 if (sel.length == 0 || sel.start == -1)
811 newLayout.SetDrawingEffect(new SelectedEffect(this.HilightForeground, this.Hilight, false), new DW.TextRange(sel.start, sel.length));
818 public List<LineToIndexTableData> BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
820 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
822 this.format.WordWrapping = DW.WordWrapping.Wrap;
824 foreach (string str in doc.GetLines(startIndex, endIndex))
826 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
829 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
831 if (metrics.Length == 0 && metrics.NewlineLength == 0)
834 bool lineend = false;
835 if (metrics.NewlineLength > 0)
838 output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
839 i += Util.RoundUp(metrics.Length);
844 startIndex += str.Length;
847 this.format.WordWrapping = DW.WordWrapping.NoWrap;
849 if (output.Count > 0)
850 output.Last().LineEnd = true;
855 public virtual void Dispose()
857 this.DestructDeviceResource();
858 this.HiddenChars.Clear();
859 if (this.format != null)
860 this.format.Dispose();
861 if(this.DWFactory != null)
862 this.DWFactory.Dispose();
863 if(this.D2DFactory != null)
864 this.D2DFactory.Dispose();
867 public void ConstructDeviceResource(double width, double height)
870 this.GetDpi(out dpiX, out dpiY);
871 D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
872 D2D.RenderTargetType.Default,
873 new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore),
876 D2D.RenderTargetUsage.None,
877 D2D.FeatureLevel.Level_DEFAULT);
879 this.render = this.ConstructRender(this.D2DFactory,prop,width,height);
881 this.Brushes.Render = this.render;
883 D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
886 bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore);
887 this.cachedBitMap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
888 this.hasCache = false;
890 this.ConstrctedResource();
892 this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground);
894 this.renderSize = new Size(width,height);
896 this.TextAntialiasMode = this._TextAntialiasMode;
899 #if METRO || WINDOWS_UWP
900 protected virtual D2D.RenderTarget ConstructRender(D2D.Factory1 factory, D2D.RenderTargetProperties prop, double width, double height)
902 protected virtual D2D.RenderTarget ConstructRender(D2D.Factory factory, D2D.RenderTargetProperties prop, double width, double height)
905 throw new NotImplementedException();
908 protected virtual void ConstrctedResource()
910 throw new NotImplementedException();
913 public virtual void GetDpi(out float dpix,out float dpiy)
915 throw new NotImplementedException();
918 protected virtual void DestructRender()
920 throw new NotImplementedException();
924 protected virtual void ReCreateTarget()
926 throw new NotImplementedException();
929 public void DestructDeviceResource()
931 this.hasCache = false;
932 if (this.cachedBitMap != null)
933 this.cachedBitMap.Dispose();
934 this.Brushes.Clear();
935 this.Strokes.Clear();
936 if (this.textRender != null)
937 this.textRender.Dispose();
938 this.DestructRender();
941 void ParseLayout(MyTextLayout layout, string str)
943 for (int i = 0; i < str.Length; i++)
945 DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str);
946 if (inlineObject != null)
948 layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1));
949 layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1));
952 layout.SetLineBreakBrush(this.Brushes.Get(this.ControlChar));