9
votes

I'm creating a c++ project using Embarcadero RAD Studio (10.2 Tokyo starter) and the Windows GDI to draw text, via the DrawText() function.

I recently saw that Windows 10 provides a new "Segoe UI Emoji" font, that potentially allows text functions to draw colored emojis. I found several examples using Direct2D, but none with pure GDI functions.

I also tried a simple code, like this:

HDC hDC = ::GetDC(Handle);

std::auto_ptr<TCanvas> pCanvas(new TCanvas());
pCanvas->Handle = hDC;

pCanvas->Brush->Color = clWhite;
pCanvas->Brush->Style = bsSolid;
pCanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));

const std::wstring text = L"Test ???? ???? ???? ???? ???? ???? ???? ????";

TRect textRect(10, 10, ClientWidth - 10, ClientHeight - 10);

hFont = ::CreateFont(-40,
                      0, 
                      0,
                      0,
                      FW_DONTCARE,
                      FALSE,
                      FALSE,
                      FALSE,
                      DEFAULT_CHARSET,
                      OUT_OUTLINE_PRECIS,
                      CLIP_DEFAULT_PRECIS,
                      CLEARTYPE_QUALITY,
                      VARIABLE_PITCH,
                      L"Segoe UI Emoji");

::SelectObject(hDC, hFont);

::DrawTextW(hDC,
            text.c_str(),
            text.length(),
            &textRect,
            DT_LEFT | DT_TOP | DT_SINGLELINE);

::DeleteObject(hFont);

The output result sounds good in terms of symbols, but they are drawn in black&white, without colors, as you can see on the screenshot below: enter image description here

I could not find any additional options that may allow the text to be drawn using colored symbols instead of black&white. Is there a way to activate the support of the color in GDI DrawText() function, and if yes, how to do that? Or only Direct2D may draw colored emojis?

EDITED on 30.10.2017

As the GDI cannot do the job (unfortunately, and as I thought) I publish here the Direct2D version of the above code, that worked for me.

const std::wstring text = L"Test ???? ???? ???? ???? ???? ???? ???? ????";

HDC hDC = ::GetDC(Handle);

std::auto_ptr<TCanvas> pGDICanvas(new TCanvas());
pGDICanvas->Handle = hDC;

pGDICanvas->Brush->Color = clWhite;
pGDICanvas->Brush->Style = bsSolid;
pGDICanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));

::D2D1_RECT_F textRect;
textRect.left   = 10;
textRect.top    = 10;
textRect.right  = ClientWidth  - 10;
textRect.bottom = ClientHeight - 10;

std::auto_ptr<TDirect2DCanvas> pCanvas(new TDirect2DCanvas(hDC, TRect(0, 0, ClientWidth, ClientHeight)));

// configure Direct2D font
pCanvas->Font->Size        = 40;
pCanvas->Font->Name        = L"Segoe UI Emoji";
pCanvas->Font->Orientation = 0;
pCanvas->Font->Pitch       = System::Uitypes::TFontPitch::fpVariable;
pCanvas->Font->Style       = TFontStyles();

// get DirectWrite text format object
_di_IDWriteTextFormat pFormat = pCanvas->Font->Handle;

if (!pFormat)
    return;

pCanvas->RenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);

::D2D1_COLOR_F color;
color.r = 0.0f;
color.g = 0.0f;
color.b = 0.0f;
color.a = 1.0f;

::ID2D1SolidColorBrush* pBrush = NULL;

// create solid color brush, use pen color if rect is completely filled with outline
pCanvas->RenderTarget->CreateSolidColorBrush(color, &pBrush);

if (!pBrush)
    return;

// set horiz alignment
pFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);

// set vert alignment
pFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);

// set reading direction
pFormat->SetReadingDirection(DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);

// set word wrapping mode
pFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);

IDWriteInlineObject* pInlineObject = NULL;

::DWRITE_TRIMMING trimming;
trimming.delimiter      = 0;
trimming.delimiterCount = 0;
trimming.granularity    = DWRITE_TRIMMING_GRANULARITY_NONE;

// set text trimming
pFormat->SetTrimming(&trimming, pInlineObject);

pCanvas->BeginDraw();

pCanvas->RenderTarget->DrawText(text.c_str(), text.length(), pFormat, textRect, pBrush,
            D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);

pCanvas->EndDraw();

Of course this code will draw colored emojis only on the currently most recent versions of Windows 10, and above. On previous versions the text will be drawn as above (and the code may not compile).

Bonus Reading

2
With DirectWrite, you have to opt-in to color rendering for color fonts to avoid breaking programs that weren't prepared for it. If it were possible with GDI, they'd probably also make that an opt-in. Given that DirectWrite is the only "current" text rendering stack, I don't think GDI has had an update to give you an opt-in ability.Adrian McCarthy
Thx for your answer, unfortunately it is what I thoughtJean-Milost Reymond
That's not to rule out the possibility that you could do it manually with GDI. I suppose there might be a way to instantiate the various layers of color as distinct fonts. Then you'd have to set the text color, draw the text for the current layer, and repeat for each of the colors.Adrian McCarthy
One more time thank you for your answers. In fact I already found a way to workaround the GDI to get exactly the effect I wished. However my code is complex, and I was interested to use the new Windows 10 features, that seems to do exactly the same thing, in order to not reinvent the wheel. Unfortunately it seems that the only way to achieve that is to rewrite my code to use Direct2D instead of the GDIJean-Milost Reymond

2 Answers

7
votes

GDI does not support color fonts (even if you go the full Uniscribe route), you have to use Direct2D if you want color font support. It makes sense that the simpler GDI APIs don't support color fonts as color fonts require using OpenType tags and none of DrawText/TextOut provide that level of control, Uniscribe allows for such tags but has simply not been extended to support color fonts.

2
votes

You can use DirectWrite to draw colored emojis onto a bitmap in memory DC, then BitBlt() to your destination DC. Basically you need to implement a custom IDWriteTextRenderer class and call IDWriteTextLayout::Draw() with your renderer, then copy the result. In your class, you retrieve IDWriteGdiInterop from IDWriteFactory and call IDWriteGdiInterop::CreateBitmapRenderTarget() to get the bitmap render target; call IDWriteFactory::CreateMonitorRenderingParams() to get the rendering parameters, and call IDWriteFactory::CreateTextFormat() to setup your text format. The only significant method is DrawGlyphRun(), where you get IDWriteColorGlyphRunEnumerator with IDWriteFactory2::TranslateColorGlyphRun() and with each color run, call IDWriteBitmapRenderTarget::DrawGlyphRun() to do the work for you. Just remember to update the render target/parameters when the window size/position changes. You may reference this MSDN documentation:

Render to a GDI Surface https://msdn.microsoft.com/en-us/library/windows/desktop/ff485856(v=vs.85).aspx