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 Background
359 public Color4 InsertCaret
365 public Color4 OverwriteCaret
371 public Color4 LineMarker
377 public Color4 UpdateArea
383 public Color4 LineNumber
389 public Color4 ControlChar
393 return this._ControlChar;
397 this._ControlChar = value;
398 if (this.HiddenChars != null)
399 this.HiddenChars.Fore = value;
400 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
413 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
417 public Color4 Hilight
421 return this._Hilight;
425 this._Hilight = value;
429 public Color4 Comment
433 return this._Comment;
437 this._Comment = value;
438 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
442 public Color4 Literal
446 return this._Literal;
450 this._Literal = value;
451 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
455 public Color4 Keyword1
459 return this._Keyword1;
463 this._Keyword1 = value;
464 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
468 public Color4 Keyword2
472 return this._Keyword2;
476 this._Keyword2 = value;
477 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
481 public Rectangle TextArea
487 public double LineNemberWidth
491 return this.emSize.Width * EditView.LineNumberLength;
495 public double FoldingWidth
501 public int TabWidthChar
503 get { return this.tabLength; }
508 this.tabLength = value;
509 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
510 float width = (float)(layout.Metrics.Width * value);
511 this.HiddenChars.TabWidth = width;
512 this.format.IncrementalTabStop = width;
522 public void DrawGripper(Point p, double radius)
524 D2D.Ellipse ellipse = new D2D.Ellipse();
526 ellipse.RadiusX = (float)radius;
527 ellipse.RadiusY = (float)radius;
528 this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
529 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
532 public void ReConstructDeviceResource()
534 this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height);
537 public void ReConstructDeviceResource(double width, double height)
539 this.DestructDeviceResource();
540 this.ConstructDeviceResource(width, height);
543 public virtual void DrawCachedBitmap(Rectangle rect)
547 public virtual void CacheContent()
551 public virtual bool IsVaildCache()
553 return this.hasCache;
556 protected void BegineDraw()
558 if (this.render == null || this.render.IsDisposed)
560 this.render.BeginDraw();
561 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
564 protected void EndDraw()
566 if (this.render == null || this.render.IsDisposed)
568 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
569 this.render.EndDraw();
572 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground)
574 if (this.render == null || this.render.IsDisposed)
577 D2D.SolidColorBrush brush;
580 case StringColorType.Forground:
581 brush = this.Brushes.Get(this.Foreground);
583 case StringColorType.LineNumber:
584 brush = this.Brushes.Get(this.LineNumber);
587 throw new ArgumentOutOfRangeException();
589 this.GetDpi(out dpix, out dpiy);
590 MyTextLayout layout = new MyTextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
591 layout.StringAlignment = align;
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 if (this.render == null || this.render.IsDisposed)
606 D2D.SolidColorBrush brush = null;
609 case FillRectType.OverwriteCaret:
610 brush = this.Brushes.Get(this.OverwriteCaret);
611 this.render.FillRectangle(rect, brush);
613 case FillRectType.InsertCaret:
614 brush = this.Brushes.Get(this.InsertCaret);
615 this.render.FillRectangle(rect, brush);
617 case FillRectType.InsertPoint:
618 brush = this.Brushes.Get(this.Hilight);
619 this.render.FillRectangle(rect, brush);
621 case FillRectType.LineMarker:
622 brush = this.Brushes.Get(this.LineMarker);
623 this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness);
625 case FillRectType.UpdateArea:
626 brush = this.Brushes.Get(this.UpdateArea);
627 this.render.FillRectangle(rect, brush);
632 public void FillBackground(Rectangle rect)
634 if (this.render == null || this.render.IsDisposed)
636 this.render.Clear(this.Background);
639 public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable<Selection> SelectRanges,PreDrawOneLineHandler PreDrawOneLine)
641 int lineLength = lti.GetLengthFromLineNumber(row);
643 if (lineLength == 0 || this.render == null || this.render.IsDisposed)
646 MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
648 if(PreDrawOneLine != null)
649 PreDrawOneLine(layout,lti,row,x,y);
651 if (SelectRanges != null)
653 foreach (Selection sel in SelectRanges)
655 if (sel.length == 0 || sel.start == -1)
658 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
662 layout.Draw(textRender, (float)x, (float)y);
666 public void BeginClipRect(Rectangle rect)
668 this.render.PushAxisAlignedClip(rect, D2D.AntialiasMode.Aliased);
671 public void EndClipRect()
673 this.render.PopAxisAlignedClip();
676 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
680 layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
683 public void DrawLine(Point from, Point to)
685 D2D.Brush brush = this.Brushes.Get(this.Foreground);
686 D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold);
687 this.render.DrawLine(from, to, brush, 1.0f, stroke);
690 public const int BoldThickness = 2;
691 public const int NormalThickness = 1;
693 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
695 if (type == HilightType.None)
698 float thickness = isBold ? BoldThickness : NormalThickness;
701 if (effectColor != null)
702 color = (Color4)effectColor;
703 else if (type == HilightType.Select)
704 color = this.Hilight;
706 color = this.Foreground;
708 IMarkerEffecter effecter = null;
709 D2D.SolidColorBrush brush = this.Brushes.Get(color);
711 if (type == HilightType.Squiggle)
712 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness);
713 else if (type == HilightType.Select)
714 effecter = new HilightMarker(this.render, brush);
715 else if (type == HilightType.None)
718 effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness);
720 if (effecter != null)
722 bool isUnderline = type != HilightType.Select;
724 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
725 foreach (DW.HitTestMetrics metric in metrics)
727 float offset = isUnderline ? metric.Height : 0;
728 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
733 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges)
736 this.GetDpi(out dpix,out dpiy);
738 bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
739 MyTextLayout newLayout = new MyTextLayout(this.DWFactory,
743 this.TextArea.Height,
745 hasNewLine && this._ShowLineBreak);
746 ParseLayout(newLayout, str);
747 if (syntaxCollection != null)
749 foreach (SyntaxInfo s in syntaxCollection)
751 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
754 case TokenType.Comment:
755 brush = this.Brushes.Get(this.Comment);
757 case TokenType.Keyword1:
758 brush = this.Brushes.Get(this.Keyword1);
760 case TokenType.Keyword2:
761 brush = this.Brushes.Get(this.Keyword2);
763 case TokenType.Literal:
764 brush = this.Brushes.Get(this.Literal);
767 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
771 if (MarkerRanges != null)
773 foreach (Marker m in MarkerRanges)
775 if (m.start == -1 || m.length == 0)
777 Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f);
778 if (m.hilight == HilightType.Url)
780 newLayout.SetDrawingEffect(new DrawingEffect(m.hilight, color,m.isBoldLine), new DW.TextRange(m.start, m.length));
781 if (m.hilight != HilightType.None && m.hilight != HilightType.Select)
782 newLayout.SetUnderline(true, new DW.TextRange(m.start, m.length));
789 public List<LineToIndexTableData> BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
791 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
793 this.format.WordWrapping = DW.WordWrapping.Wrap;
795 foreach (string str in doc.GetLines(startIndex, endIndex))
797 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
800 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
802 if (metrics.Length == 0 && metrics.NewlineLength == 0)
805 bool lineend = false;
806 if (metrics.NewlineLength > 0)
809 output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
810 i += Util.RoundUp(metrics.Length);
815 startIndex += str.Length;
818 this.format.WordWrapping = DW.WordWrapping.NoWrap;
820 if (output.Count > 0)
821 output.Last().LineEnd = true;
826 public virtual void Dispose()
828 this.DestructDeviceResource();
829 this.HiddenChars.Clear();
830 if (this.format != null)
831 this.format.Dispose();
832 if(this.DWFactory != null)
833 this.DWFactory.Dispose();
834 if(this.D2DFactory != null)
835 this.D2DFactory.Dispose();
838 public void ConstructDeviceResource(double width, double height)
841 this.GetDpi(out dpiX, out dpiY);
842 D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
843 D2D.RenderTargetType.Default,
844 new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore),
847 D2D.RenderTargetUsage.None,
848 D2D.FeatureLevel.Level_DEFAULT);
850 this.render = this.ConstructRender(this.D2DFactory,prop,width,height);
852 this.Brushes.Render = this.render;
854 D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
857 bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore);
858 this.cachedBitMap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
859 this.hasCache = false;
861 this.ConstrctedResource();
863 this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground);
865 this.renderSize = new Size(width,height);
867 this.TextAntialiasMode = this._TextAntialiasMode;
870 #if METRO || WINDOWS_UWP
871 protected virtual D2D.RenderTarget ConstructRender(D2D.Factory1 factory, D2D.RenderTargetProperties prop, double width, double height)
873 protected virtual D2D.RenderTarget ConstructRender(D2D.Factory factory, D2D.RenderTargetProperties prop, double width, double height)
876 throw new NotImplementedException();
879 protected virtual void ConstrctedResource()
881 throw new NotImplementedException();
884 public virtual void GetDpi(out float dpix,out float dpiy)
886 throw new NotImplementedException();
889 protected virtual void DestructRender()
891 throw new NotImplementedException();
895 protected virtual void ReCreateTarget()
897 throw new NotImplementedException();
900 public void DestructDeviceResource()
902 this.hasCache = false;
903 if (this.cachedBitMap != null)
904 this.cachedBitMap.Dispose();
905 this.Brushes.Clear();
906 this.Strokes.Clear();
907 if (this.textRender != null)
908 this.textRender.Dispose();
909 this.DestructRender();
912 void ParseLayout(MyTextLayout layout, string str)
914 for (int i = 0; i < str.Length; i++)
916 DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str);
917 if (inlineObject != null)
919 layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1));
920 layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1));
923 layout.SetLineBreakBrush(this.Brushes.Get(this.ControlChar));