protected void ShapeGlyphRun(TextAnalyzer textAnalyzer, int runIndex, ref int glyphStart) { // Shapes a single run of text into glyphs. // Alternately, you could iteratively interleave shaping and line // breaking to reduce the number glyphs held onto at once. It's simpler // for this demostration to just do shaping and line breaking as two // separate processes, but realize that this does have the consequence that // certain advanced fonts containing line specific features (like Gabriola) // will shape as if the line is not broken. Run run = runs_[runIndex]; int textStart = run.textStart; int textLength = run.textLength; int maxGlyphCount = glyphIndices_.Length - glyphStart; int actualGlyphCount = 0; run.glyphStart = glyphStart; run.glyphCount = 0; if (textLength == 0) return;// Nothing to do.. // Allocate space for shaping to fill with glyphs and other information, // with about as many glyphs as there are text characters. We'll actually // need more glyphs than codepoints if they are decomposed into separate // glyphs, or fewer glyphs than codepoints if multiple are substituted // into a single glyph. In any case, the shaping process will need some // room to apply those rules to even make that determintation. if (textLength > maxGlyphCount) { maxGlyphCount = EstimateGlyphCount(textLength); int totalGlyphsArrayCount = glyphStart + maxGlyphCount; short[] Resized_glyphIndices_ = new short[totalGlyphsArrayCount]; glyphIndices_.CopyTo(Resized_glyphIndices_, 0); glyphIndices_ = Resized_glyphIndices_; } ShapingTextProperties[] textProps = new ShapingTextProperties[textLength]; ShapingGlyphProperties[] glyphProps = new ShapingGlyphProperties[maxGlyphCount]; // Get the glyphs from the text, retrying if needed. int tries = 0; while (tries < 2)// We'll give it two chances. { short[] call_glyphClusters_ = new short[glyphClusters_.Length - textStart]; short[] call_glyphIndices_ = new short[glyphIndices_.Length - glyphStart]; bool isDone = false; try { textAnalyzer.GetGlyphs( text_.Substring(textStart, textLength), textLength, fontFace_, run.isSideways, (run.bidiLevel % 2 == 1), run.script, localName_, run.isNumberSubstituted ? numberSubstitution_ : null, null, null, maxGlyphCount, call_glyphClusters_, textProps, call_glyphIndices_, glyphProps, out actualGlyphCount); Array.Copy(call_glyphClusters_, 0, glyphClusters_, textStart, call_glyphClusters_.Length); Array.Copy(call_glyphIndices_, 0, glyphIndices_, glyphStart, call_glyphIndices_.Length); isDone = true; } finally { tries++; // Try again using a larger buffer. maxGlyphCount = EstimateGlyphCount(maxGlyphCount); int totalGlyphsArrayCount = glyphStart + maxGlyphCount; glyphProps = new ShapingGlyphProperties[maxGlyphCount]; glyphIndices_ = new short[totalGlyphsArrayCount]; } if (isDone) break; } // Get the placement of the all the glyphs. if (glyphAdvances_.Length < glyphStart + actualGlyphCount) { float[] Resized_glyphAdvances_ = new float[glyphStart + actualGlyphCount]; glyphAdvances_.CopyTo(Resized_glyphAdvances_, 0); glyphAdvances_ = Resized_glyphAdvances_; } if (glyphOffsets_.Length < glyphStart + actualGlyphCount) { GlyphOffset[] Resized_glyphOffsets_ = new GlyphOffset[glyphStart + actualGlyphCount]; glyphOffsets_.CopyTo(Resized_glyphOffsets_, 0); glyphOffsets_ = Resized_glyphOffsets_; } short[] call2_glyphClusters_ = new short[glyphClusters_.Length - textStart]; Array.Copy(glyphClusters_, textStart, call2_glyphClusters_, 0, call2_glyphClusters_.Length); short[] call2_glyphIndices_ = new short[glyphIndices_.Length - glyphStart]; Array.Copy(glyphIndices_, glyphStart, call2_glyphIndices_, 0, call2_glyphIndices_.Length); float[] call2_glyphAdvances_ = new float[glyphAdvances_.Length - glyphStart]; Array.Copy(glyphAdvances_, glyphStart, call2_glyphAdvances_, 0, call2_glyphAdvances_.Length); GlyphOffset[] call2_glyphOffsets_ = new GlyphOffset[glyphOffsets_.Length - glyphStart]; Array.Copy(glyphOffsets_, glyphStart, call2_glyphOffsets_, 0, call2_glyphOffsets_.Length); textAnalyzer.GetGlyphPlacements( text_.Substring(textStart, textLength), call2_glyphClusters_, textProps, textLength, call2_glyphIndices_, glyphProps, actualGlyphCount, fontFace_, fontEmSize_, run.isSideways, run.bidiLevel % 2 == 1, run.script, localName_, null, null, call2_glyphAdvances_, call2_glyphOffsets_); //call2_glyphClusters_.CopyTo(glyphClusters_, textStart); call2_glyphAdvances_.CopyTo(glyphAdvances_, glyphStart); call2_glyphOffsets_.CopyTo(glyphOffsets_, glyphStart); // Certain fonts, like Batang, contain glyphs for hidden control // and formatting characters. So we'll want to explicitly force their // advance to zero. if (run.script.Shapes == ScriptShapes.NoVisual) { for (int i = glyphStart; i < glyphStart + actualGlyphCount; i++) glyphAdvances_[i] = 0; } // Set the final glyph count of this run and advance the starting glyph. run.glyphCount = actualGlyphCount; runs_[runIndex] = run; glyphStart += actualGlyphCount; }
/// <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); }