<file_sep>////////////////////////////////////////////////////////////////////////////////
// 
//              *** Helper code to convert from UTF-8 to UTF-16 ***
// 
//                              by <NAME>
// 
// 
// This code is based on my MSDN Magazine article published 
// on the 2016 September issue:
//
// "C++ - Unicode Encoding Conversions with STL Strings and Win32 APIs"
// https://msdn.microsoft.com/magazine/mt763237
//
//////////////////////////////////////////////////////////////////////////////// 

#pragma once

#include <limits>           // std::numeric_limits
#include <stdexcept>        // std::runtime_error, std::overflow_error
#include <string>           // std::string, std::wstring

#include <crtdbg.h>         // _ASSERTE()
#include <Windows.h>        // Win32 Platform SDK main header


//------------------------------------------------------------------------------
// Error when converting from UTF-8 to UTF-16
//------------------------------------------------------------------------------
class Utf16FromUtf8ConversionError
    : public std::runtime_error
{
public:

    // Initialize with error message raw C-string and last Win32 error code 
    Utf16FromUtf8ConversionError(const char* message, DWORD errorCode)
        : std::runtime_error(message)
        , m_errorCode(errorCode)
    {}

    // Initialize with error message string and last Win32 error code 
    Utf16FromUtf8ConversionError(const std::string& message, DWORD errorCode)
        : std::runtime_error(message)
        , m_errorCode(errorCode)
    {}

    // Retrieve error code associated to the failed conversion
    DWORD ErrorCode() const
    {
        return m_errorCode;
    }

private:
    // Error code from GetLastError()
    DWORD  m_errorCode;
};


//------------------------------------------------------------------------------
// Convert from UTF-8 to UTF-16.
// Throws Utf16FromUtf8ConversionError on error.
//------------------------------------------------------------------------------
inline std::wstring Utf16FromUtf8(const std::string& utf8)
{
    // Result of the conversion
    std::wstring utf16;

    // First, handle the special case of empty input string
    if (utf8.empty())
    {
        _ASSERTE(utf16.empty());
        return utf16;
    }

    // Safely fail if an invalid UTF-8 character sequence is encountered
    const DWORD kFlags = MB_ERR_INVALID_CHARS;

    // Safely cast the length of the source UTF-8 string (expressed in chars)
    // from size_t (returned by std::string::length()) to int 
    // for the MultiByteToWideChar API.
    // If the size_t value is too big to be stored into an int, 
    // throw an exception to prevent conversion errors like huge size_t values 
    // converted to *negative* integers.
    if (utf8.length() > static_cast<size_t>((std::numeric_limits<int>::max)()))
    {
        throw std::overflow_error(
            "Input string too long: size_t-length doesn't fit into int.");
    }
    const int utf8Length = static_cast<int>(utf8.length());

    // Get the size of the destination UTF-16 string
    const int utf16Length = ::MultiByteToWideChar(
        CP_UTF8,       // source string is in UTF-8
        kFlags,        // conversion flags
        utf8.data(),   // source UTF-8 string pointer
        utf8Length,    // length of the source UTF-8 string, in chars
        nullptr,       // unused - no conversion done in this step
        0              // request size of destination buffer, in wchar_ts
    );
    if (utf16Length == 0)
    {
        // Conversion error: capture error code and throw
        const DWORD error = ::GetLastError();
        throw Utf16FromUtf8ConversionError(
            error == ERROR_NO_UNICODE_TRANSLATION ?
            "Invalid UTF-8 sequence found in input string."
            :
            "Cannot get result string length when converting " \
            "from UTF-8 to UTF-16 (MultiByteToWideChar failed).",
            error
        );
    }

    // Make room in the destination string for the converted bits
    utf16.resize(utf16Length);

    // Do the actual conversion from UTF-8 to UTF-16
    int result = ::MultiByteToWideChar(
        CP_UTF8,       // source string is in UTF-8
        kFlags,        // conversion flags
        utf8.data(),   // source UTF-8 string pointer
        utf8Length,    // length of source UTF-8 string, in chars
        &utf16[0],     // pointer to destination buffer
        utf16Length    // size of destination buffer, in wchar_ts           
    );
    if (result == 0)
    {
        // Conversion error: capture error code and throw
        const DWORD error = ::GetLastError();
        throw Utf16FromUtf8ConversionError(
            error == ERROR_NO_UNICODE_TRANSLATION ?
            "Invalid UTF-8 sequence found in input string."
            :
            "Cannot convert from UTF-8 to UTF-16 "\
            "(MultiByteToWideChar failed).",
            error
        );
    }

    return utf16;
}

////////////////////////////////////////////////////////////////////////////////
<file_sep>////////////////////////////////////////////////////////////////////////////////
//
// Test printing UTF-8-encoded text to the Windows console
//
// by <NAME>
//
////////////////////////////////////////////////////////////////////////////////


#include "UnicodeConv.hpp"  // My Unicode conversion helpers

#include <fcntl.h>
#include <io.h>
#include <stdint.h>
#include <iostream>
#include <string>


int main()
{
    // Change stdout to Unicode UTF-16.
    //
    // Note: _O_U8TEXT doesn't seem to work, e.g.:
    // https://blogs.msmvps.com/gdicanio/2017/08/22/printing-utf-8-text-to-the-windows-console/
    //
    _setmode(_fileno(stdout), _O_U16TEXT);


    // Japanese name for Japan, encoded in UTF-8
    uint8_t utf8[] = {
        0xE6, 0x97, 0xA5, // U+65E5
        0xE6, 0x9C, 0xAC, // U+672C
        0x00
    };
    std::string japan(reinterpret_cast<const char*>(utf8));

    // Print UTF-16-encoded text
    std::wcout << Utf16FromUtf8("Japan") << L"\n\n";
    std::wcout << Utf16FromUtf8(japan)   << L'\n';
}

////////////////////////////////////////////////////////////////////////////////

<file_sep># PrintUtf8ToWindowsConsole
Demo code printing UTF-8-encoded text to the Windows console

by <NAME>

This repo is associated to this blog post:

https://blogs.msmvps.com/gdicanio/2019/04/09/printing-utf-8-text-to-the-windows-console-sample-code/
