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
68 if (this.RenderChanged != null)
69 this.RenderChanged(this, null);
73 public D2D.SolidColorBrush Get(Color4 key)
75 if (this.Render == null || this.Render.IsDisposed)
76 throw new InvalidOperationException();
77 D2D.SolidColorBrush brush;
78 bool result = cache.TryGetValue(key, out brush);
81 brush = new D2D.SolidColorBrush(this.Render, key);
82 cache.Add(key, brush);
94 sealed class StrokeCollection
96 ResourceManager<HilightType, D2D.StrokeStyle> cache = new ResourceManager<HilightType, D2D.StrokeStyle>();
98 public D2D.StrokeStyle Get(D2D.RenderTarget render,HilightType type)
100 return this.Get(render.Factory, type);
104 public D2D.StrokeStyle Get(HilightType type)
106 return this.Get(D2DRenderShared.D2DFactory, type);
109 public D2D.StrokeStyle Get(D2D.Factory factory,HilightType type)
111 D2D.StrokeStyle stroke;
112 if (this.cache.TryGetValue(type, out stroke))
115 D2D.StrokeStyleProperties prop = new D2D.StrokeStyleProperties();
116 prop.DashCap = D2D.CapStyle.Flat;
118 prop.DashStyle = D2D.DashStyle.Solid;
119 prop.EndCap = D2D.CapStyle.Flat;
120 prop.LineJoin = D2D.LineJoin.Miter;
122 prop.StartCap = D2D.CapStyle.Flat;
125 case HilightType.Sold:
126 case HilightType.Url:
127 case HilightType.Squiggle:
128 prop.DashStyle = D2D.DashStyle.Solid;
130 case HilightType.Dash:
131 prop.DashStyle = D2D.DashStyle.Dash;
133 case HilightType.DashDot:
134 prop.DashStyle = D2D.DashStyle.DashDot;
136 case HilightType.DashDotDot:
137 prop.DashStyle = D2D.DashStyle.DashDotDot;
139 case HilightType.Dot:
140 prop.DashStyle = D2D.DashStyle.Dot;
143 stroke = new D2D.StrokeStyle(D2DRenderShared.D2DFactory, prop);
144 this.cache.Add(type, stroke);
153 class D2DRenderShared
155 static DW.Factory _DWFactory;
156 static public DW.Factory DWFactory
160 if(_DWFactory == null)
162 _DWFactory = new DW.Factory(DW.FactoryType.Shared);
167 #if METRO || WINDOWS_UWP
168 static D2D.Factory1 _D2DFactory;
170 static public D2D.Factory1 D2DFactory
174 if (_D2DFactory == null)
176 _D2DFactory = new D2D.Factory1(D2D.FactoryType.SingleThreaded);
182 static D2D.Factory _D2DFactory;
183 static public D2D.Factory D2DFactory
187 if (_D2DFactory == null)
189 _D2DFactory = new D2D.Factory(D2D.FactoryType.SingleThreaded);
198 class D2DRenderCommon : IDisposable
200 InlineManager HiddenChars;
201 TextAntialiasMode _TextAntialiasMode;
202 Color4 _ControlChar,_Forground,_URL,_Hilight;
203 DW.TextFormat format;
204 protected CustomTextRenderer textRender;
205 protected D2D.Bitmap cachedBitMap;
207 bool _ShowLineBreak,_RightToLeft;
208 Color4 _Comment, _Literal, _Keyword1, _Keyword2;
209 protected bool hasCache = false;
210 protected Size renderSize;
212 protected ColorBrushCollection Brushes
218 protected StrokeCollection Strokes
224 D2D.RenderTarget _render;
225 protected D2D.RenderTarget render
227 get { return _render; }
231 this.TextAntialiasMode = this._TextAntialiasMode;
232 this.Brushes.Render = this.render;
236 public D2DRenderCommon()
238 this.Brushes = new ColorBrushCollection();
239 this.Strokes = new StrokeCollection();
240 this.ChangedRenderResource += (s, e) => { };
241 this.ChangedRightToLeft += (s, e) => { };
242 this.renderSize = new Size();
245 public event ChangedRenderResourceEventHandler ChangedRenderResource;
247 public event EventHandler ChangedRightToLeft;
249 public const int MiniumeWidth = 40; //これ以上ないと誤操作が起こる
251 public void InitTextFormat(string fontName, float fontSize, DW.FontWeight fontWeigth = DW.FontWeight.Normal,DW.FontStyle fontStyle = DW.FontStyle.Normal)
253 if(this.format != null)
254 this.format.Dispose();
256 if (this.HiddenChars != null)
257 this.HiddenChars = null;
260 this.GetDpi(out dpix, out dpiy);
262 this.format = new DW.TextFormat(D2DRenderShared.DWFactory, fontName, fontWeigth, fontStyle, fontSize);
263 this.format.WordWrapping = DW.WordWrapping.NoWrap;
264 this.format.ReadingDirection = GetDWRightDirect(_RightToLeft);
266 if (this.HiddenChars == null)
267 this.HiddenChars = new InlineManager(D2DRenderShared.DWFactory, this.format, this.ControlChar, this.Brushes);
269 this.HiddenChars.Format = this.format;
271 this.TabWidthChar = this.TabWidthChar;
273 this.hasCache = false;
275 MyTextLayout layout = new MyTextLayout(D2DRenderShared.DWFactory, "0", this.format, float.MaxValue, float.MaxValue, dpix, false);
276 layout.RightToLeft = false;
277 this.emSize = new Size(layout.Width, layout.Height);
280 layout = new MyTextLayout(D2DRenderShared.DWFactory, "+", this.format, float.MaxValue, float.MaxValue, dpix, false);
281 layout.RightToLeft = false;
283 this.FoldingWidth = Math.Max(D2DRenderCommon.MiniumeWidth, layout.Width);
285 this.FoldingWidth = layout.Width;
289 this.ChangedRenderResource(this,new ChangedRenderRsourceEventArgs(ResourceType.Font));
292 DW.ReadingDirection GetDWRightDirect(bool rtl)
294 return rtl ? DW.ReadingDirection.RightToLeft : DW.ReadingDirection.LeftToRight;
297 public bool RightToLeft
305 _RightToLeft = value;
306 this.format.ReadingDirection = GetDWRightDirect(value);
307 this.ChangedRightToLeft(this, null);
311 public TextAntialiasMode TextAntialiasMode
315 return this._TextAntialiasMode;
319 if (this.render == null)
320 throw new InvalidOperationException();
321 this._TextAntialiasMode = value;
322 this.render.TextAntialiasMode = (D2D.TextAntialiasMode)value;
323 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Antialias));
327 public bool ShowFullSpace
331 if (this.HiddenChars == null)
334 return this.HiddenChars.ContainsSymbol(' ');
338 if (this.HiddenChars == null)
339 throw new InvalidOperationException();
341 this.HiddenChars.AddSymbol(' ', '□');
343 this.HiddenChars.RemoveSymbol(' ');
347 public bool ShowHalfSpace
351 if (this.HiddenChars == null)
354 return this.HiddenChars.ContainsSymbol(' ');
358 if (this.HiddenChars == null)
359 throw new InvalidOperationException();
361 this.HiddenChars.AddSymbol(' ', 'ロ');
363 this.HiddenChars.RemoveSymbol(' ');
371 if (this.HiddenChars == null)
374 return this.HiddenChars.ContainsSymbol('\t');
378 if (this.HiddenChars == null)
379 throw new InvalidOperationException();
381 this.HiddenChars.AddSymbol('\t', '>');
383 this.HiddenChars.RemoveSymbol('\t');
387 public bool ShowLineBreak
391 return this._ShowLineBreak;
395 this._ShowLineBreak = value;
399 public Color4 Foreground
403 return this._Forground;
407 if (this.render == null)
409 this._Forground = value;
410 if (this.textRender != null)
411 this.textRender.DefaultFore = value;
412 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
416 public Color4 HilightForeground
422 public Color4 Background
428 public Color4 InsertCaret
434 public Color4 OverwriteCaret
440 public Color4 LineMarker
446 public Color4 UpdateArea
452 public Color4 LineNumber
458 public Color4 ControlChar
462 return this._ControlChar;
466 this._ControlChar = value;
467 if (this.HiddenChars != null)
468 this.HiddenChars.Fore = value;
469 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
482 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
486 public Color4 Hilight
490 return this._Hilight;
494 this._Hilight = value;
498 public Color4 Comment
502 return this._Comment;
506 this._Comment = value;
507 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
511 public Color4 Literal
515 return this._Literal;
519 this._Literal = value;
520 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
524 public Color4 Keyword1
528 return this._Keyword1;
532 this._Keyword1 = value;
533 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
537 public Color4 Keyword2
541 return this._Keyword2;
545 this._Keyword2 = value;
546 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
550 public Rectangle TextArea
556 public double LineNemberWidth
560 return this.emSize.Width * EditView.LineNumberLength;
564 public double FoldingWidth
570 public int TabWidthChar
572 get { return this.tabLength; }
577 this.tabLength = value;
578 DW.TextLayout layout = new DW.TextLayout(D2DRenderShared.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
579 float width = (float)(layout.Metrics.Width * value);
580 this.HiddenChars.TabWidth = width;
581 this.format.IncrementalTabStop = width;
592 public void DrawGripper(Point p, double radius)
594 D2D.Ellipse ellipse = new D2D.Ellipse();
596 ellipse.RadiusX = (float)radius;
597 ellipse.RadiusY = (float)radius;
598 this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
599 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
603 public virtual void DrawCachedBitmap(Rectangle rect)
607 public virtual void CacheContent()
611 public virtual bool IsVaildCache()
613 return this.hasCache;
616 protected void BegineDraw()
618 if (this.render == null || this.render.IsDisposed)
620 this.render.BeginDraw();
621 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
624 protected void EndDraw()
626 if (this.render == null || this.render.IsDisposed)
628 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
629 this.render.EndDraw();
632 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground)
634 if (this.render == null || this.render.IsDisposed)
637 D2D.SolidColorBrush brush;
640 case StringColorType.Forground:
641 brush = this.Brushes.Get(this.Foreground);
643 case StringColorType.LineNumber:
644 brush = this.Brushes.Get(this.LineNumber);
647 throw new ArgumentOutOfRangeException();
649 this.GetDpi(out dpix, out dpiy);
650 MyTextLayout layout = new MyTextLayout(D2DRenderShared.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false);
651 layout.StringAlignment = align;
652 layout.Draw(this.render, (float)x, (float)y, brush);
656 public void DrawFoldingMark(bool expand, double x, double y)
658 string mark = expand ? "-" : "+";
659 this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
662 public void FillRectangle(Rectangle rect,FillRectType type)
664 if (this.render == null || this.render.IsDisposed)
666 D2D.SolidColorBrush brush = null;
669 case FillRectType.OverwriteCaret:
670 brush = this.Brushes.Get(this.OverwriteCaret);
671 this.render.FillRectangle(rect, brush);
673 case FillRectType.InsertCaret:
674 brush = this.Brushes.Get(this.InsertCaret);
675 this.render.FillRectangle(rect, brush);
677 case FillRectType.InsertPoint:
678 brush = this.Brushes.Get(this.Hilight);
679 this.render.FillRectangle(rect, brush);
681 case FillRectType.LineMarker:
682 brush = this.Brushes.Get(this.LineMarker);
683 this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness);
685 case FillRectType.UpdateArea:
686 brush = this.Brushes.Get(this.UpdateArea);
687 this.render.FillRectangle(rect, brush);
692 public void FillBackground(Rectangle rect)
694 if (this.render == null || this.render.IsDisposed)
696 this.render.Clear(this.Background);
699 public void DrawOneLine(Document doc,LineToIndexTable lti, int row, double x, double y, PreDrawOneLineHandler PreDrawOneLine)
701 int lineLength = lti.GetLengthFromLineNumber(row);
703 if (lineLength == 0 || this.render == null || this.render.IsDisposed)
706 MyTextLayout layout = (MyTextLayout)lti.GetLayout(row);
708 if(PreDrawOneLine != null)
709 PreDrawOneLine(layout,lti,row,x,y);
711 if(doc.Selections.Count > 0)
713 int lineIndex = lti.GetIndexFromLineNumber(row);
714 var SelectRanges = from s in doc.Selections.Get(lineIndex, lineLength)
715 let n = Util.ConvertAbsIndexToRelIndex(s, lineIndex, lineLength)
718 if (SelectRanges != null)
720 foreach (Selection sel in SelectRanges)
722 if (sel.length == 0 || sel.start == -1)
725 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
730 layout.Draw(textRender, (float)x, (float)y);
734 public void BeginClipRect(Rectangle rect)
736 this.render.PushAxisAlignedClip(rect, D2D.AntialiasMode.Aliased);
739 public void EndClipRect()
741 this.render.PopAxisAlignedClip();
744 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
748 layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
751 public void DrawLine(Point from, Point to)
753 D2D.Brush brush = this.Brushes.Get(this.Foreground);
754 D2D.StrokeStyle stroke = this.Strokes.Get(D2DRenderShared.D2DFactory,HilightType.Sold);
755 this.render.DrawLine(from, to, brush, 1.0f, stroke);
758 public const int BoldThickness = 2;
759 public const int NormalThickness = 1;
761 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
763 if (type == HilightType.None)
766 float thickness = isBold ? BoldThickness : NormalThickness;
769 if (effectColor != null)
770 color = (Color4)effectColor;
771 else if (type == HilightType.Select)
772 color = this.Hilight;
774 color = this.Foreground;
776 IMarkerEffecter effecter = null;
777 D2D.SolidColorBrush brush = this.Brushes.Get(color);
779 if (type == HilightType.Squiggle)
780 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(D2DRenderShared.D2DFactory, HilightType.Squiggle), thickness);
781 else if (type == HilightType.Select)
782 effecter = new HilightMarker(this.render, brush);
783 else if (type == HilightType.None)
786 effecter = new LineMarker(this.render, brush, this.Strokes.Get(D2DRenderShared.D2DFactory,type), thickness);
788 if (effecter != null)
790 bool isUnderline = type != HilightType.Select;
792 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
793 foreach (DW.HitTestMetrics metric in metrics)
795 float offset = isUnderline ? metric.Height : 0;
796 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
801 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges, IEnumerable<Selection> SelectRanges)
804 this.GetDpi(out dpix,out dpiy);
806 bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine;
807 MyTextLayout newLayout = new MyTextLayout(D2DRenderShared.DWFactory,
811 this.TextArea.Height,
813 hasNewLine && this._ShowLineBreak);
814 ParseLayout(newLayout, str);
815 if (syntaxCollection != null)
817 foreach (SyntaxInfo s in syntaxCollection)
819 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
822 case TokenType.Comment:
823 brush = this.Brushes.Get(this.Comment);
825 case TokenType.Keyword1:
826 brush = this.Brushes.Get(this.Keyword1);
828 case TokenType.Keyword2:
829 brush = this.Brushes.Get(this.Keyword2);
831 case TokenType.Literal:
832 brush = this.Brushes.Get(this.Literal);
835 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
839 if (MarkerRanges != null)
841 foreach (Marker m in MarkerRanges)
843 if (m.start == -1 || m.length == 0)
845 Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f);
846 if (m.hilight == HilightType.Url)
848 if (m.hilight == HilightType.Select)
849 newLayout.SetDrawingEffect(new SelectedEffect(this.HilightForeground, color, m.isBoldLine), new DW.TextRange(m.start, m.length));
851 newLayout.SetDrawingEffect(new DrawingEffect(m.hilight, color,m.isBoldLine), new DW.TextRange(m.start, m.length));
852 if (m.hilight != HilightType.None && m.hilight != HilightType.Select)
853 newLayout.SetUnderline(true, new DW.TextRange(m.start, m.length));
857 if (SelectRanges != null && this.HilightForeground.Alpha > 0.0)
859 foreach (Selection sel in SelectRanges)
861 if (sel.length == 0 || sel.start == -1)
864 newLayout.SetDrawingEffect(new SelectedEffect(this.HilightForeground, this.Hilight, false), new DW.TextRange(sel.start, sel.length));
871 public List<LineToIndexTableData> BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth)
873 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
875 this.format.WordWrapping = DW.WordWrapping.Wrap;
877 foreach (string str in doc.GetLines(startIndex, endIndex))
879 DW.TextLayout layout = new DW.TextLayout(D2DRenderShared.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
882 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
884 if (metrics.Length == 0 && metrics.NewlineLength == 0)
887 bool lineend = false;
888 if (metrics.NewlineLength > 0)
891 output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null));
892 i += Util.RoundUp(metrics.Length);
897 startIndex += str.Length;
900 this.format.WordWrapping = DW.WordWrapping.NoWrap;
902 if (output.Count > 0)
903 output.Last().LineEnd = true;
908 bool _Disposed = false;
909 public void Dispose()
914 this.HiddenChars.Clear();
915 if (this.format != null)
916 this.format.Dispose();
918 this._Disposed = true;
921 protected virtual void Dispose(bool dispose)
925 public virtual void GetDpi(out float dpix,out float dpiy)
927 throw new NotImplementedException();
930 public double GetScale()
933 this.GetDpi(out dpi, out dpi);
937 void ParseLayout(MyTextLayout layout, string str)
939 for (int i = 0; i < str.Length; i++)
941 DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str);
942 if (inlineObject != null)
944 layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1));
945 layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1));
948 layout.SetLineBreakBrush(this.Brushes.Get(this.ControlChar));