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(' ');
302 public bool ShowHalfSpace
306 if (this.HiddenChars == null)
309 return this.HiddenChars.ContainsSymbol(' ');
313 if (this.HiddenChars == null)
314 throw new InvalidOperationException();
316 this.HiddenChars.AddSymbol(' ', 'ロ');
318 this.HiddenChars.RemoveSymbol(' ');
326 if (this.HiddenChars == null)
329 return this.HiddenChars.ContainsSymbol('\t');
333 if (this.HiddenChars == null)
334 throw new InvalidOperationException();
336 this.HiddenChars.AddSymbol('\t', '>');
338 this.HiddenChars.RemoveSymbol('\t');
342 public bool ShowLineBreak
346 return this._ShowLineBreak;
350 this._ShowLineBreak = value;
354 public Color4 Foreground
358 return this._Forground;
362 if (this.render == null)
364 this._Forground = value;
365 if (this.textRender != null)
366 this.textRender.DefaultFore = value;
367 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
371 public Color4 Background
377 public Color4 InsertCaret
383 public Color4 OverwriteCaret
389 public Color4 LineMarker
395 public Color4 UpdateArea
401 public Color4 LineNumber
407 public Color4 ControlChar
411 return this._ControlChar;
415 this._ControlChar = value;
416 if (this.HiddenChars != null)
417 this.HiddenChars.Fore = value;
418 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
431 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
435 public Color4 Hilight
439 return this._Hilight;
443 this._Hilight = value;
447 public Color4 Comment
451 return this._Comment;
455 this._Comment = value;
456 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
460 public Color4 Literal
464 return this._Literal;
468 this._Literal = value;
469 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
473 public Color4 Keyword1
477 return this._Keyword1;
481 this._Keyword1 = value;
482 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
486 public Color4 Keyword2
490 return this._Keyword2;
494 this._Keyword2 = value;
495 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
499 public Rectangle TextArea
505 public double LineNemberWidth
509 return this.emSize.Width * EditView.LineNumberLength;
513 public double FoldingWidth
519 public int TabWidthChar
521 get { return this.tabLength; }
526 this.tabLength = value;
527 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
528 float width = (float)(layout.Metrics.Width * value);
529 this.HiddenChars.TabWidth = width;
530 this.format.IncrementalTabStop = width;
540 public void DrawGripper(Point p, double radius)
542 D2D.Ellipse ellipse = new D2D.Ellipse();
544 ellipse.RadiusX = (float)radius;
545 ellipse.RadiusY = (float)radius;
546 this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
547 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
550 public void ReConstructDeviceResource()
552 this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height);
555 public void ReConstructDeviceResource(double width, double height)
557 this.DestructDeviceResource();
558 this.ConstructDeviceResource(width, height);
561 public virtual void DrawCachedBitmap(Rectangle rect)
563 if (this.render == null || this.render.IsDisposed)
565 render.DrawBitmap(this.bitmap, rect, 1.0f, D2D.BitmapInterpolationMode.Linear, rect);
568 public virtual void CacheContent()
570 if (this.render == null || this.bitmap == null || this.bitmap.IsDisposed || this.render.IsDisposed)
573 this.bitmap.CopyFromRenderTarget(this.render, new SharpDX.Point(), new SharpDX.Rectangle(0, 0, (int)this.renderSize.Width, (int)this.renderSize.Height));
574 this.hasCache = true;
577 public virtual bool IsVaildCache()
579 return this.hasCache;
582 public virtual void BegineDraw()
584 if (this.render == null || this.render.IsDisposed)
586 this.render.BeginDraw();
587 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
590 public virtual void EndDraw()
592 if (this.render == null || this.render.IsDisposed)
594 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
595 this.render.EndDraw();
598 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground)
600 if (this.render == null || this.render.IsDisposed)
603 D2D.SolidColorBrush brush;
606 case StringColorType.Forground:
607 brush = this.Brushes.Get(this.Foreground);
609 case StringColorType.LineNumber:
610 brush = this.Brushes.Get(this.LineNumber);
613 throw new ArgumentOutOfRangeException();
615 this.GetDpi(out dpix, out dpiy);
616 MyTextLayout layout = new MyTextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
617 layout.StringAlignment = align;
618 layout.Draw(this.render, (float)x, (float)y, brush);
622 public void DrawFoldingMark(bool expand, double x, double y)
624 string mark = expand ? "-" : "+";
625 this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
628 public void FillRectangle(Rectangle rect,FillRectType type)
630 if (this.render == null || this.render.IsDisposed)
632 D2D.SolidColorBrush brush = null;
635 case FillRectType.OverwriteCaret:
636 brush = this.Brushes.Get(this.OverwriteCaret);
637 this.render.FillRectangle(rect, brush);
639 case FillRectType.InsertCaret:
640 brush = this.Brushes.Get(this.InsertCaret);
641 this.render.FillRectangle(rect, brush);
643 case FillRectType.InsertPoint:
644 brush = this.Brushes.Get(this.Hilight);
645 this.render.FillRectangle(rect, brush);
647 case FillRectType.LineMarker:
648 brush = this.Brushes.Get(this.LineMarker);
649 this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness);
651 case FillRectType.UpdateArea:
652 brush = this.Brushes.Get(this.UpdateArea);
653 this.render.FillRectangle(rect, brush);
658 public void FillBackground(Rectangle rect)
660 if (this.render == null || this.render.IsDisposed)
662 this.render.Clear(this.Background);
665 public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable<Selection> SelectRanges,PreDrawOneLineHandler PreDrawOneLine)
667 int lineLength = lti.GetLengthFromLineNumber(row);
669 if (lineLength == 0 || this.render == null || this.render.IsDisposed)
672 MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
674 this.render.PushAxisAlignedClip(this.TextArea, D2D.AntialiasMode.Aliased);
676 if(PreDrawOneLine != null)
677 PreDrawOneLine(layout);
679 if (SelectRanges != null)
681 foreach (Selection sel in SelectRanges)
683 if (sel.length == 0 || sel.start == -1)
686 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
690 layout.Draw(textRender, (float)x, (float)y);
692 this.render.PopAxisAlignedClip();
695 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
699 layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
702 public void DrawLine(Point from, Point to)
704 D2D.Brush brush = this.Brushes.Get(this.Foreground);
705 D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold);
706 this.render.DrawLine(from, to, brush, 1.0f, stroke);
709 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
711 if (type == HilightType.None)
714 float thickness = isBold ? 2 : 1;
717 if (effectColor != null)
718 color = (Color4)effectColor;
719 else if (type == HilightType.Select)
720 color = this.Hilight;
722 color = this.Foreground;
724 IMarkerEffecter effecter = null;
725 D2D.SolidColorBrush brush = this.Brushes.Get(color);
727 if (type == HilightType.Squiggle)
728 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness);
729 else if (type == HilightType.Select)
730 effecter = new HilightMarker(this.render, brush);
731 else if (type == HilightType.None)
734 effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness);
736 if (effecter != null)
738 bool isUnderline = type != HilightType.Select;
740 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
741 foreach (DW.HitTestMetrics metric in metrics)
743 float offset = isUnderline ? metric.Height : 0;
744 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
749 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges)
752 this.GetDpi(out dpix,out dpiy);
754 bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
755 MyTextLayout newLayout = new MyTextLayout(this.DWFactory,
759 this.TextArea.Height,
761 hasNewLine && this._ShowLineBreak);
762 ParseLayout(newLayout, str);
763 if (syntaxCollection != null)
765 foreach (SyntaxInfo s in syntaxCollection)
767 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
770 case TokenType.Comment:
771 brush = this.Brushes.Get(this.Comment);
773 case TokenType.Keyword1:
774 brush = this.Brushes.Get(this.Keyword1);
776 case TokenType.Keyword2:
777 brush = this.Brushes.Get(this.Keyword2);
779 case TokenType.Literal:
780 brush = this.Brushes.Get(this.Literal);
783 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
787 if (MarkerRanges != null)
789 foreach (Marker m in MarkerRanges)
791 if (m.start == -1 || m.length == 0)
793 Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f);
794 if (m.hilight == HilightType.Url)
796 newLayout.SetDrawingEffect(this.Effects.Get(color, m.hilight), new DW.TextRange(m.start, m.length));
803 public List<LineToIndexTableData> BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
805 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
807 this.format.WordWrapping = DW.WordWrapping.Wrap;
809 foreach (string str in doc.GetLines(startIndex, endIndex))
811 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
814 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
816 if (metrics.Length == 0 && metrics.NewlineLength == 0)
819 bool lineend = false;
820 if (metrics.NewlineLength > 0)
823 output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
824 i += Util.RoundUp(metrics.Length);
829 startIndex += str.Length;
832 this.format.WordWrapping = DW.WordWrapping.NoWrap;
834 if (output.Count > 0)
835 output.Last().LineEnd = true;
840 public virtual void Dispose()
842 this.DestructDeviceResource();
843 this.HiddenChars.Clear();
844 if (this.format != null)
845 this.format.Dispose();
846 if(this.DWFactory != null)
847 this.DWFactory.Dispose();
848 if(this.D2DFactory != null)
849 this.D2DFactory.Dispose();
852 public void ConstructDeviceResource(double width, double height)
855 this.GetDpi(out dpiX, out dpiY);
856 D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
857 D2D.RenderTargetType.Default,
858 new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore),
861 D2D.RenderTargetUsage.None,
862 D2D.FeatureLevel.Level_DEFAULT);
864 this.render = this.ConstructRender(this.D2DFactory,prop,width,height);
866 this.Brushes.Render = this.render;
868 D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
871 bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore);
872 this.bitmap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
873 this.hasCache = false;
875 this.ConstrctedResource();
877 this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground);
879 this.renderSize.Width = width;
880 this.renderSize.Height = 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.bitmap != null)
919 this.bitmap.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));