/// <summary> /// Allocates the features from the jagged array.. /// </summary> /// <param name="features">The features.</param> /// <returns>A pointer to the allocated native features or 0 if features is null or empty.</returns> private static IntPtr AllocateFeatures(FontFeature[][] features) { unsafe { var pFeatures = (byte*)0; if (features != null && features.Length > 0) { // Calculate the total size of the buffer to allocate: // (0) (1) (2) // ------------------------------------------------------------- // | array | TypographicFeatures || FontFeatures || // | ptr to (1) | | | || || // | | ptr to FontFeatures || || // ------------------------------------------------------------- // Offset in bytes to (1) int offsetToTypographicFeatures = sizeof(IntPtr) * features.Length; // Add offset (1) and Size in bytes to (1) int calcSize = offsetToTypographicFeatures + sizeof(TypographicFeatures) * features.Length; // Calculate size (2) foreach (var fontFeature in features) { if (fontFeature == null) throw new ArgumentNullException("FontFeature[] inside features array cannot be null", "features"); // calcSize += typographicFeatures.Length * sizeof(FontFeature) calcSize += sizeof(FontFeature) * fontFeature.Length; } // Allocate the whole buffer pFeatures = (byte*)Marshal.AllocHGlobal(calcSize); // Pointer to (1) var pTypographicFeatures = (TypographicFeatures*)(pFeatures + offsetToTypographicFeatures); // Pointer to (2) var pFontFeatures = (FontFeature*)(pTypographicFeatures + features.Length); // Iterate on features and copy them to (2) for (int i = 0; i < features.Length; i++) { // Write array pointers in (0) ((void**)pFeatures)[i] = pTypographicFeatures; var featureSet = features[i]; // Write TypographicFeatures in (1) pTypographicFeatures->Features = (IntPtr)pFontFeatures; pTypographicFeatures->FeatureCount = featureSet.Length; pTypographicFeatures++; // Write FontFeatures in (2) for (int j = 0; j < featureSet.Length; j++) { *pFontFeatures = featureSet[j]; pFontFeatures++; } } } return (IntPtr)pFeatures; } }
/// <summary> /// Gets the glyphs (TODO doc) /// </summary> /// <param name="textString">The text string.</param> /// <param name="textLength">Length of the text.</param> /// <param name="fontFace">The font face.</param> /// <param name="isSideways">if set to <c>true</c> [is sideways].</param> /// <param name="isRightToLeft">if set to <c>true</c> [is right to left].</param> /// <param name="scriptAnalysis">The script analysis.</param> /// <param name="localeName">Name of the locale.</param> /// <param name="numberSubstitution">The number substitution.</param> /// <param name="features">The features.</param> /// <param name="featureRangeLengths">The feature range lengths.</param> /// <param name="maxGlyphCount">The max glyph count.</param> /// <param name="clusterMap">The cluster map.</param> /// <param name="textProps">The text props.</param> /// <param name="glyphIndices">The glyph indices.</param> /// <param name="glyphProps">The glyph props.</param> /// <param name="actualGlyphCount">The actual glyph count.</param> /// <returns> /// If the method succeeds, it returns <see cref="Result.Ok"/>. /// </returns> /// <unmanaged>HRESULT IDWriteTextAnalyzer::GetGlyphs([In, Buffer] const wchar_t* textString,[In] unsigned int textLength,[In] IDWriteFontFace* fontFace,[In] BOOL isSideways,[In] BOOL isRightToLeft,[In] const DWRITE_SCRIPT_ANALYSIS* scriptAnalysis,[In, Buffer, Optional] const wchar_t* localeName,[In, Optional] IDWriteNumberSubstitution* numberSubstitution,[In, Optional] const void** features,[In, Buffer, Optional] const unsigned int* featureRangeLengths,[In] unsigned int featureRanges,[In] unsigned int maxGlyphCount,[Out, Buffer] unsigned short* clusterMap,[Out, Buffer] DWRITE_SHAPING_TEXT_PROPERTIES* textProps,[Out, Buffer] unsigned short* glyphIndices,[Out, Buffer] DWRITE_SHAPING_GLYPH_PROPERTIES* glyphProps,[Out] unsigned int* actualGlyphCount)</unmanaged> public void GetGlyphs(string textString, int textLength, SharpDX.DirectWrite.FontFace fontFace, bool isSideways, bool isRightToLeft, SharpDX.DirectWrite.ScriptAnalysis scriptAnalysis, string localeName, SharpDX.DirectWrite.NumberSubstitution numberSubstitution, FontFeature[][] features, int[] featureRangeLengths, int maxGlyphCount, short[] clusterMap, SharpDX.DirectWrite.ShapingTextProperties[] textProps, short[] glyphIndices, SharpDX.DirectWrite.ShapingGlyphProperties[] glyphProps, out int actualGlyphCount) { var pFeatures = AllocateFeatures(features); try { GetGlyphs( textString, textLength, fontFace, isSideways, isRightToLeft, scriptAnalysis, localeName, numberSubstitution, pFeatures, featureRangeLengths, featureRangeLengths == null ? 0 : featureRangeLengths.Length, maxGlyphCount, clusterMap, textProps, glyphIndices, glyphProps, out actualGlyphCount); } finally { if (pFeatures != IntPtr.Zero) Marshal.FreeHGlobal(pFeatures); } }
/// <summary> /// Gets the GDI compatible glyph placements. /// </summary> /// <param name="textString">The text string.</param> /// <param name="clusterMap">The cluster map.</param> /// <param name="textProps">The text props.</param> /// <param name="textLength">Length of the text.</param> /// <param name="glyphIndices">The glyph indices.</param> /// <param name="glyphProps">The glyph props.</param> /// <param name="glyphCount">The glyph count.</param> /// <param name="fontFace">The font face.</param> /// <param name="fontEmSize">Size of the font em.</param> /// <param name="pixelsPerDip">The pixels per dip.</param> /// <param name="transform">The transform.</param> /// <param name="useGdiNatural">if set to <c>true</c> [use GDI natural].</param> /// <param name="isSideways">if set to <c>true</c> [is sideways].</param> /// <param name="isRightToLeft">if set to <c>true</c> [is right to left].</param> /// <param name="scriptAnalysis">The script analysis.</param> /// <param name="localeName">Name of the locale.</param> /// <param name="features">The features.</param> /// <param name="featureRangeLengths">The feature range lengths.</param> /// <param name="glyphAdvances">The glyph advances.</param> /// <param name="glyphOffsets">The glyph offsets.</param> /// <returns> /// If the method succeeds, it returns <see cref="Result.Ok"/>. /// </returns> /// <unmanaged>HRESULT IDWriteTextAnalyzer::GetGdiCompatibleGlyphPlacements([In, Buffer] const wchar_t* textString,[In, Buffer] const unsigned short* clusterMap,[In, Buffer] DWRITE_SHAPING_TEXT_PROPERTIES* textProps,[In] unsigned int textLength,[In, Buffer] const unsigned short* glyphIndices,[In, Buffer] const DWRITE_SHAPING_GLYPH_PROPERTIES* glyphProps,[In] unsigned int glyphCount,[In] IDWriteFontFace* fontFace,[In] float fontEmSize,[In] float pixelsPerDip,[In, Optional] const DWRITE_MATRIX* transform,[In] BOOL useGdiNatural,[In] BOOL isSideways,[In] BOOL isRightToLeft,[In] const DWRITE_SCRIPT_ANALYSIS* scriptAnalysis,[In, Buffer, Optional] const wchar_t* localeName,[In, Optional] const void** features,[In, Buffer, Optional] const unsigned int* featureRangeLengths,[In] unsigned int featureRanges,[Out, Buffer] float* glyphAdvances,[Out, Buffer] DWRITE_GLYPH_OFFSET* glyphOffsets)</unmanaged> public void GetGdiCompatibleGlyphPlacements(string textString, short[] clusterMap, SharpDX.DirectWrite.ShapingTextProperties[] textProps, int textLength, short[] glyphIndices, SharpDX.DirectWrite.ShapingGlyphProperties[] glyphProps, int glyphCount, SharpDX.DirectWrite.FontFace fontFace, float fontEmSize, float pixelsPerDip, SharpDX.DirectWrite.Matrix? transform, bool useGdiNatural, bool isSideways, bool isRightToLeft, SharpDX.DirectWrite.ScriptAnalysis scriptAnalysis, string localeName, FontFeature[][] features, int[] featureRangeLengths, float[] glyphAdvances, SharpDX.DirectWrite.GlyphOffset[] glyphOffsets) { var pFeatures = AllocateFeatures(features); try { GetGdiCompatibleGlyphPlacements( textString, clusterMap, textProps, textLength, glyphIndices, glyphProps, glyphCount, fontFace, fontEmSize, pixelsPerDip, transform, useGdiNatural, isSideways, isRightToLeft, scriptAnalysis, localeName, pFeatures, featureRangeLengths, featureRangeLengths == null ? 0 : featureRangeLengths.Length, glyphAdvances, glyphOffsets ); } finally { if (pFeatures != IntPtr.Zero) Marshal.FreeHGlobal(pFeatures); } }
/// <inheritdoc /> protected override void Draw() { while (this.pendingActions.TryDequeue(out var action)) { action(); } var args = this.neovimClient.GetScreen(); if (args == null) { return; } this.scriptAnalysesCache.StartNewFrame(); this.DeviceContext.BeginDraw(); this.DeviceContext.Clear(Helpers.GetColor(args.BackgroundColor, 0)); // Paint the background for (int i = 0; i < args.Cells.GetLength(0); i++) { for (int j = 0; j < args.Cells.GetLength(1); j++) { if (args.Cells[i, j].BackgroundColor != args.BackgroundColor || args.Cells[i, j].Reverse) { var x = j * this.textParam.CharWidth; var y = i * this.textParam.LineHeight; var rect = new RawRectangleF(x, y, x + this.textParam.CharWidth, y + this.textParam.LineHeight); int color = args.Cells[i, j].Reverse ? args.Cells[i, j].ForegroundColor : args.Cells[i, j].BackgroundColor; var brush = this.brushCache.GetBrush(this.DeviceContext, color); this.DeviceContext.FillRectangle(rect, brush); } } } // Paint the foreground for (int i = 0; i < args.Cells.GetLength(0); i++) { int j = 0; while (j < args.Cells.GetLength(1)) { // Cells with the same style should be analyzed together. // This prevents the inproper ligature in <html>= // Of course, it relies on enabling the syntax. int cellRangeStart = j; int cellRangeEnd = j; Cell startCell = args.Cells[i, cellRangeStart]; while (true) { if (cellRangeEnd == args.Cells.GetLength(1)) { break; } Cell cell = args.Cells[i, cellRangeEnd]; if (cell.Character != null && (cell.ForegroundColor != startCell.ForegroundColor || cell.BackgroundColor != startCell.BackgroundColor || cell.SpecialColor != startCell.SpecialColor || cell.Italic != startCell.Italic || cell.Bold != startCell.Bold || cell.Reverse != startCell.Reverse || cell.Undercurl != startCell.Undercurl || cell.Underline != startCell.Underline)) { break; } cellRangeEnd++; } j = cellRangeEnd; var fontWeight = args.Cells[i, cellRangeStart].Bold ? DWrite.FontWeight.Bold : this.textParam.Weight; var fontStyle = args.Cells[i, cellRangeStart].Italic ? DWrite.FontStyle.Italic : this.textParam.Style; int cellIndex = cellRangeStart; using (var textSource = new RowTextSource(this.factoryDWrite, args.Cells, i, cellRangeStart, cellRangeEnd)) { var scriptAnalyses = this.scriptAnalysesCache.GetOrAddAnalysisResult( textSource.GetTextAtPosition(0), (_) => { using (var textSink = new TextAnalysisSink()) { this.textAnalyzer.AnalyzeScript(textSource, 0, textSource.Length, textSink); return(textSink.ScriptAnalyses); } }); // The result of AalyzeScript may cut the text into several ranges, // and in each range the text's scripts are different. foreach (var(codePointStart, codePointLength, scriptAnalysis) in scriptAnalyses) { var glyphBufferLength = (codePointLength * 3 / 2) + 16; var clusterMap = new short[codePointLength]; var textProperties = new DWrite.ShapingTextProperties[codePointLength]; short[] indices; DWrite.ShapingGlyphProperties[] shapingProperties; var fontFace = this.fontCache.GetPrimaryFontFace(fontWeight, fontStyle); int actualGlyphCount; // We don't know how many glyphs the text have. TextLength * 3 / 2 + 16 // is an empirical estimation suggested by MSDN. So using a loop to detect // the actual glyph count. while (true) { indices = new short[glyphBufferLength]; shapingProperties = new DWrite.ShapingGlyphProperties[glyphBufferLength]; try { var str = textSource.GetSubString(codePointStart, codePointLength); DWrite.FontFeature[][] fontFeatures = null; int[] featureLength = null; if (!this.EnableLigature) { fontFeatures = new DWrite.FontFeature[][] { new DWrite.FontFeature[] { new DWrite.FontFeature(DWrite.FontFeatureTag.StandardLigatures, 0), }, }; featureLength = new int[] { str.Length, }; } this.textAnalyzer.GetGlyphs( str, str.Length, fontFace, false, false, scriptAnalysis, null, null, fontFeatures, featureLength, glyphBufferLength, clusterMap, textProperties, indices, shapingProperties, out actualGlyphCount); break; } catch (SharpDX.SharpDXException e) { const int ERROR_INSUFFICIENT_BUFFER = 122; if (e.ResultCode == SharpDX.Result.GetResultFromWin32Error(ERROR_INSUFFICIENT_BUFFER)) { glyphBufferLength *= 2; } } } for (int codePointIndex = 0, glyphIndex = 0; codePointIndex < codePointLength;) { // var fontWeight = args.Screen[i, cellIndex].Bold ? DWrite.FontWeight.Bold : DWrite.FontWeight.Normal; // var fontStyle = args.Screen[i, cellIndex].Italic ? DWrite.FontStyle.Italic : DWrite.FontStyle.Normal; var foregroundColor = args.Cells[i, cellIndex].Reverse ? args.Cells[i, cellIndex].BackgroundColor : args.Cells[i, cellIndex].ForegroundColor; var foregroundBrush = this.brushCache.GetBrush(this.DeviceContext, foregroundColor); var fontFace2 = fontFace; short[] indices2; int cellWidth = 0; int codePointCount = 0; int glyphCount = 0; if (indices[glyphIndex] == 0) { // If the primary font doesn't have the glyph, get a font from system font fallback. // Ligatures for fallback fonts are not supported yet. int codePoint = args.Cells[i, cellIndex].Character.Value; fontFace2 = this.fontCache.GetFontFace(codePoint, fontWeight, fontStyle); indices2 = fontFace2.GetGlyphIndices(new int[] { codePoint }); glyphCount = indices2.Length; // NativeInterop.Methods.wcwidth(textSource.GetCodePoint(codePointStart + codePointIndex)); cellWidth = this.GetCharWidth(args.Cells, i, cellIndex); codePointCount = 1; } else { // The cluster map stores the information about the codepoint-glyph mapping. // If several codepoints share the same glyph (e.g. ligature), then they will // have the same value in clusterMap. // If one code point has several corresponding glyphs, then for the next codepoint, // the value in clusterMap will bump higher. // Example: CodePointLength = 5, GlyphCount = 5, clusterMap = [0, 1, 1, 2, 4] means: // Codepoint Index Glyph Index // 0 ----------- 0 // 1 ----------- 1 // 2 ----------/ // 3 ----------- 2 // \---------- 3 // 4 ----------- 4 var cluster = clusterMap[codePointIndex]; int nextCluster = cluster; while (true) { if (codePointIndex + codePointCount == clusterMap.Length) { nextCluster++; break; } nextCluster = clusterMap[codePointIndex + codePointCount]; if (cluster != nextCluster) { break; } // NativeInterop.Methods.wcwidth(textSource.GetCodePoint(codePointStart + codePointIndex + codePointCount)); cellWidth += this.GetCharWidth(args.Cells, i, cellIndex + cellWidth); codePointCount++; } glyphCount = nextCluster - cluster; indices2 = new short[glyphCount]; for (int c = 0; c < glyphCount; c++) { indices2[c] = indices[glyphIndex]; } } using (var glyphrun = new DWrite.GlyphRun { FontFace = fontFace2, Advances = null, BidiLevel = 0, FontSize = this.textParam.DipSize, Indices = indices2, IsSideways = false, Offsets = null, }) { var origin = new RawVector2(this.textParam.CharWidth * cellIndex, this.textParam.LineHeight * (i + 0.8f)); this.DeviceContext.DrawGlyphRun(origin, glyphrun, foregroundBrush, D2D.MeasuringMode.GdiNatural); glyphrun.FontFace = null; } cellIndex += cellWidth; codePointIndex += codePointCount; glyphIndex += glyphCount; } } } } } this.DeviceContext.EndDraw(); this.DrawCursor(args); }