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 int dpix,out int 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;
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 Color4 Foreground
339 return this._Forground;
343 if (this.render == null)
345 this._Forground = value;
346 if (this.textRender != null)
347 this.textRender.DefaultFore = value;
348 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
352 public Color4 Background
358 public Color4 InsertCaret
364 public Color4 OverwriteCaret
370 public Color4 LineMarker
376 public Color4 ControlChar
380 return this._ControlChar;
384 if (this.render == null)
386 this._ControlChar = value;
387 if (this.HiddenChars != null)
388 this.HiddenChars.Fore = value;
389 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
401 if (this.render == null)
405 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
409 public Color4 Hilight
413 return this._Hilight;
417 this._Hilight = value;
421 public Color4 Comment
425 return this._Comment;
429 this._Comment = value;
430 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
434 public Color4 Literal
438 return this._Literal;
442 this._Literal = value;
443 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
447 public Color4 Keyword1
451 return this._Keyword1;
455 this._Keyword1 = value;
456 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
460 public Color4 Keyword2
464 return this._Keyword2;
468 this._Keyword2 = value;
469 this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush));
473 public Rectangle ClipRect
479 public double LineNemberWidth
483 return this.emSize.Width * (EditView.LineNumberLength + 1);
487 public double FoldingWidth
493 public int TabWidthChar
495 get { return this.tabLength; }
498 this.tabLength = value;
499 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue);
500 float width = (float)(layout.Metrics.Width * value);
501 this.HiddenChars.TabWidth = width;
502 this.format.IncrementalTabStop = width;
512 public void DrawGripper(Point p, double radius)
514 D2D.Ellipse ellipse = new D2D.Ellipse();
516 ellipse.RadiusX = (float)radius;
517 ellipse.RadiusY = (float)radius;
518 this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background));
519 this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground));
522 public void ReConstructDeviceResource()
524 this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height);
527 public void ReConstructDeviceResource(double width, double height)
529 this.DestructDeviceResource();
530 this.ConstructDeviceResource(width, height);
533 public void DrawCachedBitmap(Rectangle rect)
535 if (this.render == null || this.render.IsDisposed)
537 render.DrawBitmap(this.bitmap, rect, 1.0f, D2D.BitmapInterpolationMode.Linear, rect);
540 public void CacheContent()
542 if (this.render == null || this.bitmap == null || this.bitmap.IsDisposed || this.render.IsDisposed)
545 this.bitmap.CopyFromRenderTarget(this.render, new SharpDX.Point(), new SharpDX.Rectangle(0, 0, (int)this.renderSize.Width, (int)this.renderSize.Height));
546 this.hasCache = true;
549 public bool IsVaildCache()
551 return this.hasCache;
554 public void BegineDraw()
556 if (this.render == null || this.render.IsDisposed)
558 this.render.BeginDraw();
559 this.render.AntialiasMode = D2D.AntialiasMode.Aliased;
562 public void EndDraw()
564 if (this.render == null || this.render.IsDisposed)
566 this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive;
567 this.render.EndDraw();
570 public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect)
572 if (this.render == null || this.render.IsDisposed)
574 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height);
577 case StringAlignment.Left:
578 layout.TextAlignment = DW.TextAlignment.Leading;
580 case StringAlignment.Center:
581 layout.TextAlignment = DW.TextAlignment.Center;
583 case StringAlignment.Right:
584 layout.TextAlignment = DW.TextAlignment.Trailing;
587 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
588 this.render.DrawTextLayout(new Vector2((float)x, (float)y), layout, brush);
592 public void DrawFoldingMark(bool expand, double x, double y)
594 string mark = expand ? "-" : "+";
595 this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height));
598 public void FillRectangle(Rectangle rect,FillRectType type)
600 const float lineMarkerThickness = 2;
601 if (this.render == null || this.render.IsDisposed)
603 D2D.SolidColorBrush brush = null;
606 case FillRectType.OverwriteCaret:
607 brush = this.Brushes.Get(this.OverwriteCaret);
608 this.render.FillRectangle(rect, brush);
610 case FillRectType.InsertCaret:
611 brush = this.Brushes.Get(this.InsertCaret);
612 this.render.FillRectangle(rect, brush);
614 case FillRectType.InsertPoint:
615 brush = this.Brushes.Get(this.Hilight);
616 this.render.FillRectangle(rect, brush);
618 case FillRectType.LineMarker:
619 brush = this.Brushes.Get(this.LineMarker);
620 this.render.DrawRectangle(rect, brush, lineMarkerThickness);
625 public void FillBackground(Rectangle rect)
627 if (this.render == null || this.render.IsDisposed)
629 this.render.Clear(this.Background);
632 public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable<Selection> SelectRanges,PreDrawOneLineHandler PreDrawOneLine)
634 LineToIndexTableData lineData = lti.GetData(row);
636 if (lineData.Length == 0 || this.render == null || this.render.IsDisposed)
639 MyTextLayout layout = (MyTextLayout)lti.GetData(row).Layout;
641 this.render.PushAxisAlignedClip(this.ClipRect, D2D.AntialiasMode.Aliased);
643 if(PreDrawOneLine != null)
644 PreDrawOneLine(layout);
646 if (SelectRanges != null)
648 foreach (Selection sel in SelectRanges)
650 if (sel.length == 0 || sel.start == -1)
653 this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false);
657 layout.Draw(textRender, (float)x, (float)y);
659 this.render.PopAxisAlignedClip();
662 public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color)
666 layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length));
669 public void DrawLine(Point from, Point to)
671 D2D.Brush brush = this.Brushes.Get(this.Foreground);
672 D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold);
673 this.render.DrawLine(from, to, brush, 1.0f, stroke);
676 public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null)
678 if (type == HilightType.None)
681 float thickness = isBold ? 2 : 1;
684 if (effectColor != null)
685 color = (Color4)effectColor;
686 else if (type == HilightType.Select)
687 color = this.Hilight;
689 color = this.Foreground;
691 IMarkerEffecter effecter = null;
692 D2D.SolidColorBrush brush = this.Brushes.Get(color);
694 if (type == HilightType.Squiggle)
695 effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness);
696 else if (type == HilightType.Select)
697 effecter = new HilightMarker(this.render, brush);
698 else if (type == HilightType.None)
701 effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness);
703 if (effecter != null)
705 bool isUnderline = type != HilightType.Select;
707 DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y);
708 foreach (DW.HitTestMetrics metric in metrics)
710 float offset = isUnderline ? metric.Height : 0;
711 effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height);
716 public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges)
718 MyTextLayout newLayout = new MyTextLayout(new DW.TextLayout(this.DWFactory,
719 //this.HiddenChars.ContainsSymbol('\t') ? str.Replace('\t', '0'): str, //プロポーショナルフォント対策
722 (float)this.ClipRect.Width,
723 (float)this.ClipRect.Height));
724 ParseLayout(newLayout, str);
725 if (syntaxCollection != null)
727 foreach (SyntaxInfo s in syntaxCollection)
729 D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground);
732 case TokenType.Comment:
733 brush = this.Brushes.Get(this.Comment);
735 case TokenType.Keyword1:
736 brush = this.Brushes.Get(this.Keyword1);
738 case TokenType.Keyword2:
739 brush = this.Brushes.Get(this.Keyword2);
741 case TokenType.Literal:
742 brush = this.Brushes.Get(this.Literal);
745 newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length));
749 if (MarkerRanges != null)
751 foreach (Marker m in MarkerRanges)
753 if (m.start == -1 || m.length == 0)
755 Color4 color = new Color4(m.R / 255.0f, m.G / 255.0f, m.B / 255.0f, m.A / 255.0f);
756 if (m.hilight == HilightType.Url)
758 newLayout.SetDrawingEffect(this.Effects.Get(color, m.hilight), new DW.TextRange(m.start, m.length));
765 public List<LineToIndexTableData> BreakLine(Document doc, int startIndex, int endIndex, double wrapwidth)
767 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
769 this.format.WordWrapping = DW.WordWrapping.Wrap;
771 foreach (string str in doc.GetLines(startIndex, endIndex))
773 DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue);
776 foreach (DW.LineMetrics metrics in layout.GetLineMetrics())
778 if (metrics.Length == 0 && metrics.NewlineLength == 0)
781 bool lineend = false;
782 if (metrics.NewlineLength > 0)
785 output.Add(new LineToIndexTableData(i, (int)metrics.Length, lineend, null));
786 i += Util.RoundUp(metrics.Length);
791 startIndex += str.Length;
794 this.format.WordWrapping = DW.WordWrapping.NoWrap;
796 if (output.Count > 0)
797 output.Last().LineEnd = true;
802 public void Dispose()
804 this.DestructDeviceResource();
805 this.HiddenChars.Clear();
806 if (this.format != null)
807 this.format.Dispose();
808 if(this.DWFactory != null)
809 this.DWFactory.Dispose();
810 if(this.D2DFactory != null)
811 this.D2DFactory.Dispose();
814 public void ConstructDeviceResource(double width, double height)
817 this.GetDpi(out dpiX, out dpiY);
818 D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
819 D2D.RenderTargetType.Default,
820 new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore),
823 D2D.RenderTargetUsage.None,
824 D2D.FeatureLevel.Level_DEFAULT);
826 this.render = this.ConstructRender(this.D2DFactory,prop,width,height);
828 this.Brushes.Render = this.render;
830 D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
833 bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore);
834 this.bitmap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
835 this.hasCache = false;
837 this.ConstrctedResource();
839 this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground);
841 this.renderSize.Width = width;
842 this.renderSize.Height = height;
844 this.TextAntialiasMode = this._TextAntialiasMode;
848 public System.Func<D2D.Factory1,D2D.RenderTargetProperties,double,double,D2D.RenderTarget> ConstructRender;
850 public System.Func<D2D.Factory,D2D.RenderTargetProperties,double,double,D2D.RenderTarget> ConstructRender;
852 public System.Action ConstrctedResource;
854 public GetDpiHandler GetDpi;
856 public System.Action DestructRender;
858 public System.Action ReCreateTarget;
860 public void DestructDeviceResource()
862 this.hasCache = false;
863 if (this.bitmap != null)
864 this.bitmap.Dispose();
865 this.Brushes.Clear();
866 this.Strokes.Clear();
867 this.Effects.Clear();
868 if (this.textRender != null)
869 this.textRender.Dispose();
870 this.DestructRender();
873 void ParseLayout(MyTextLayout layout, string str)
875 for (int i = 0; i < str.Length; i++)
877 DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str);
878 if (inlineObject != null)
880 layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1));
881 layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1));