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 #define CACHE_COLOR_BURSH
15 using System.Collections.Generic;
20 using D2D = SharpDX.Direct2D1;
21 using DW = SharpDX.DirectWrite;
22 using DXGI = SharpDX.DXGI;
23 using System.Runtime.InteropServices;
25 namespace FooEditEngine
27 delegate void PreDrawOneLineHandler(MyTextLayout layout,LineToIndexTable lti,int row,double x,double y);
29 delegate void GetDpiHandler(out float dpix,out float dpiy);
32 /// 文字列のアンチエイリアシングモードを指定する
34 public enum TextAntialiasMode
39 Default = D2D.TextAntialiasMode.Default,
41 /// ClearTypeでアンチエイリアシングを行います
43 ClearType = D2D.TextAntialiasMode.Cleartype,
45 /// グレイスケールモードでアンチエイリアシングを行います
47 GrayScale = D2D.TextAntialiasMode.Grayscale,
51 Aliased = D2D.TextAntialiasMode.Aliased,
54 sealed class ColorBrushCollection
56 ResourceManager<Color4, D2D.SolidColorBrush> cache = new ResourceManager<Color4, D2D.SolidColorBrush>();
58 public D2D.SolidColorBrush Get(D2D.RenderTarget render,Color4 key)
60 D2D.SolidColorBrush brush;
63 bool result = cache.TryGetValue(key, out brush);
66 brush = new D2D.SolidColorBrush(render, key);
67 cache.Add(key, brush);
70 brush = new D2D.SolidColorBrush(render, key);
82 sealed class StrokeCollection
84 ResourceManager<HilightType, D2D.StrokeStyle> cache = new ResourceManager<HilightType, D2D.StrokeStyle>();
86 public D2D.StrokeStyle Get(D2D.RenderTarget render,HilightType type)
88 return this.Get(render.Factory, type);
92 public D2D.StrokeStyle Get(HilightType type)
94 return this.Get(D2DRenderShared.D2DFactory, type);
97 public D2D.StrokeStyle Get(D2D.Factory factory,HilightType type)
99 D2D.StrokeStyle stroke;
100 if (this.cache.TryGetValue(type, out stroke))
103 D2D.StrokeStyleProperties prop = new D2D.StrokeStyleProperties();
104 prop.DashCap = D2D.CapStyle.Flat;
106 prop.DashStyle = D2D.DashStyle.Solid;
107 prop.EndCap = D2D.CapStyle.Flat;
108 prop.LineJoin = D2D.LineJoin.Miter;
110 prop.StartCap = D2D.CapStyle.Flat;
113 case HilightType.Sold:
114 case HilightType.Url:
115 case HilightType.Squiggle:
116 prop.DashStyle = D2D.DashStyle.Solid;
118 case HilightType.Dash:
119 prop.DashStyle = D2D.DashStyle.Dash;
121 case HilightType.DashDot:
122 prop.DashStyle = D2D.DashStyle.DashDot;
124 case HilightType.DashDotDot:
125 prop.DashStyle = D2D.DashStyle.DashDotDot;
127 case HilightType.Dot:
128 prop.DashStyle = D2D.DashStyle.Dot;
131 stroke = new D2D.StrokeStyle(D2DRenderShared.D2DFactory, prop);
132 this.cache.Add(type, stroke);
141 class D2DRenderShared
143 static DW.Factory _DWFactory;
144 static public DW.Factory DWFactory
148 if(_DWFactory == null)
150 _DWFactory = new DW.Factory(DW.FactoryType.Shared);
155 #if METRO || WINDOWS_UWP
156 static D2D.Factory1 _D2DFactory;
158 static public D2D.Factory1 D2DFactory
162 if (_D2DFactory == null)
164 _D2DFactory = new D2D.Factory1(D2D.FactoryType.SingleThreaded);
170 static D2D.Factory _D2DFactory;
171 static public D2D.Factory D2DFactory
175 if (_D2DFactory == null)
177 _D2DFactory = new D2D.Factory(D2D.FactoryType.SingleThreaded);
185 class D2DRenderCommon : IDisposable
187 InlineManager HiddenChars;
188 TextAntialiasMode _TextAntialiasMode;
189 Color4 _ControlChar,_Forground,_URL,_Hilight;
190 DW.TextFormat format;
191 protected CustomTextRenderer textRender;
192 protected D2D.Bitmap cachedBitMap;
194 bool _ShowLineBreak,_RightToLeft;
195 Color4 _Comment, _Literal, _Keyword1, _Keyword2;
196 protected bool hasCache = false;
197 protected Size renderSize;
199 protected ColorBrushCollection Brushes
205 protected StrokeCollection Strokes
211 D2D.RenderTarget _render;
212 protected D2D.RenderTarget render
214 get { return _render; }
218 this.TextAntialiasMode = this._TextAntialiasMode;
219 if(this.HiddenChars != null)
220 this.HiddenChars.ReGenerate();
224 public D2DRenderCommon()
226 this.Brushes = new ColorBrushCollection();
227 this.Strokes = new StrokeCollection();
228 this.ChangedRenderResource += (s, e) => { };
229 this.ChangedRightToLeft += (s, e) => { };
230 this.renderSize = new Size();
233 public event ChangedRenderResourceEventHandler ChangedRenderResource;
235 public event EventHandler ChangedRightToLeft;
237 public const int MiniumeWidth = 40; //これ以上ないと誤操作が起こる
239 public void InitTextFormat(string fontName, float fontSize, DW.FontWeight fontWeigth = DW.FontWeight.Normal,DW.FontStyle fontStyle = DW.FontStyle.Normal)
241 if(this.format != null)
242 this.format.Dispose();
245 this.GetDpi(out dpix, out dpiy);
247 this.format = new DW.TextFormat(D2DRenderShared.DWFactory, fontName, fontWeigth, fontStyle, fontSize);
248 this.format.WordWrapping = DW.WordWrapping.NoWrap;
249 this.format.ReadingDirection = GetDWRightDirect(_RightToLeft);
251 if (this.HiddenChars == null)
252 this.HiddenChars = new InlineManager(D2DRenderShared.DWFactory, this.format, this.ControlChar, this.Brushes);
254 this.HiddenChars.Format = this.format;
256 this.TabWidthChar = this.TabWidthChar;
258 this.hasCache = false;
260 MyTextLayout layout = new MyTextLayout(D2DRenderShared.DWFactory, "0", this.format, float.MaxValue, float.MaxValue, dpix, false);
261 layout.RightToLeft = false;
262 this.emSize = new Size(layout.Width, layout.Height);
265 layout = new MyTextLayout(D2DRenderShared.DWFactory, "+", this.format, float.MaxValue, float.MaxValue, dpix, false);
266 layout.RightToLeft = false;
268 this.FoldingWidth = Math.Max(D2DRenderCommon.MiniumeWidth, layout.Width);
270 this.FoldingWidth = layout.Width;
274 this.OnChangedRenderResource(this,new ChangedRenderRsourceEventArgs(ResourceType.Font));
277 public void OnChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)
279 if (this.ChangedRenderResource != null)
280 this.ChangedRenderResource(sender, e);
283 DW.ReadingDirection GetDWRightDirect(bool rtl)
285 return rtl ? DW.ReadingDirection.RightToLeft : DW.ReadingDirection.LeftToRight;
288 public bool RightToLeft
296 _RightToLeft = value;
297 this.format.ReadingDirection = GetDWRightDirect(value);
298 this.ChangedRightToLeft(this, null);
302 public TextAntialiasMode TextAntialiasMode
306 return this._TextAntialiasMode;
310 if (this.render == null)
311 throw new InvalidOperationException();
312 this._TextAntialiasMode = value;
313 this.render.TextAntialiasMode = (D2D.TextAntialiasMode)value;
314 this.OnChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Antialias));
318 public bool ShowFullSpace
322 if (this.HiddenChars == null)
325 return this.HiddenChars.ContainsSymbol(' ');
329 if (this.HiddenChars == null)
330 throw new InvalidOperationException();
332 this.HiddenChars.AddSymbol(' ', '□');
334 this.HiddenChars.RemoveSymbol(' ');
338 public bool ShowHalfSpace
342 if (this.HiddenChars == null)
345 return this.HiddenChars.ContainsSymbol(' ');
349 if (this.HiddenChars == null)
350 throw new InvalidOperationException();
352 this.HiddenChars.AddSymbol(' ', 'ロ');
354 this.HiddenChars.RemoveSymbol(' ');
362 if (this.HiddenChars == null)
365 return this.HiddenChars.ContainsSymbol('\t');
369 if (this.HiddenChars == null)
370 throw new InvalidOperationException();
372 this.HiddenChars.AddSymbol('\t', '>');
374 this.HiddenChars.RemoveSymbol('\t');
378 public bool ShowLineBreak
382 return this._ShowLineBreak;
386 this._ShowLineBreak = value;
390 public Color4 Foreground
394 return this._Forground;
398 if (this.render == null)
400 this._Forground = value;
401 if (this.textRender != null)
402 this.textRender.DefaultFore = value;
403 this.OnChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
407 public Color4 HilightForeground
413 public Color4 Background
419 public Color4 InsertCaret
425 public Color4 OverwriteCaret
431 public Color4 LineMarker
437 public Color4 UpdateArea
443 public Color4 LineNumber
449 public Color4 ControlChar
453 return this._ControlChar;
457 this._ControlChar = value;
458 if (this.HiddenChars != null)
459 this.HiddenChars.Fore = value;
460 this.OnChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
473 this.OnChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
477 public Color4 Hilight
481 return this._Hilight;
485 this._Hilight = value;
489 public Color4 Comment
493 return this._Comment;
497 this._Comment = value;
498 this.OnChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
502 public Color4 Literal
506 return this._Literal;
510 this._Literal = value;
511 this.OnChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
515 public Color4 Keyword1
519 return this._Keyword1;
523 this._Keyword1 = value;
524 this.OnChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
528 public Color4 Keyword2
532 return this._Keyword2;
536 this._Keyword2 = value;
537 this.OnChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
541 public Rectangle TextArea
547 public double LineNemberWidth
551 return this.emSize.Width * EditView.LineNumberLength;
555 public double FoldingWidth
561 public int TabWidthChar
563 get { return this.tabLength; }
568 this.tabLength = value;
569 DW.TextLayout layout = new DW.TextLayout(D2DRenderShared.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
570 float width = (float)(layout.Metrics.Width * value);
571 this.HiddenChars.TabWidth = width;
572 this.format.IncrementalTabStop = width;
583 public void DrawGripper(Point p, double radius)
585 D2D.Ellipse ellipse = new D2D.Ellipse();
587 ellipse.RadiusX = (float)radius;
588 ellipse.RadiusY = (float)radius;
589 this.render.FillEllipse(ellipse, this.Brushes.Get(this.render,this.Background));
590 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.render, this.Foreground));
594 public virtual void DrawCachedBitmap(Rectangle rect)
598 public virtual void CacheContent()
602 public virtual bool IsVaildCache()
604 return this.hasCache;
607 protected void BegineDraw()
609 if (this.render == null || this.render.IsDisposed)
611 this.render.BeginDraw();
612 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
615 protected void EndDraw()
617 if (this.render == null || this.render.IsDisposed)
619 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
620 this.render.EndDraw();
623 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground)
625 if (this.render == null || this.render.IsDisposed)
628 D2D.SolidColorBrush brush;
631 case StringColorType.Forground:
632 brush = this.Brushes.Get(this.render, this.Foreground);
634 case StringColorType.LineNumber:
635 brush = this.Brushes.Get(this.render, this.LineNumber);
638 throw new ArgumentOutOfRangeException();
640 this.GetDpi(out dpix, out dpiy);
641 MyTextLayout layout = new MyTextLayout(D2DRenderShared.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
642 layout.StringAlignment = align;
643 layout.Draw(this.render, (float)x, (float)y, brush);
647 public void DrawFoldingMark(bool expand, double x, double y)
649 string mark = expand ? "-" : "+";
650 this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
653 public void FillRectangle(Rectangle rect,FillRectType type)
655 if (this.render == null || this.render.IsDisposed)
657 D2D.SolidColorBrush brush = null;
660 case FillRectType.OverwriteCaret:
661 brush = this.Brushes.Get(this.render, this.OverwriteCaret);
662 this.render.FillRectangle(rect, brush);
664 case FillRectType.InsertCaret:
665 brush = this.Brushes.Get(this.render, this.InsertCaret);
666 this.render.FillRectangle(rect, brush);
668 case FillRectType.InsertPoint:
669 brush = this.Brushes.Get(this.render, this.Hilight);
670 this.render.FillRectangle(rect, brush);
672 case FillRectType.LineMarker:
673 brush = this.Brushes.Get(this.render, this.LineMarker);
674 this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness);
676 case FillRectType.UpdateArea:
677 brush = this.Brushes.Get(this.render, this.UpdateArea);
678 this.render.FillRectangle(rect, brush);
683 public void FillBackground(Rectangle rect)
685 if (this.render == null || this.render.IsDisposed)
687 this.render.Clear(this.Background);
690 public void DrawOneLine(Document doc,LineToIndexTable lti, int row, double x, double y, PreDrawOneLineHandler PreDrawOneLine)
692 int lineLength = lti.GetLengthFromLineNumber(row);
694 if (lineLength == 0 || this.render == null || this.render.IsDisposed)
697 MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
699 if(PreDrawOneLine != null)
700 PreDrawOneLine(layout,lti,row,x,y);
702 if(doc.Selections.Count > 0)
704 int lineIndex = lti.GetIndexFromLineNumber(row);
705 var SelectRanges = from s in doc.Selections.Get(lineIndex, lineLength)
706 let n = Util.ConvertAbsIndexToRelIndex(s, lineIndex, lineLength)
709 if (SelectRanges != null)
711 foreach (Selection sel in SelectRanges)
713 if (sel.length == 0 || sel.start == -1)
716 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
721 layout.Draw(this.render,textRender, (float)x, (float)y);
725 public void BeginClipRect(Rectangle rect)
727 this.render.PushAxisAlignedClip(rect, D2D.AntialiasMode.Aliased);
730 public void EndClipRect()
732 this.render.PopAxisAlignedClip();
735 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
739 layout.SetDrawingEffect(this.Brushes.Get(this.render, (Color4)color), new DW.TextRange(start, length));
742 public void DrawLine(Point from, Point to)
744 D2D.Brush brush = this.Brushes.Get(this.render, this.Foreground);
745 D2D.StrokeStyle stroke = this.Strokes.Get(D2DRenderShared.D2DFactory,HilightType.Sold);
746 this.render.DrawLine(from, to, brush, 1.0f, stroke);
749 public const int BoldThickness = 2;
750 public const int NormalThickness = 1;
752 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
754 if (type == HilightType.None)
757 float thickness = isBold ? BoldThickness : NormalThickness;
760 if (effectColor != null)
761 color = (Color4)effectColor;
762 else if (type == HilightType.Select)
763 color = this.Hilight;
765 color = this.Foreground;
767 IMarkerEffecter effecter = null;
768 D2D.SolidColorBrush brush = this.Brushes.Get(this.render, color);
770 if (type == HilightType.Squiggle)
771 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(D2DRenderShared.D2DFactory, HilightType.Squiggle), thickness);
772 else if (type == HilightType.Select)
773 effecter = new HilightMarker(this.render, brush);
774 else if (type == HilightType.None)
777 effecter = new LineMarker(this.render, brush, this.Strokes.Get(D2DRenderShared.D2DFactory,type), thickness);
779 if (effecter != null)
781 bool isUnderline = type != HilightType.Select;
783 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
784 foreach (DW.HitTestMetrics metric in metrics)
786 float offset = isUnderline ? metric.Height : 0;
787 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
792 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges, IEnumerable<Selection> SelectRanges,double WrapWidth)
795 this.GetDpi(out dpix,out dpiy);
797 double layoutWidth = this.TextArea.Width;
798 if (WrapWidth != LineToIndexTable.NONE_BREAK_LINE)
800 this.format.WordWrapping = DW.WordWrapping.Wrap;
801 layoutWidth = WrapWidth;
805 this.format.WordWrapping = DW.WordWrapping.NoWrap;
808 bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
809 MyTextLayout newLayout = new MyTextLayout(D2DRenderShared.DWFactory,
813 this.TextArea.Height,
815 hasNewLine && this._ShowLineBreak);
816 ParseLayout(newLayout, str);
817 if (syntaxCollection != null)
819 foreach (SyntaxInfo s in syntaxCollection)
821 D2D.SolidColorBrush brush = this.Brushes.Get(this.render, this.Foreground);
824 case TokenType.Comment:
825 brush = this.Brushes.Get(this.render, this.Comment);
827 case TokenType.Keyword1:
828 brush = this.Brushes.Get(this.render, this.Keyword1);
830 case TokenType.Keyword2:
831 brush = this.Brushes.Get(this.render, this.Keyword2);
833 case TokenType.Literal:
834 brush = this.Brushes.Get(this.render, this.Literal);
837 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
841 if (MarkerRanges != null)
843 foreach (Marker m in MarkerRanges)
845 if (m.start == -1 || m.length == 0)
847 Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f);
848 if (m.hilight == HilightType.Url)
850 if (m.hilight == HilightType.Select)
851 newLayout.SetDrawingEffect(new SelectedEffect(this.HilightForeground, color, m.isBoldLine), new DW.TextRange(m.start, m.length));
853 newLayout.SetDrawingEffect(new DrawingEffect(m.hilight, color,m.isBoldLine), new DW.TextRange(m.start, m.length));
854 if (m.hilight != HilightType.None && m.hilight != HilightType.Select)
855 newLayout.SetUnderline(true, new DW.TextRange(m.start, m.length));
859 if (SelectRanges != null && this.HilightForeground.Alpha > 0.0)
861 foreach (Selection sel in SelectRanges)
863 if (sel.length == 0 || sel.start == -1)
866 newLayout.SetDrawingEffect(new SelectedEffect(this.HilightForeground, this.Hilight, false), new DW.TextRange(sel.start, sel.length));
870 this.format.WordWrapping = DW.WordWrapping.NoWrap;
875 public List<LineToIndexTableData> BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
877 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
879 this.format.WordWrapping = DW.WordWrapping.Wrap;
881 foreach (string str in doc.GetLines(startIndex, endIndex))
883 DW.TextLayout layout = new DW.TextLayout(D2DRenderShared.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
886 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
888 if (metrics.Length == 0 && metrics.NewlineLength == 0)
891 bool lineend = false;
892 if (metrics.NewlineLength > 0)
895 output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
896 i += Util.RoundUp(metrics.Length);
901 startIndex += str.Length;
904 this.format.WordWrapping = DW.WordWrapping.NoWrap;
906 if (output.Count > 0)
907 output.Last().LineEnd = true;
912 bool _Disposed = false;
913 public void Dispose()
918 this.HiddenChars.Clear();
919 if (this.format != null)
920 this.format.Dispose();
922 this._Disposed = true;
925 protected virtual void Dispose(bool dispose)
929 public virtual void GetDpi(out float dpix,out float dpiy)
931 throw new NotImplementedException();
934 public double GetScale()
937 this.GetDpi(out dpi, out dpi);
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.render, this.ControlChar), new DW.TextRange(i, 1));
952 layout.SetLineBreakBrush(this.Brushes.Get(this.render, this.ControlChar));