private static void WriteConsoleOutputCJK()

in DbgShell/ConsoleControl.cs [1419:1622]


        private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinates origin, Rectangle contentsRegion, BufferCell[,] contents, BufferCellArrayRowType rowType)
        {
            Util.Assert(origin.X >= 0 && origin.Y >= 0,
                "origin must be within the output buffer");
            int rows = contentsRegion.Bottom - contentsRegion.Top + 1;
            int cols = contentsRegion.Right - contentsRegion.Left + 1;

            CONSOLE_FONT_INFO_EX fontInfo = GetConsoleFontInfo(consoleHandle);
            int fontType = fontInfo.FontFamily & NativeMethods.FontTypeMask;
            bool trueTypeInUse = (fontType & NativeMethods.TrueTypeFont) == NativeMethods.TrueTypeFont;

            int bufferLimit = 2 * 1024; // Limit is 8K bytes as each CHAR_INFO takes 4 bytes

            COORD bufferCoord;

            bufferCoord.X = 0;
            bufferCoord.Y = 0;

            // keeps track of which screen area write
            SMALL_RECT writeRegion;

            writeRegion.Top = (short)origin.Y;

            int rowsRemaining = rows;

            while (rowsRemaining > 0)
            {
                // Iteration of columns is nested inside iteration of rows.
                // If the size of contents exceeds the buffer limit, writing is
                // done in blocks of size equal to the bufferlimit from left to right
                // then top to bottom.
                // For each iteration of rows,
                // - writeRegion.Left and bufferSize.X are reset
                // - rowsRemaining, writeRegion.Top, writeRegion.Bottom, and bufferSize.Y
                //     are updated
                //   For each iteration of columns,
                //   - writeRegion.Left, writeRegion.Right and bufferSize.X are updated

                writeRegion.Left = (short)origin.X;

                COORD bufferSize;

                bufferSize.X = (short)Math.Min(cols, bufferLimit);
                bufferSize.Y = (short)Math.Min
                                        (
                                            rowsRemaining,
                                            bufferLimit / bufferSize.X
                                        );
                writeRegion.Bottom = (short)(writeRegion.Top + bufferSize.Y - 1);

                // atRow is at which row of contents a particular iteration is operating
                int atRow = rows - rowsRemaining + contentsRegion.Top;

                // number of columns yet to be written
                int colsRemaining = cols;
                while (colsRemaining > 0)
                {
                    writeRegion.Right = (short)(writeRegion.Left + bufferSize.X - 1);

                    // atCol is at which column of contents a particular iteration is operating
                    int atCol = cols - colsRemaining + contentsRegion.Left;
                    // if this is not the last column iteration &&
                    //   the leftmost BufferCell is a leading cell, don't write that cell
                    if (colsRemaining > bufferSize.X &&
                         contents[atRow, atCol + bufferSize.X - 1].BufferCellType == BufferCellType.Leading)
                    {
                        bufferSize.X--;
                        writeRegion.Right--;
                    }
                    CHAR_INFO[] characterBuffer = new CHAR_INFO[bufferSize.Y * bufferSize.X];

                    // copy characterBuffer to contents;
                    int characterBufferIndex = 0;
                    bool lastCharIsLeading = false;
                    BufferCell lastLeadingCell = new BufferCell();
                    for (int r = atRow; r < bufferSize.Y + atRow; r++)
                    {
                        for (int c = atCol; c < bufferSize.X + atCol; c++, characterBufferIndex++)
                        {
                            if (contents[r, c].BufferCellType == BufferCellType.Complete)
                            {
                                characterBuffer[characterBufferIndex].UnicodeChar =
                                    (ushort)contents[r, c].Character;
                                characterBuffer[characterBufferIndex].Attributes =
                                    (ushort)(ColorToWORD(contents[r, c].ForegroundColor, contents[r, c].BackgroundColor));

                                lastCharIsLeading = false;
                            }
                            else if (contents[r, c].BufferCellType == BufferCellType.Leading)
                            {
                                characterBuffer[characterBufferIndex].UnicodeChar =
                                    (ushort)contents[r, c].Character;
                                characterBuffer[characterBufferIndex].Attributes =
                                    (ushort)(ColorToWORD(contents[r, c].ForegroundColor, contents[r, c].BackgroundColor)
                                                | (ushort)NativeMethods.CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE);

                                lastCharIsLeading = true;
                                lastLeadingCell = contents[r, c];
                            }
                            else if (contents[r, c].BufferCellType == BufferCellType.Trailing)
                            {
                                // The FontFamily is a 8-bit integer. The low-order bit (bit 0) specifies the pitch of the font. 
                                // If it is 1, the font is variable pitch (or proportional). If it is 0, the font is fixed pitch 
                                // (or monospace). Bits 1 and 2 specify the font type. If both bits are 0, the font is a raster font; 
                                // if bit 1 is 1 and bit 2 is 0, the font is a vector font; if bit 1 is 0 and bit 2 is set, or if both 
                                // bits are 1, the font is true type. Bit 3 is 1 if the font is a device font; otherwise, it is 0.
                                // We only care about the bit 1 and 2, which indicate the font type.
                                // There are only two font type defined for the Console, at
                                // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\.
                                //     Console\Nls           --- national language supports
                                //     Console\RasterFonts   --- raster type font
                                //     Console\TrueTypeFont  --- true type font
                                // For CJK characters, if it's TrueType, we need to output the trailing character marked with "Trailing_byte"
                                // attribute. But if it's RasterFont, we ignore the trailing character, and the "Leading_byte"/"Trailing_byte"
                                // attributes are not effective at all when reading the character from the console buffer.
                                if (lastCharIsLeading && trueTypeInUse)
                                {
                                    // For TrueType Font, we output the trailing byte with "Trailing_byte" attribute
                                    characterBuffer[characterBufferIndex].UnicodeChar = lastLeadingCell.Character;
                                    characterBuffer[characterBufferIndex].Attributes =
                                        (ushort)(ColorToWORD(contents[r, c].ForegroundColor, contents[r, c].BackgroundColor)
                                                | (ushort)NativeMethods.CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE);
                                }
                                else
                                {
                                    // We don't output anything for this cell if Raster font is in use, or if the last cell is not a leading byte
                                    characterBufferIndex--;
                                }
                                lastCharIsLeading = false;
                            }
                        }
                    }

                    // Now writeRegion, bufferSize and characterBuffer are updated.
                    // Call NativeMethods.WriteConsoleOutput
                    bool result;
                    if ((rowType & BufferCellArrayRowType.RightLeading) != 0 &&
                            colsRemaining == bufferSize.X)
                    {
                        COORD bSize = bufferSize;
                        bSize.X++;
                        SMALL_RECT wRegion = writeRegion;
                        wRegion.Right++;
                        // Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
                        // get the error code.
#pragma warning disable 56523
                        result = NativeMethods.WriteConsoleOutput(
                            consoleHandle.DangerousGetHandle(),
                            characterBuffer,
                            bSize,
                            bufferCoord,
                            ref wRegion);
                    }
                    else
                    {
                        // Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
                        // get the error code.
#pragma warning disable 56523
                        result = NativeMethods.WriteConsoleOutput(
                            consoleHandle.DangerousGetHandle(),
                            characterBuffer,
                            bufferSize,
                            bufferCoord,
                            ref writeRegion);
                    }
                    if (result == false)
                    {
                        // When WriteConsoleOutput fails, half bufferLimit
                        if (bufferLimit < 2)
                        {
                            int err = Marshal.GetLastWin32Error();
                            HostException e = CreateHostException(err, "WriteConsoleOutput",
                                ErrorCategory.WriteError, "The Win32 internal error \"{0}\" 0x{1:X} occurred while writing to the console output buffer. Contact Microsoft Customer Support Services." ); //ConsoleControlStrings.WriteConsoleOutputExceptionTemplate);
                            throw e;
                        }
                        bufferLimit /= 2;
                        if (cols == colsRemaining)
                        {
                            // if cols == colsRemaining, nothing is guaranteed written in this pass and
                            //  the unwritten area is still rectangular
                            bufferSize.Y = 0;
                            break;
                        }
                        else
                        {
                            // some areas have been written. This could only happen when the number of columns
                            // to write is larger than bufferLimit. In that case, the algorithm writes one row
                            // at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged
                            // to retry with a smaller bufferSize.X.
                            Util.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y));
                            bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
                            continue;
                        }
                    }

                    colsRemaining -= bufferSize.X;
                    writeRegion.Left += bufferSize.X;
                    bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
                }  // column iteration

                rowsRemaining -= bufferSize.Y;
                writeRegion.Top += bufferSize.Y;
            }  // row iteration
        }