// 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); }
void addAttributedRange (const AttributedString::Attribute& attr, IDWriteTextLayout* textLayout, const int textLen, ID2D1RenderTarget* const renderTarget, IDWriteFontCollection* const fontCollection) { DWRITE_TEXT_RANGE range; range.startPosition = attr.range.getStart(); range.length = jmin (attr.range.getLength(), textLen - attr.range.getStart()); const Font* const font = attr.getFont(); if (font != nullptr) { BOOL fontFound = false; uint32 fontIndex; fontCollection->FindFamilyName (FontStyleHelpers::getConcreteFamilyName (*font).toWideCharPointer(), &fontIndex, &fontFound); if (! fontFound) fontIndex = 0; ComSmartPtr<IDWriteFontFamily> fontFamily; HRESULT hr = fontCollection->GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress()); ComSmartPtr<IDWriteFont> dwFont; uint32 fontFacesCount = 0; fontFacesCount = fontFamily->GetFontCount(); for (int i = fontFacesCount; --i >= 0;) { hr = fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress()); if (font->getTypefaceStyle() == getFontFaceName (dwFont)) break; } textLayout->SetFontFamilyName (attr.getFont()->getTypefaceName().toWideCharPointer(), range); textLayout->SetFontWeight (dwFont->GetWeight(), range); textLayout->SetFontStretch (dwFont->GetStretch(), range); textLayout->SetFontStyle (dwFont->GetStyle(), range); const float fontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (dwFont); textLayout->SetFontSize (font->getHeight() * fontHeightToEmSizeFactor, range); } if (attr.getColour() != nullptr) { ComSmartPtr<ID2D1SolidColorBrush> d2dBrush; renderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF (attr.getColour()->getFloatRed(), attr.getColour()->getFloatGreen(), attr.getColour()->getFloatBlue(), attr.getColour()->getFloatAlpha())), d2dBrush.resetAndGetPointerAddress()); // We need to call SetDrawingEffect with a legimate brush to get DirectWrite to break text based on colours textLayout->SetDrawingEffect (d2dBrush, range); } }
bool setupLayout (const AttributedString& text, const float maxWidth, const float maxHeight, ID2D1RenderTarget& renderTarget, IDWriteFactory& directWriteFactory, IDWriteFontCollection& fontCollection, ComSmartPtr<IDWriteTextLayout>& textLayout) { // 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 Font defaultFont; BOOL fontFound = false; uint32 fontIndex; fontCollection.FindFamilyName (defaultFont.getTypeface()->getName().toWideCharPointer(), &fontIndex, &fontFound); if (! fontFound) fontIndex = 0; ComSmartPtr<IDWriteFontFamily> dwFontFamily; HRESULT hr = fontCollection.GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress()); ComSmartPtr<IDWriteFont> dwFont; hr = dwFontFamily->GetFirstMatchingFont (DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, dwFont.resetAndGetPointerAddress()); jassert (dwFont != nullptr); const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*dwFont); ComSmartPtr<IDWriteTextFormat> dwTextFormat; hr = directWriteFactory.CreateTextFormat (defaultFont.getTypefaceName().toWideCharPointer(), &fontCollection, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, defaultFont.getHeight() * defaultFontHeightToEmSizeFactor, L"en-us", dwTextFormat.resetAndGetPointerAddress()); setTextFormatProperties (text, *dwTextFormat); { DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, 0, 0 }; ComSmartPtr<IDWriteInlineObject> trimmingSign; hr = directWriteFactory.CreateEllipsisTrimmingSign (dwTextFormat, trimmingSign.resetAndGetPointerAddress()); hr = dwTextFormat->SetTrimming (&trimming, trimmingSign); } const int textLen = text.getText().length(); hr = directWriteFactory.CreateTextLayout (text.getText().toWideCharPointer(), textLen, dwTextFormat, maxWidth, maxHeight, textLayout.resetAndGetPointerAddress()); if (FAILED (hr) || textLayout == nullptr) return false; const int numAttributes = text.getNumAttributes(); for (int i = 0; i < numAttributes; ++i) addAttributedRange (*text.getAttribute (i), *textLayout, textLen, renderTarget, fontCollection); return true; }
void setupLayout (const AttributedString& text, const float& maxWidth, const float& maxHeight, ID2D1RenderTarget* const renderTarget, IDWriteFactory* const directWriteFactory, IDWriteFontCollection* const fontCollection, IDWriteTextLayout** dwTextLayout) { // 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 Font defaultFont; BOOL fontFound = false; uint32 fontIndex; fontCollection->FindFamilyName (defaultFont.getTypeface()->getName().toWideCharPointer(), &fontIndex, &fontFound); if (! fontFound) fontIndex = 0; ComSmartPtr<IDWriteFontFamily> dwFontFamily; HRESULT hr = fontCollection->GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress()); ComSmartPtr<IDWriteFont> dwFont; hr = dwFontFamily->GetFirstMatchingFont (DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, dwFont.resetAndGetPointerAddress()); const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (dwFont); jassert (directWriteFactory != nullptr); ComSmartPtr<IDWriteTextFormat> dwTextFormat; hr = directWriteFactory->CreateTextFormat (defaultFont.getTypefaceName().toWideCharPointer(), fontCollection, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, defaultFont.getHeight() * defaultFontHeightToEmSizeFactor, L"en-us", dwTextFormat.resetAndGetPointerAddress()); setTextFormatProperties (text, dwTextFormat); const int textLen = text.getText().length(); hr = directWriteFactory->CreateTextLayout (text.getText().toWideCharPointer(), textLen, dwTextFormat, maxWidth, maxHeight, dwTextLayout); const int numAttributes = text.getNumAttributes(); for (int i = 0; i < numAttributes; ++i) addAttributedRange (*text.getAttribute (i), *dwTextLayout, textLen, renderTarget, fontCollection); }
void createLayout (TextLayout& layout, const AttributedString& text, IDWriteFactory* const directWriteFactory, ID2D1Factory* const direct2dFactory, IDWriteFontCollection* const fontCollection) { // 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 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); ComSmartPtr<ID2D1DCRenderTarget> renderTarget; HRESULT hr = direct2dFactory->CreateDCRenderTarget (&d2dRTProp, renderTarget.resetAndGetPointerAddress()); Font defaultFont; BOOL fontFound = false; uint32 fontIndex; fontCollection->FindFamilyName (defaultFont.getTypefaceName().toWideCharPointer(), &fontIndex, &fontFound); if (! fontFound) fontIndex = 0; ComSmartPtr<IDWriteFontFamily> dwFontFamily; hr = fontCollection->GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress()); ComSmartPtr<IDWriteFont> dwFont; hr = dwFontFamily->GetFirstMatchingFont (DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, dwFont.resetAndGetPointerAddress()); const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (dwFont); jassert (directWriteFactory != nullptr); ComSmartPtr<IDWriteTextFormat> dwTextFormat; hr = directWriteFactory->CreateTextFormat (defaultFont.getTypefaceName().toWideCharPointer(), fontCollection, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, defaultFont.getHeight() * defaultFontHeightToEmSizeFactor, L"en-us", dwTextFormat.resetAndGetPointerAddress()); setTextFormatProperties (text, dwTextFormat); const int textLen = text.getText().length(); ComSmartPtr<IDWriteTextLayout> dwTextLayout; hr = directWriteFactory->CreateTextLayout (text.getText().toWideCharPointer(), textLen, dwTextFormat, layout.getWidth(), 1.0e7f, dwTextLayout.resetAndGetPointerAddress()); const int numAttributes = text.getNumAttributes(); for (int i = 0; i < numAttributes; ++i) addAttributedRange (*text.getAttribute (i), dwTextLayout, textLen, renderTarget, fontCollection); UINT32 actualLineCount = 0; hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount); layout.ensureStorageAllocated (actualLineCount); { ComSmartPtr<CustomDirectWriteTextRenderer> textRenderer (new CustomDirectWriteTextRenderer (fontCollection)); hr = dwTextLayout->Draw (&layout, textRenderer, 0, 0); } HeapBlock <DWRITE_LINE_METRICS> dwLineMetrics (actualLineCount); hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount); int lastLocation = 0; const int numLines = jmin ((int) actualLineCount, layout.getNumLines()); for (int i = 0; i < numLines; ++i) { lastLocation = dwLineMetrics[i].length; layout.getLine(i).stringRange = Range<int> (lastLocation, (int) lastLocation + dwLineMetrics[i].length); } }