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 sealed 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 void InitTextFormat(string fontName, float fontSize, DW.FontWeight fontWeigth = DW.FontWeight.Normal,DW.FontStyle fontStyle = DW.FontStyle.Normal)
216 if(this.format != null)
217 this.format.Dispose();
219 this.format = new DW.TextFormat(this.DWFactory, fontName,fontWeigth,fontStyle, fontSize / 72.0f * 96.0f);
220 this.format.WordWrapping = DW.WordWrapping.NoWrap;
222 if (this.HiddenChars == null)
224 this.HiddenChars = new InlineManager(this.DWFactory, this.format, this.ControlChar, this.Brushes);
225 this.HiddenChars.TabWidth = this.HiddenChars.TabWidth;
228 this.HiddenChars.Format = this.format;
230 this.hasCache = false;
232 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
233 layout.ReadingDirection = DW.ReadingDirection.LeftToRight;
234 this.emSize = new Size(layout.Metrics.WidthIncludingTrailingWhitespace, layout.Metrics.Height);
236 layout = new DW.TextLayout(this.DWFactory, "+", this.format, float.MaxValue, float.MaxValue);
237 layout.ReadingDirection = DW.ReadingDirection.LeftToRight;
238 this.FoldingWidth = layout.Metrics.Width;
240 this.ChangedRenderResource(this,new ChangedRenderRsourceEventArgs(ResourceType.Font));
243 public bool RightToLeft
247 return this.format.ReadingDirection == DW.ReadingDirection.RightToLeft;
251 this.format.ReadingDirection = value ? DW.ReadingDirection.RightToLeft : DW.ReadingDirection.LeftToRight;
252 this.ChangedRightToLeft(this, null);
256 public TextAntialiasMode TextAntialiasMode
260 return this._TextAntialiasMode;
264 if (this.render == null)
265 throw new InvalidOperationException();
266 this._TextAntialiasMode = value;
267 this.render.TextAntialiasMode = (D2D.TextAntialiasMode)value;
268 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Antialias));
272 public bool ShowFullSpace
276 if (this.HiddenChars == null)
279 return this.HiddenChars.ContainsSymbol(' ');
283 if (this.HiddenChars == null)
284 throw new InvalidOperationException();
286 this.HiddenChars.AddSymbol(' ', '□');
288 this.HiddenChars.RemoveSymbol(' ');
289 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
293 public bool ShowHalfSpace
297 if (this.HiddenChars == null)
300 return this.HiddenChars.ContainsSymbol(' ');
304 if (this.HiddenChars == null)
305 throw new InvalidOperationException();
307 this.HiddenChars.AddSymbol(' ', 'ロ');
309 this.HiddenChars.RemoveSymbol(' ');
310 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
318 if (this.HiddenChars == null)
321 return this.HiddenChars.ContainsSymbol('\t');
325 if (this.HiddenChars == null)
326 throw new InvalidOperationException();
328 this.HiddenChars.AddSymbol('\t', '>');
330 this.HiddenChars.RemoveSymbol('\t');
331 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
335 public bool ShowLineBreak
339 return this._ShowLineBreak;
343 this._ShowLineBreak = value;
344 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar));
348 public Color4 Foreground
352 return this._Forground;
356 if (this.render == null)
358 this._Forground = value;
359 if (this.textRender != null)
360 this.textRender.DefaultFore = value;
361 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
365 public Color4 Background
371 public Color4 InsertCaret
377 public Color4 OverwriteCaret
383 public Color4 LineMarker
389 public Color4 ControlChar
393 return this._ControlChar;
397 if (this.render == null)
399 this._ControlChar = value;
400 if (this.HiddenChars != null)
401 this.HiddenChars.Fore = value;
402 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
414 if (this.render == null)
418 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
422 public Color4 Hilight
426 return this._Hilight;
430 this._Hilight = value;
434 public Color4 Comment
438 return this._Comment;
442 this._Comment = value;
443 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
447 public Color4 Literal
451 return this._Literal;
455 this._Literal = value;
456 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
460 public Color4 Keyword1
464 return this._Keyword1;
468 this._Keyword1 = value;
469 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
473 public Color4 Keyword2
477 return this._Keyword2;
481 this._Keyword2 = value;
482 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
486 public Rectangle ClipRect
492 public double LineNemberWidth
496 return this.emSize.Width * (EditView.LineNumberLength + 1);
500 public double FoldingWidth
506 public int TabWidthChar
508 get { return this.tabLength; }
511 this.tabLength = value;
512 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
513 float width = (float)(layout.Metrics.Width * value);
514 this.HiddenChars.TabWidth = width;
515 this.format.IncrementalTabStop = width;
525 public void DrawGripper(Point p, double radius)
527 D2D.Ellipse ellipse = new D2D.Ellipse();
529 ellipse.RadiusX = (float)radius;
530 ellipse.RadiusY = (float)radius;
531 this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
532 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
535 public void ReConstructDeviceResource()
537 this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height);
540 public void ReConstructDeviceResource(double width, double height)
542 this.DestructDeviceResource();
543 this.ConstructDeviceResource(width, height);
546 public void DrawCachedBitmap(Rectangle rect)
548 if (this.render == null || this.render.IsDisposed)
550 render.DrawBitmap(this.bitmap, rect, 1.0f, D2D.BitmapInterpolationMode.Linear, rect);
553 public void CacheContent()
555 if (this.render == null || this.bitmap == null || this.bitmap.IsDisposed || this.render.IsDisposed)
558 this.bitmap.CopyFromRenderTarget(this.render, new SharpDX.Point(), new SharpDX.Rectangle(0, 0, (int)this.renderSize.Width, (int)this.renderSize.Height));
559 this.hasCache = true;
562 public bool IsVaildCache()
564 return this.hasCache;
567 public void BegineDraw()
569 if (this.render == null || this.render.IsDisposed)
571 this.render.BeginDraw();
572 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
575 public void EndDraw()
577 if (this.render == null || this.render.IsDisposed)
579 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
580 this.render.EndDraw();
583 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect)
585 if (this.render == null || this.render.IsDisposed)
588 this.GetDpi(out dpix,out dpiy);
589 MyTextLayout layout = new MyTextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
590 layout.StringAlignment = align;
591 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
592 layout.Draw(this.render, (float)x, (float)y, brush);
596 public void DrawFoldingMark(bool expand, double x, double y)
598 string mark = expand ? "-" : "+";
599 this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
602 public void FillRectangle(Rectangle rect,FillRectType type)
604 const float lineMarkerThickness = 2;
605 if (this.render == null || this.render.IsDisposed)
607 D2D.SolidColorBrush brush = null;
610 case FillRectType.OverwriteCaret:
611 brush = this.Brushes.Get(this.OverwriteCaret);
612 this.render.FillRectangle(rect, brush);
614 case FillRectType.InsertCaret:
615 brush = this.Brushes.Get(this.InsertCaret);
616 this.render.FillRectangle(rect, brush);
618 case FillRectType.InsertPoint:
619 brush = this.Brushes.Get(this.Hilight);
620 this.render.FillRectangle(rect, brush);
622 case FillRectType.LineMarker:
623 brush = this.Brushes.Get(this.LineMarker);
624 this.render.DrawRectangle(rect, brush, lineMarkerThickness);
629 public void FillBackground(Rectangle rect)
631 if (this.render == null || this.render.IsDisposed)
633 this.render.Clear(this.Background);
636 public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable<Selection> SelectRanges,PreDrawOneLineHandler PreDrawOneLine)
638 int lineLength = lti.GetLengthFromLineNumber(row);
640 if (lineLength == 0 || this.render == null || this.render.IsDisposed)
643 MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
645 this.render.PushAxisAlignedClip(this.ClipRect, D2D.AntialiasMode.Aliased);
647 if(PreDrawOneLine != null)
648 PreDrawOneLine(layout);
650 if (SelectRanges != null)
652 foreach (Selection sel in SelectRanges)
654 if (sel.length == 0 || sel.start == -1)
657 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
661 layout.Draw(textRender, (float)x, (float)y);
663 this.render.PopAxisAlignedClip();
666 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
670 layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
673 public void DrawLine(Point from, Point to)
675 D2D.Brush brush = this.Brushes.Get(this.Foreground);
676 D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold);
677 this.render.DrawLine(from, to, brush, 1.0f, stroke);
680 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
682 if (type == HilightType.None)
685 float thickness = isBold ? 2 : 1;
688 if (effectColor != null)
689 color = (Color4)effectColor;
690 else if (type == HilightType.Select)
691 color = this.Hilight;
693 color = this.Foreground;
695 IMarkerEffecter effecter = null;
696 D2D.SolidColorBrush brush = this.Brushes.Get(color);
698 if (type == HilightType.Squiggle)
699 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness);
700 else if (type == HilightType.Select)
701 effecter = new HilightMarker(this.render, brush);
702 else if (type == HilightType.None)
705 effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness);
707 if (effecter != null)
709 bool isUnderline = type != HilightType.Select;
711 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
712 foreach (DW.HitTestMetrics metric in metrics)
714 float offset = isUnderline ? metric.Height : 0;
715 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
720 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges)
723 this.GetDpi(out dpix,out dpiy);
725 bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
726 MyTextLayout newLayout = new MyTextLayout(this.DWFactory,
730 this.ClipRect.Height,
732 hasNewLine && this._ShowLineBreak);
733 ParseLayout(newLayout, str);
734 if (syntaxCollection != null)
736 foreach (SyntaxInfo s in syntaxCollection)
738 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
741 case TokenType.Comment:
742 brush = this.Brushes.Get(this.Comment);
744 case TokenType.Keyword1:
745 brush = this.Brushes.Get(this.Keyword1);
747 case TokenType.Keyword2:
748 brush = this.Brushes.Get(this.Keyword2);
750 case TokenType.Literal:
751 brush = this.Brushes.Get(this.Literal);
754 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
758 if (MarkerRanges != null)
760 foreach (Marker m in MarkerRanges)
762 if (m.start == -1 || m.length == 0)
764 Color4 color = new Color4(m.R / 255.0f, m.G / 255.0f, m.B / 255.0f, m.A / 255.0f);
765 if (m.hilight == HilightType.Url)
767 newLayout.SetDrawingEffect(this.Effects.Get(color, m.hilight), new DW.TextRange(m.start, m.length));
774 public List<LineToIndexTableData> BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
776 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
778 this.format.WordWrapping = DW.WordWrapping.Wrap;
780 foreach (string str in doc.GetLines(startIndex, endIndex))
782 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
785 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
787 if (metrics.Length == 0 && metrics.NewlineLength == 0)
790 bool lineend = false;
791 if (metrics.NewlineLength > 0)
794 output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
795 i += Util.RoundUp(metrics.Length);
800 startIndex += str.Length;
803 this.format.WordWrapping = DW.WordWrapping.NoWrap;
805 if (output.Count > 0)
806 output.Last().LineEnd = true;
811 public void Dispose()
813 this.DestructDeviceResource();
814 this.HiddenChars.Clear();
815 if (this.format != null)
816 this.format.Dispose();
817 if(this.DWFactory != null)
818 this.DWFactory.Dispose();
819 if(this.D2DFactory != null)
820 this.D2DFactory.Dispose();
823 public void ConstructDeviceResource(double width, double height)
826 this.GetDpi(out dpiX, out dpiY);
827 D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
828 D2D.RenderTargetType.Default,
829 new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore),
832 D2D.RenderTargetUsage.None,
833 D2D.FeatureLevel.Level_DEFAULT);
835 this.render = this.ConstructRender(this.D2DFactory,prop,width,height);
837 this.Brushes.Render = this.render;
839 D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
842 bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore);
843 this.bitmap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
844 this.hasCache = false;
846 this.ConstrctedResource();
848 this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground);
850 this.renderSize.Width = width;
851 this.renderSize.Height = height;
853 this.TextAntialiasMode = this._TextAntialiasMode;
857 public System.Func<D2D.Factory1,D2D.RenderTargetProperties,double,double,D2D.RenderTarget> ConstructRender;
859 public System.Func<D2D.Factory,D2D.RenderTargetProperties,double,double,D2D.RenderTarget> ConstructRender;
861 public System.Action ConstrctedResource;
863 public GetDpiHandler GetDpi;
865 public System.Action DestructRender;
867 public System.Action ReCreateTarget;
869 public void DestructDeviceResource()
871 this.hasCache = false;
872 if (this.bitmap != null)
873 this.bitmap.Dispose();
874 this.Brushes.Clear();
875 this.Strokes.Clear();
876 this.Effects.Clear();
877 if (this.textRender != null)
878 this.textRender.Dispose();
879 this.DestructRender();
882 void ParseLayout(MyTextLayout layout, string str)
884 for (int i = 0; i < str.Length; i++)
886 DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str);
887 if (inlineObject != null)
889 layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1));
890 layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1));
893 layout.SetLineBreakBrush(this.Brushes.Get(this.ControlChar));