// This method takes an attributed string and outputs a GlyphLayout data // structure that contains the glyph number and location of each inidividual glyph void getGlyphLayout (const AttributedString& text, GlyphLayout& glyphLayout) { // For now we are creating the DirectWrite Factory, System Font Collection, // D2D Factory and GDI Render target every time we layout text. // This is inefficient and we may be loading and unloading libraries each layout. // These four things should be created once at application startup and be destroyed // when the application exits. I'm not sure where the best place to do this so // for now I will just use the inefficient method. IDWriteFactory* dwFactory = nullptr; HRESULT hr = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&dwFactory)); IDWriteFontCollection* dwFontCollection = nullptr; hr = dwFactory->GetSystemFontCollection (&dwFontCollection); // To add color to text, we need to create a D2D render target // Since we are not actually rendering to a D2D context we create a temporary GDI render target ID2D1Factory *d2dFactory = nullptr; hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2dFactory); D2D1_RENDER_TARGET_PROPERTIES d2dRTProp = D2D1::RenderTargetProperties( D2D1_RENDER_TARGET_TYPE_SOFTWARE, D2D1::PixelFormat( DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), 0, 0, D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE, D2D1_FEATURE_LEVEL_DEFAULT ); ID2D1DCRenderTarget* d2dDCRT = nullptr; hr = d2dFactory->CreateDCRenderTarget (&d2dRTProp, &d2dDCRT); // Initially we set the paragraph up with a default font and then apply the attributed string ranges later Font defaultFont; const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (defaultFont, *dwFontCollection); // We should probably be detecting the locale instead of hard coding it to en-us String localeName("en-us"); // We multiply the font height by the size factor so we layout text at the correct size IDWriteTextFormat* dwTextFormat = nullptr; hr = dwFactory->CreateTextFormat ( defaultFont.getTypefaceName().toWideCharPointer(), dwFontCollection, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, defaultFont.getHeight() * defaultFontHeightToEmSizeFactor, localeName.toWideCharPointer(), &dwTextFormat ); // Paragraph Attributes // Set Paragraph Alignment if (text.getTextAlignment() == AttributedString::left) dwTextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_LEADING); if (text.getTextAlignment() == AttributedString::right) dwTextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_TRAILING); if (text.getTextAlignment() == AttributedString::center) dwTextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_CENTER); // DirectWrite cannot justify text, default to left alignment if (text.getTextAlignment() == AttributedString::justified) dwTextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_LEADING); // Set Word Wrap if (text.getWordWrap() == AttributedString::none) dwTextFormat->SetWordWrapping (DWRITE_WORD_WRAPPING_NO_WRAP); if (text.getWordWrap() == AttributedString::byWord) dwTextFormat->SetWordWrapping (DWRITE_WORD_WRAPPING_WRAP); // DirectWrite does not support wrapping by character, default to wrapping by word if (text.getWordWrap() == AttributedString::byChar) dwTextFormat->SetWordWrapping (DWRITE_WORD_WRAPPING_WRAP); // DirectWrite does not automatically set reading direction // This must be set correctly and manually when using RTL Scripts (Hebrew, Arabic) if (text.getReadingDirection() == AttributedString::rightToLeft) dwTextFormat->SetReadingDirection (DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); IDWriteTextLayout* dwTextLayout = nullptr; hr = dwFactory->CreateTextLayout ( text.getText().toWideCharPointer(), text.getText().length(), dwTextFormat, glyphLayout.getWidth(), glyphLayout.getHeight(), &dwTextLayout ); // Character Attributes int numCharacterAttributes = text.getCharAttributesSize(); for (int i = 0; i < numCharacterAttributes; ++i) { Attr* attr = text.getCharAttribute (i); // Character Range Error Checking if (attr->range.getStart() > text.getText().length()) continue; if (attr->range.getEnd() > text.getText().length()) attr->range.setEnd (text.getText().length()); if (attr->attribute == Attr::font) { AttrFont* attrFont = static_cast<AttrFont*>(attr); DWRITE_TEXT_RANGE dwRange; dwRange.startPosition = attrFont->range.getStart(); dwRange.length = attrFont->range.getLength(); dwTextLayout->SetFontFamilyName (attrFont->font.getTypefaceName().toWideCharPointer(), dwRange); // We multiply the font height by the size factor so we layout text at the correct size const float fontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (attrFont->font, *dwFontCollection); dwTextLayout->SetFontSize (attrFont->font.getHeight() * fontHeightToEmSizeFactor, dwRange); } if (attr->attribute == Attr::foregroundColour) { AttrColour* attrColour = static_cast<AttrColour*>(attr); DWRITE_TEXT_RANGE dwRange; dwRange.startPosition = attrColour->range.getStart(); dwRange.length = attrColour->range.getLength(); ID2D1SolidColorBrush* d2dBrush = nullptr; d2dDCRT->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF(attrColour->colour.getFloatRed(), attrColour->colour.getFloatGreen(), attrColour->colour.getFloatBlue(), attrColour->colour.getFloatAlpha())), &d2dBrush); // We need to call SetDrawingEffect with a legimate brush to get DirectWrite to break text based on colours dwTextLayout->SetDrawingEffect (d2dBrush, dwRange); safeRelease (&d2dBrush); } } UINT32 actualLineCount = 0; hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount); // Preallocate GlyphLayout Line Array glyphLayout.setNumLines (actualLineCount); HeapBlock <DWRITE_LINE_METRICS> dwLineMetrics (actualLineCount); hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount); int location = 0; // Create GlyphLine structures for each line in the layout for (UINT32 i = 0; i < actualLineCount; ++i) { // Get string range Range<int> lineStringRange (location, (int) location + dwLineMetrics[i].length); location = dwLineMetrics[i].length; GlyphLine* glyphLine = new GlyphLine(); glyphLine->setStringRange (lineStringRange); glyphLayout.addGlyphLine (glyphLine); } // To copy glyph data from DirectWrite into our own data structures we must create our // own CustomTextRenderer. Instead of passing the draw method an actual graphics context, // we pass it the GlyphLayout object that needs to be filled with glyphs. CustomDirectWriteTextRenderer* textRenderer = nullptr; textRenderer = new CustomDirectWriteTextRenderer(); hr = dwTextLayout->Draw ( &glyphLayout, textRenderer, glyphLayout.getX(), glyphLayout.getY() ); safeRelease (&textRenderer); safeRelease (&dwTextLayout); safeRelease (&dwTextFormat); safeRelease (&d2dDCRT); safeRelease (&d2dFactory); safeRelease (&dwFontCollection); safeRelease (&dwFactory); }
int CDirectWriteRenderer::GetFitCharCount(LPCWSTR pText, int Length, int Width, CDirectWriteFont &Font) { if (pText == nullptr || Length == 0) return 0; if (m_pRenderTarget == nullptr) return 0; int FitCharCount = 0; IDWriteFactory *pFactory = m_System.GetDWriteFactory(); if (pFactory != nullptr) { IDWriteTextFormat *pTextFormat = Font.GetTextFormat(); if (pTextFormat != nullptr) { IDWriteTextLayout *pTextLayout; pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); if (Length < 0) Length = ::lstrlenW(pText); HRESULT hr = pFactory->CreateTextLayout( pText, Length, pTextFormat, static_cast<float>(Width), m_pRenderTarget->GetSize().height, &pTextLayout); if (SUCCEEDED(hr)) { Util::CTempBuffer<DWRITE_CLUSTER_METRICS, 256> ClusterMetrics(Length); UINT32 ClusterCount; hr = pTextLayout->GetClusterMetrics(ClusterMetrics.GetBuffer(), Length, &ClusterCount); if (SUCCEEDED(hr)) { float Pos = 0.0f; for (UINT32 i = 0; i < ClusterCount; i++) { Pos += ClusterMetrics[i].width; if (static_cast<int>(std::ceil(Pos)) > Width) break; FitCharCount += ClusterMetrics[i].length; } } pTextLayout->Release(); } pTextFormat->Release(); } pFactory->Release(); } return FitCharCount; }
bool CDirectWriteRenderer::DrawText( LPCWSTR pText, int Length, const RECT &Rect, CDirectWriteFont &Font, CDirectWriteBrush &Brush, unsigned int Flags) { if (pText == nullptr) return false; if (m_pRenderTarget == nullptr) return false; bool fOK = false; IDWriteTextFormat *pTextFormat = Font.GetTextFormat(); if (pTextFormat != nullptr) { ID2D1Brush *pBrush = Brush.GetBrush(); if (pBrush != nullptr) { pTextFormat->SetTextAlignment( (Flags & DRAW_TEXT_ALIGN_HORZ_CENTER) != 0 ? DWRITE_TEXT_ALIGNMENT_CENTER : (Flags & DRAW_TEXT_ALIGN_RIGHT) != 0 ? DWRITE_TEXT_ALIGNMENT_TRAILING : ((Flags & DRAW_TEXT_ALIGN_JUSTIFIED) != 0 && Util::OS::IsWindows8OrLater()) ? DWRITE_TEXT_ALIGNMENT_JUSTIFIED : DWRITE_TEXT_ALIGNMENT_LEADING); pTextFormat->SetParagraphAlignment( (Flags & DRAW_TEXT_ALIGN_VERT_CENTER) != 0 ? DWRITE_PARAGRAPH_ALIGNMENT_CENTER : (Flags & DRAW_TEXT_ALIGN_BOTTOM) != 0 ? DWRITE_PARAGRAPH_ALIGNMENT_FAR : DWRITE_PARAGRAPH_ALIGNMENT_NEAR); pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); DWRITE_TRIMMING Trimming = {DWRITE_TRIMMING_GRANULARITY_NONE, 0, 0}; pTextFormat->SetTrimming(&Trimming, nullptr); m_pRenderTarget->DrawText( pText, Length >= 0 ? Length : ::lstrlenW(pText), pTextFormat, D2DRectF(Rect), pBrush); fOK = true; pBrush->Release(); } pTextFormat->Release(); } return fOK; }
bool CDirectWriteRenderer::GetTextMetrics( LPCWSTR pText, int Length, CDirectWriteFont &Font, TextMetrics *pMetrics) { if (pText == nullptr || pMetrics == nullptr) return false; if (m_pRenderTarget == nullptr) return false; HRESULT hr = E_UNEXPECTED; IDWriteFactory *pFactory = m_System.GetDWriteFactory(); if (pFactory != nullptr) { IDWriteTextFormat *pTextFormat = Font.GetTextFormat(); if (pTextFormat != nullptr) { D2D1_SIZE_F Size; IDWriteTextLayout *pTextLayout; pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); if (Length < 0) Length = ::lstrlenW(pText); Size = m_pRenderTarget->GetSize(); hr = pFactory->CreateTextLayout( pText, Length, pTextFormat, Size.width, Size.height, &pTextLayout); if (SUCCEEDED(hr)) { DWRITE_TEXT_METRICS Metrics; hr = pTextLayout->GetMetrics(&Metrics); if (SUCCEEDED(hr)) { pMetrics->Width = Metrics.width; pMetrics->WidthIncludingTrailingWhitespace = Metrics.widthIncludingTrailingWhitespace; pMetrics->Height = Metrics.height; } pTextLayout->Release(); } pTextFormat->Release(); } pFactory->Release(); } return SUCCEEDED(hr); }
void DWriteContext::SetFont(const LOGFONTW &logFont) { SafeRelease(&mTextFormat); mLastHFont = NULL; HRESULT hr = SetLOGFONT(logFont, 0.f); if (SUCCEEDED(hr)) hr = mTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); if (SUCCEEDED(hr)) hr = mTextFormat->SetParagraphAlignment( DWRITE_PARAGRAPH_ALIGNMENT_CENTER); if (SUCCEEDED(hr)) hr = mTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); }
void setTextFormatProperties (const AttributedString& text, IDWriteTextFormat& format) { DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT_LEADING; DWRITE_WORD_WRAPPING wrapType = DWRITE_WORD_WRAPPING_WRAP; switch (text.getJustification().getOnlyHorizontalFlags()) { case Justification::left: break; case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break; case Justification::horizontallyCentred: alignment = DWRITE_TEXT_ALIGNMENT_CENTER; break; case Justification::horizontallyJustified: break; // DirectWrite cannot justify text, default to left alignment default: jassertfalse; break; // Illegal justification flags } switch (text.getWordWrap()) { case AttributedString::none: wrapType = DWRITE_WORD_WRAPPING_NO_WRAP; break; case AttributedString::byWord: break; case AttributedString::byChar: break; // DirectWrite doesn't support wrapping by character, default to word-wrap default: jassertfalse; break; // Illegal flags! } // DirectWrite does not automatically set reading direction // This must be set correctly and manually when using RTL Scripts (Hebrew, Arabic) if (text.getReadingDirection() == AttributedString::rightToLeft) { format.SetReadingDirection (DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); switch (text.getJustification().getOnlyHorizontalFlags()) { case Justification::left: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break; case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_LEADING; break; default: break; } } format.SetTextAlignment (alignment); format.SetWordWrapping (wrapType); }