/* * Copyright (C) 2013 FooProject * * 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 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using FooEditEngine; using SharpDX; using D2D = SharpDX.Direct2D1; using DW = SharpDX.DirectWrite; using DXGI = SharpDX.DXGI; using System.Runtime.InteropServices; namespace FooEditEngine { delegate void PreDrawOneLineHandler(MyTextLayout layout); delegate void GetDpiHandler(out float dpix,out float dpiy); /// /// 文字列のアンチエイリアシングモードを指定する /// public enum TextAntialiasMode { /// /// 最適なものが選択されます /// Default = D2D.TextAntialiasMode.Default, /// /// ClearTypeでアンチエイリアシングを行います /// ClearType = D2D.TextAntialiasMode.Cleartype, /// /// グレイスケールモードでアンチエイリアシングを行います /// GrayScale = D2D.TextAntialiasMode.Grayscale, /// /// アンチエイリアシングを行いません /// Aliased = D2D.TextAntialiasMode.Aliased, } sealed class ColorBrushCollection { ResourceManager cache = new ResourceManager(); D2D.RenderTarget _render; public event EventHandler RenderChanged; public D2D.RenderTarget Render { get { return this._render; } set { this._render = value; if (this.RenderChanged != null) this.RenderChanged(this, null); } } public D2D.SolidColorBrush Get(Color4 key) { if (this.Render == null || this.Render.IsDisposed) throw new InvalidOperationException(); D2D.SolidColorBrush brush; bool result = cache.TryGetValue(key, out brush); if (!result) { brush = new D2D.SolidColorBrush(this.Render, key); cache.Add(key, brush); } return brush; } public void Clear() { cache.Clear(); } } sealed class StrokeCollection { ResourceManager cache = new ResourceManager(); public D2D.Factory Factory; public D2D.StrokeStyle Get(HilightType type) { if(this.Factory == null || this.Factory.IsDisposed) throw new InvalidOperationException(); D2D.StrokeStyle stroke; if (this.cache.TryGetValue(type, out stroke)) return stroke; D2D.StrokeStyleProperties prop = new D2D.StrokeStyleProperties(); prop.DashCap = D2D.CapStyle.Flat; prop.DashOffset = 0; prop.DashStyle = D2D.DashStyle.Solid; prop.EndCap = D2D.CapStyle.Flat; prop.LineJoin = D2D.LineJoin.Miter; prop.MiterLimit = 0; prop.StartCap = D2D.CapStyle.Flat; switch (type) { case HilightType.Sold: case HilightType.Url: case HilightType.Squiggle: prop.DashStyle = D2D.DashStyle.Solid; break; case HilightType.Dash: prop.DashStyle = D2D.DashStyle.Dash; break; case HilightType.DashDot: prop.DashStyle = D2D.DashStyle.DashDot; break; case HilightType.DashDotDot: prop.DashStyle = D2D.DashStyle.DashDotDot; break; case HilightType.Dot: prop.DashStyle = D2D.DashStyle.Dot; break; } stroke = new D2D.StrokeStyle(this.Factory, prop); this.cache.Add(type, stroke); return stroke; } public void Clear() { cache.Clear(); } } sealed class EffectCollection { ResourceManager> cache = new ResourceManager>(); public DrawingEffect Get(Color4 color, HilightType type) { ResourceManager hilights; DrawingEffect effect; if (this.cache.TryGetValue(color, out hilights)) { if (hilights.TryGetValue(type, out effect)) return effect; effect = new DrawingEffect(type, color); hilights.Add(type, effect); return effect; } effect = new DrawingEffect(type, color); hilights = new ResourceManager(); hilights.Add(type, effect); this.cache.Add(color, hilights); return effect; } public void Clear() { foreach (ResourceManager hilights in this.cache.Values) hilights.Clear(); cache.Clear(); } } class D2DRenderCommon : IDisposable { ColorBrushCollection Brushes = new ColorBrushCollection(); StrokeCollection Strokes = new StrokeCollection(); EffectCollection Effects = new EffectCollection(); InlineManager HiddenChars; TextAntialiasMode _TextAntialiasMode; Color4 _ControlChar,_Forground,_URL,_Hilight; DW.Factory DWFactory; #if METRO D2D.Factory1 D2DFactory; #else D2D.Factory D2DFactory; #endif DW.TextFormat format; D2D.Bitmap bitmap; D2D.RenderTarget render; CustomTextRenderer textRender; int tabLength = 8; bool hasCache, _ShowLineBreak; Size renderSize = new Size(); Color4 _Comment, _Literal, _Keyword1, _Keyword2; public D2DRenderCommon() { this.DWFactory = new DW.Factory(DW.FactoryType.Shared); #if METRO this.D2DFactory = new D2D.Factory1(D2D.FactoryType.MultiThreaded); #else this.D2DFactory = new D2D.Factory(D2D.FactoryType.MultiThreaded); #endif this.Strokes.Factory = this.D2DFactory; this.ChangedRenderResource += (s, e) => { }; this.ChangedRightToLeft += (s, e) => { }; } public event ChangedRenderResourceEventHandler ChangedRenderResource; public event EventHandler ChangedRightToLeft; public const int MiniumeWidth = 40; //これ以上ないと誤操作が起こる public void InitTextFormat(string fontName, float fontSize, DW.FontWeight fontWeigth = DW.FontWeight.Normal,DW.FontStyle fontStyle = DW.FontStyle.Normal) { if(this.format != null) this.format.Dispose(); float dpix, dpiy; this.GetDpi(out dpix, out dpiy); this.format = new DW.TextFormat(this.DWFactory, fontName, fontWeigth, fontStyle, fontSize); this.format.WordWrapping = DW.WordWrapping.NoWrap; if (this.HiddenChars == null) this.HiddenChars = new InlineManager(this.DWFactory, this.format, this.ControlChar, this.Brushes); else this.HiddenChars.Format = this.format; this.TabWidthChar = this.TabWidthChar; this.hasCache = false; MyTextLayout layout = new MyTextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue, dpix, false); layout.RightToLeft = false; this.emSize = new Size(layout.Width, layout.Height); layout.Dispose(); layout = new MyTextLayout(this.DWFactory, "+", this.format, float.MaxValue, float.MaxValue, dpix, false); layout.RightToLeft = false; #if METRO this.FoldingWidth = Math.Max(D2DRenderCommon.MiniumeWidth, layout.Width); #else this.FoldingWidth = layout.Width; #endif layout.Dispose(); this.ChangedRenderResource(this,new ChangedRenderRsourceEventArgs(ResourceType.Font)); } public bool RightToLeft { get { return this.format.ReadingDirection == DW.ReadingDirection.RightToLeft; } set { this.format.ReadingDirection = value ? DW.ReadingDirection.RightToLeft : DW.ReadingDirection.LeftToRight; this.ChangedRightToLeft(this, null); } } public TextAntialiasMode TextAntialiasMode { get { return this._TextAntialiasMode; } set { if (this.render == null) throw new InvalidOperationException(); this._TextAntialiasMode = value; this.render.TextAntialiasMode = (D2D.TextAntialiasMode)value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Antialias)); } } public bool ShowFullSpace { get { if (this.HiddenChars == null) return false; else return this.HiddenChars.ContainsSymbol(' '); } set { if (this.HiddenChars == null) throw new InvalidOperationException(); if (value) this.HiddenChars.AddSymbol(' ', '□'); else this.HiddenChars.RemoveSymbol(' '); this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar)); } } public bool ShowHalfSpace { get { if (this.HiddenChars == null) return false; else return this.HiddenChars.ContainsSymbol(' '); } set { if (this.HiddenChars == null) throw new InvalidOperationException(); if (value) this.HiddenChars.AddSymbol(' ', 'ロ'); else this.HiddenChars.RemoveSymbol(' '); this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar)); } } public bool ShowTab { get { if (this.HiddenChars == null) return false; else return this.HiddenChars.ContainsSymbol('\t'); } set { if (this.HiddenChars == null) throw new InvalidOperationException(); if (value) this.HiddenChars.AddSymbol('\t', '>'); else this.HiddenChars.RemoveSymbol('\t'); this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar)); } } public bool ShowLineBreak { get { return this._ShowLineBreak; } set { this._ShowLineBreak = value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.InlineChar)); } } public Color4 Foreground { get { return this._Forground; } set { if (this.render == null) return; this._Forground = value; if (this.textRender != null) this.textRender.DefaultFore = value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush)); } } public Color4 Background { get; set; } public Color4 InsertCaret { get; set; } public Color4 OverwriteCaret { get; set; } public Color4 LineMarker { get; set; } public Color4 UpdateArea { get; set; } public Color4 LineNumber { get; set; } public Color4 ControlChar { get { return this._ControlChar; } set { this._ControlChar = value; if (this.HiddenChars != null) this.HiddenChars.Fore = value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush)); } } public Color4 Url { get { return this._URL; } set { this._URL = value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush)); } } public Color4 Hilight { get { return this._Hilight; } set { this._Hilight = value; } } public Color4 Comment { get { return this._Comment; } set { this._Comment = value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush)); } } public Color4 Literal { get { return this._Literal; } set { this._Literal = value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush)); } } public Color4 Keyword1 { get { return this._Keyword1; } set { this._Keyword1 = value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush)); } } public Color4 Keyword2 { get { return this._Keyword2; } set { this._Keyword2 = value; this.ChangedRenderResource(this, new ChangedRenderRsourceEventArgs(ResourceType.Brush)); } } public Rectangle TextArea { get; set; } public double LineNemberWidth { get { return this.emSize.Width * EditView.LineNumberLength; } } public double FoldingWidth { get; private set; } public int TabWidthChar { get { return this.tabLength; } set { if (value == 0) return; this.tabLength = value; DW.TextLayout layout = new DW.TextLayout(this.DWFactory, "0", this.format, float.MaxValue, float.MaxValue); float width = (float)(layout.Metrics.Width * value); this.HiddenChars.TabWidth = width; this.format.IncrementalTabStop = width; } } public Size emSize { get; private set; } public void DrawGripper(Point p, double radius) { D2D.Ellipse ellipse = new D2D.Ellipse(); ellipse.Point = p; ellipse.RadiusX = (float)radius; ellipse.RadiusY = (float)radius; this.render.FillEllipse(ellipse, this.Brushes.Get(this.Background)); this.render.DrawEllipse(ellipse, this.Brushes.Get(this.Foreground)); } public void ReConstructDeviceResource() { this.ReConstructDeviceResource(this.renderSize.Width, this.renderSize.Height); } public void ReConstructDeviceResource(double width, double height) { this.DestructDeviceResource(); this.ConstructDeviceResource(width, height); } public virtual void DrawCachedBitmap(Rectangle rect) { if (this.render == null || this.render.IsDisposed) return; render.DrawBitmap(this.bitmap, rect, 1.0f, D2D.BitmapInterpolationMode.Linear, rect); } public virtual void CacheContent() { if (this.render == null || this.bitmap == null || this.bitmap.IsDisposed || this.render.IsDisposed) return; render.Flush(); this.bitmap.CopyFromRenderTarget(this.render, new SharpDX.Point(), new SharpDX.Rectangle(0, 0, (int)this.renderSize.Width, (int)this.renderSize.Height)); this.hasCache = true; } public virtual bool IsVaildCache() { return this.hasCache; } public virtual void BegineDraw() { if (this.render == null || this.render.IsDisposed) return; this.render.BeginDraw(); this.render.AntialiasMode = D2D.AntialiasMode.Aliased; } public virtual void EndDraw() { if (this.render == null || this.render.IsDisposed) return; this.render.AntialiasMode = D2D.AntialiasMode.PerPrimitive; this.render.EndDraw(); } public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect, StringColorType colorType = StringColorType.Forground) { if (this.render == null || this.render.IsDisposed) return; float dpix, dpiy; D2D.SolidColorBrush brush; switch (colorType) { case StringColorType.Forground: brush = this.Brushes.Get(this.Foreground); break; case StringColorType.LineNumber: brush = this.Brushes.Get(this.LineNumber); break; default: throw new ArgumentOutOfRangeException(); } this.GetDpi(out dpix, out dpiy); MyTextLayout layout = new MyTextLayout(this.DWFactory, str, this.format, (float)layoutRect.Width, (float)layoutRect.Height,dpix,false); layout.StringAlignment = align; layout.Draw(this.render, (float)x, (float)y, brush); layout.Dispose(); } public void DrawFoldingMark(bool expand, double x, double y) { string mark = expand ? "-" : "+"; this.DrawString(mark, x, y,StringAlignment.Left, new Size(this.FoldingWidth, this.emSize.Height)); } public void FillRectangle(Rectangle rect,FillRectType type) { if (this.render == null || this.render.IsDisposed) return; D2D.SolidColorBrush brush = null; switch(type) { case FillRectType.OverwriteCaret: brush = this.Brushes.Get(this.OverwriteCaret); this.render.FillRectangle(rect, brush); break; case FillRectType.InsertCaret: brush = this.Brushes.Get(this.InsertCaret); this.render.FillRectangle(rect, brush); break; case FillRectType.InsertPoint: brush = this.Brushes.Get(this.Hilight); this.render.FillRectangle(rect, brush); break; case FillRectType.LineMarker: brush = this.Brushes.Get(this.LineMarker); this.render.DrawRectangle(rect, brush, EditView.LineMarkerThickness); break; case FillRectType.UpdateArea: brush = this.Brushes.Get(this.UpdateArea); this.render.FillRectangle(rect, brush); break; } } public void FillBackground(Rectangle rect) { if (this.render == null || this.render.IsDisposed) return; this.render.Clear(this.Background); } public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable SelectRanges,PreDrawOneLineHandler PreDrawOneLine) { int lineLength = lti.GetLengthFromLineNumber(row); if (lineLength == 0 || this.render == null || this.render.IsDisposed) return; MyTextLayout layout = (MyTextLayout)lti.GetLayout(row); this.render.PushAxisAlignedClip(this.TextArea, D2D.AntialiasMode.Aliased); if(PreDrawOneLine != null) PreDrawOneLine(layout); if (SelectRanges != null) { foreach (Selection sel in SelectRanges) { if (sel.length == 0 || sel.start == -1) continue; this.DrawMarkerEffect(layout, HilightType.Select, sel.start, sel.length, x, y, false); } } layout.Draw(textRender, (float)x, (float)y); this.render.PopAxisAlignedClip(); } public void SetTextColor(MyTextLayout layout,int start, int length, Color4? color) { if (color == null) return; layout.SetDrawingEffect(this.Brushes.Get((Color4)color), new DW.TextRange(start, length)); } public void DrawLine(Point from, Point to) { D2D.Brush brush = this.Brushes.Get(this.Foreground); D2D.StrokeStyle stroke = this.Strokes.Get(HilightType.Sold); this.render.DrawLine(from, to, brush, 1.0f, stroke); } public void DrawMarkerEffect(MyTextLayout layout, HilightType type, int start, int length, double x, double y, bool isBold, Color4? effectColor = null) { if (type == HilightType.None) return; float thickness = isBold ? 2 : 1; Color4 color; if (effectColor != null) color = (Color4)effectColor; else if (type == HilightType.Select) color = this.Hilight; else color = this.Foreground; IMarkerEffecter effecter = null; D2D.SolidColorBrush brush = this.Brushes.Get(color); if (type == HilightType.Squiggle) effecter = new D2DSquilleLineMarker(this.render, brush, this.Strokes.Get(HilightType.Squiggle), thickness); else if (type == HilightType.Select) effecter = new HilightMarker(this.render, brush); else if (type == HilightType.None) effecter = null; else effecter = new LineMarker(this.render, brush, this.Strokes.Get(type), thickness); if (effecter != null) { bool isUnderline = type != HilightType.Select; DW.HitTestMetrics[] metrics = layout.HitTestTextRange(start, length, (float)x, (float)y); foreach (DW.HitTestMetrics metric in metrics) { float offset = isUnderline ? metric.Height : 0; effecter.Draw(metric.Left, metric.Top + offset, metric.Width, metric.Height); } } } public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable MarkerRanges) { float dpix,dpiy; this.GetDpi(out dpix,out dpiy); bool hasNewLine = str.Length > 0 && str[str.Length - 1] == Document.NewLine; MyTextLayout newLayout = new MyTextLayout(this.DWFactory, str, this.format, this.TextArea.Width, this.TextArea.Height, dpiy, hasNewLine && this._ShowLineBreak); ParseLayout(newLayout, str); if (syntaxCollection != null) { foreach (SyntaxInfo s in syntaxCollection) { D2D.SolidColorBrush brush = this.Brushes.Get(this.Foreground); switch (s.type) { case TokenType.Comment: brush = this.Brushes.Get(this.Comment); break; case TokenType.Keyword1: brush = this.Brushes.Get(this.Keyword1); break; case TokenType.Keyword2: brush = this.Brushes.Get(this.Keyword2); break; case TokenType.Literal: brush = this.Brushes.Get(this.Literal); break; } newLayout.SetDrawingEffect(brush, new DW.TextRange(s.index, s.length)); } } if (MarkerRanges != null) { foreach (Marker m in MarkerRanges) { if (m.start == -1 || m.length == 0) continue; Color4 color = new Color4(m.color.R / 255.0f, m.color.G / 255.0f, m.color.B / 255.0f, m.color.A / 255.0f); if (m.hilight == HilightType.Url) color = this.Url; newLayout.SetDrawingEffect(this.Effects.Get(color, m.hilight), new DW.TextRange(m.start, m.length)); } } return newLayout; } public List BreakLine(Document doc, LineToIndexTable layoutLineCollection, int startIndex, int endIndex, double wrapwidth) { List output = new List(); this.format.WordWrapping = DW.WordWrapping.Wrap; foreach (string str in doc.GetLines(startIndex, endIndex)) { DW.TextLayout layout = new DW.TextLayout(this.DWFactory, str, this.format, (float)wrapwidth, float.MaxValue); int i = startIndex; foreach (DW.LineMetrics metrics in layout.GetLineMetrics()) { if (metrics.Length == 0 && metrics.NewlineLength == 0) continue; bool lineend = false; if (metrics.NewlineLength > 0) lineend = true; output.Add(layoutLineCollection.CreateLineToIndexTableData(i, (int)metrics.Length, lineend, null)); i += Util.RoundUp(metrics.Length); } layout.Dispose(); startIndex += str.Length; } this.format.WordWrapping = DW.WordWrapping.NoWrap; if (output.Count > 0) output.Last().LineEnd = true; return output; } public virtual void Dispose() { this.DestructDeviceResource(); this.HiddenChars.Clear(); if (this.format != null) this.format.Dispose(); if(this.DWFactory != null) this.DWFactory.Dispose(); if(this.D2DFactory != null) this.D2DFactory.Dispose(); } public void ConstructDeviceResource(double width, double height) { float dpiX, dpiY; this.GetDpi(out dpiX, out dpiY); D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties( D2D.RenderTargetType.Default, new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore), dpiX, dpiY, D2D.RenderTargetUsage.None, D2D.FeatureLevel.Level_DEFAULT); this.render = this.ConstructRender(this.D2DFactory,prop,width,height); this.Brushes.Render = this.render; D2D.BitmapProperties bmpProp = new D2D.BitmapProperties(); bmpProp.DpiX = dpiX; bmpProp.DpiY = dpiY; bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore); this.bitmap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp); this.hasCache = false; this.ConstrctedResource(); this.textRender = new CustomTextRenderer(this.render, this.Brushes,this.Strokes, this.Foreground); this.renderSize.Width = width; this.renderSize.Height = height; this.TextAntialiasMode = this._TextAntialiasMode; } #if METRO protected virtual D2D.RenderTarget ConstructRender(D2D.Factory1 factory, D2D.RenderTargetProperties prop, double width, double height) #else protected virtual D2D.RenderTarget ConstructRender(D2D.Factory factory, D2D.RenderTargetProperties prop, double width, double height) #endif { throw new NotImplementedException(); } protected virtual void ConstrctedResource() { throw new NotImplementedException(); } public virtual void GetDpi(out float dpix,out float dpiy) { throw new NotImplementedException(); } protected virtual void DestructRender() { throw new NotImplementedException(); } protected virtual void ReCreateTarget() { throw new NotImplementedException(); } public void DestructDeviceResource() { this.hasCache = false; if (this.bitmap != null) this.bitmap.Dispose(); this.Brushes.Clear(); this.Strokes.Clear(); this.Effects.Clear(); if (this.textRender != null) this.textRender.Dispose(); this.DestructRender(); } void ParseLayout(MyTextLayout layout, string str) { for (int i = 0; i < str.Length; i++) { DW.InlineObject inlineObject = this.HiddenChars.Get(layout,i, str); if (inlineObject != null) { layout.SetInlineObject(inlineObject, new DW.TextRange(i, 1)); layout.SetDrawingEffect(this.Brushes.Get(this.ControlChar), new DW.TextRange(i, 1)); } } layout.SetLineBreakBrush(this.Brushes.Get(this.ControlChar)); } } }