1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License 11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). 12 13 End User License Agreement: www.juce.com/juce-6-licence 14 Privacy Policy: www.juce.com/juce-privacy-policy 15 16 Or: You may also use this code under the terms of the GPL v3 (see 17 www.gnu.org/licenses). 18 19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 21 DISCLAIMED. 22 23 ============================================================================== 24 */ 25 26 namespace juce 27 { 28 29 #if JUCE_USE_DIRECTWRITE 30 namespace 31 { getLocalisedName(IDWriteLocalizedStrings * names)32 static String getLocalisedName (IDWriteLocalizedStrings* names) 33 { 34 jassert (names != nullptr); 35 36 uint32 index = 0; 37 BOOL exists = false; 38 auto hr = names->FindLocaleName (L"en-us", &index, &exists); 39 40 if (! exists) 41 index = 0; 42 43 uint32 length = 0; 44 hr = names->GetStringLength (index, &length); 45 46 HeapBlock<wchar_t> name (length + 1); 47 hr = names->GetString (index, name, length + 1); 48 49 return static_cast<const wchar_t*> (name); 50 } 51 getFontFamilyName(IDWriteFontFamily * family)52 static String getFontFamilyName (IDWriteFontFamily* family) 53 { 54 jassert (family != nullptr); 55 ComSmartPtr<IDWriteLocalizedStrings> familyNames; 56 auto hr = family->GetFamilyNames (familyNames.resetAndGetPointerAddress()); 57 jassert (SUCCEEDED (hr)); ignoreUnused (hr); 58 return getLocalisedName (familyNames); 59 } 60 getFontFaceName(IDWriteFont * font)61 static String getFontFaceName (IDWriteFont* font) 62 { 63 jassert (font != nullptr); 64 ComSmartPtr<IDWriteLocalizedStrings> faceNames; 65 auto hr = font->GetFaceNames (faceNames.resetAndGetPointerAddress()); 66 jassert (SUCCEEDED (hr)); ignoreUnused (hr); 67 return getLocalisedName (faceNames); 68 } 69 convertPoint(D2D1_POINT_2F p)70 inline Point<float> convertPoint (D2D1_POINT_2F p) noexcept { return Point<float> ((float) p.x, (float) p.y); } 71 } 72 73 class Direct2DFactories 74 { 75 public: Direct2DFactories()76 Direct2DFactories() 77 { 78 if (direct2dDll.open ("d2d1.dll")) 79 { 80 JUCE_LOAD_WINAPI_FUNCTION (direct2dDll, D2D1CreateFactory, d2d1CreateFactory, 81 HRESULT, (D2D1_FACTORY_TYPE, REFIID, D2D1_FACTORY_OPTIONS*, void**)) 82 83 if (d2d1CreateFactory != nullptr) 84 { 85 D2D1_FACTORY_OPTIONS options; 86 options.debugLevel = D2D1_DEBUG_LEVEL_NONE; 87 88 d2d1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof (ID2D1Factory), &options, 89 (void**) d2dFactory.resetAndGetPointerAddress()); 90 } 91 } 92 93 if (directWriteDll.open ("DWrite.dll")) 94 { 95 JUCE_LOAD_WINAPI_FUNCTION (directWriteDll, DWriteCreateFactory, dWriteCreateFactory, 96 HRESULT, (DWRITE_FACTORY_TYPE, REFIID, IUnknown**)) 97 98 if (dWriteCreateFactory != nullptr) 99 { 100 dWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof (IDWriteFactory), 101 (IUnknown**) directWriteFactory.resetAndGetPointerAddress()); 102 103 if (directWriteFactory != nullptr) 104 directWriteFactory->GetSystemFontCollection (systemFonts.resetAndGetPointerAddress()); 105 } 106 107 if (d2dFactory != nullptr) 108 { 109 auto d2dRTProp = D2D1::RenderTargetProperties (D2D1_RENDER_TARGET_TYPE_SOFTWARE, 110 D2D1::PixelFormat (DXGI_FORMAT_B8G8R8A8_UNORM, 111 D2D1_ALPHA_MODE_IGNORE), 112 0, 0, 113 D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE, 114 D2D1_FEATURE_LEVEL_DEFAULT); 115 116 d2dFactory->CreateDCRenderTarget (&d2dRTProp, directWriteRenderTarget.resetAndGetPointerAddress()); 117 } 118 } 119 } 120 ~Direct2DFactories()121 ~Direct2DFactories() 122 { 123 d2dFactory = nullptr; // (need to make sure these are released before deleting the DynamicLibrary objects) 124 directWriteFactory = nullptr; 125 systemFonts = nullptr; 126 directWriteRenderTarget = nullptr; 127 } 128 129 ComSmartPtr<ID2D1Factory> d2dFactory; 130 ComSmartPtr<IDWriteFactory> directWriteFactory; 131 ComSmartPtr<IDWriteFontCollection> systemFonts; 132 ComSmartPtr<ID2D1DCRenderTarget> directWriteRenderTarget; 133 134 private: 135 DynamicLibrary direct2dDll, directWriteDll; 136 137 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DFactories) 138 }; 139 140 //============================================================================== 141 class WindowsDirectWriteTypeface : public Typeface 142 { 143 public: WindowsDirectWriteTypeface(const Font & font,IDWriteFontCollection * fontCollection)144 WindowsDirectWriteTypeface (const Font& font, IDWriteFontCollection* fontCollection) 145 : Typeface (font.getTypefaceName(), font.getTypefaceStyle()) 146 { 147 jassert (fontCollection != nullptr); 148 149 BOOL fontFound = false; 150 uint32 fontIndex = 0; 151 auto hr = fontCollection->FindFamilyName (font.getTypefaceName().toWideCharPointer(), &fontIndex, &fontFound); 152 153 if (! fontFound) 154 fontIndex = 0; 155 156 // Get the font family using the search results 157 // Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family 158 ComSmartPtr<IDWriteFontFamily> dwFontFamily; 159 hr = fontCollection->GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress()); 160 161 // Get a specific font in the font family using typeface style 162 { 163 ComSmartPtr<IDWriteFont> dwFont; 164 165 for (int i = (int) dwFontFamily->GetFontCount(); --i >= 0;) 166 { 167 hr = dwFontFamily->GetFont (i, dwFont.resetAndGetPointerAddress()); 168 169 if (i == 0) 170 break; 171 172 ComSmartPtr<IDWriteLocalizedStrings> faceNames; 173 hr = dwFont->GetFaceNames (faceNames.resetAndGetPointerAddress()); 174 175 if (font.getTypefaceStyle() == getLocalisedName (faceNames)) 176 break; 177 } 178 179 jassert (dwFont != nullptr); 180 hr = dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress()); 181 } 182 183 if (dwFontFace != nullptr) 184 { 185 DWRITE_FONT_METRICS dwFontMetrics; 186 dwFontFace->GetMetrics (&dwFontMetrics); 187 188 // All Font Metrics are in design units so we need to get designUnitsPerEm value 189 // to get the metrics into Em/Design Independent Pixels 190 designUnitsPerEm = dwFontMetrics.designUnitsPerEm; 191 192 ascent = std::abs ((float) dwFontMetrics.ascent); 193 auto totalSize = ascent + std::abs ((float) dwFontMetrics.descent); 194 ascent /= totalSize; 195 unitsToHeightScaleFactor = designUnitsPerEm / totalSize; 196 197 auto tempDC = GetDC (0); 198 auto dpi = (GetDeviceCaps (tempDC, LOGPIXELSX) + GetDeviceCaps (tempDC, LOGPIXELSY)) / 2.0f; 199 heightToPointsFactor = (dpi / GetDeviceCaps (tempDC, LOGPIXELSY)) * unitsToHeightScaleFactor; 200 ReleaseDC (0, tempDC); 201 202 auto pathAscent = (1024.0f * dwFontMetrics.ascent) / designUnitsPerEm; 203 auto pathDescent = (1024.0f * dwFontMetrics.descent) / designUnitsPerEm; 204 auto pathScale = 1.0f / (std::abs (pathAscent) + std::abs (pathDescent)); 205 pathTransform = AffineTransform::scale (pathScale); 206 } 207 } 208 loadedOk() const209 bool loadedOk() const noexcept { return dwFontFace != nullptr; } 210 getAscent() const211 float getAscent() const { return ascent; } getDescent() const212 float getDescent() const { return 1.0f - ascent; } getHeightToPointsFactor() const213 float getHeightToPointsFactor() const { return heightToPointsFactor; } 214 getStringWidth(const String & text)215 float getStringWidth (const String& text) 216 { 217 auto textUTF32 = text.toUTF32(); 218 auto len = textUTF32.length(); 219 220 HeapBlock<UINT16> glyphIndices (len); 221 dwFontFace->GetGlyphIndices (textUTF32, (UINT32) len, glyphIndices); 222 223 HeapBlock<DWRITE_GLYPH_METRICS> dwGlyphMetrics (len); 224 dwFontFace->GetDesignGlyphMetrics (glyphIndices, (UINT32) len, dwGlyphMetrics, false); 225 226 float x = 0; 227 228 for (size_t i = 0; i < len; ++i) 229 x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm; 230 231 return x * unitsToHeightScaleFactor; 232 } 233 getGlyphPositions(const String & text,Array<int> & resultGlyphs,Array<float> & xOffsets)234 void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) 235 { 236 xOffsets.add (0); 237 238 auto textUTF32 = text.toUTF32(); 239 auto len = textUTF32.length(); 240 241 HeapBlock<UINT16> glyphIndices (len); 242 dwFontFace->GetGlyphIndices (textUTF32, (UINT32) len, glyphIndices); 243 HeapBlock<DWRITE_GLYPH_METRICS> dwGlyphMetrics (len); 244 dwFontFace->GetDesignGlyphMetrics (glyphIndices, (UINT32) len, dwGlyphMetrics, false); 245 246 float x = 0; 247 248 for (size_t i = 0; i < len; ++i) 249 { 250 x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm; 251 xOffsets.add (x * unitsToHeightScaleFactor); 252 resultGlyphs.add (glyphIndices[i]); 253 } 254 } 255 getOutlineForGlyph(int glyphNumber,Path & path)256 bool getOutlineForGlyph (int glyphNumber, Path& path) 257 { 258 jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty 259 auto glyphIndex = (UINT16) glyphNumber; 260 ComSmartPtr<PathGeometrySink> pathGeometrySink (new PathGeometrySink()); 261 262 dwFontFace->GetGlyphRunOutline (1024.0f, &glyphIndex, nullptr, nullptr, 263 1, false, false, pathGeometrySink); 264 path = pathGeometrySink->path; 265 266 if (! pathTransform.isIdentity()) 267 path.applyTransform (pathTransform); 268 269 return true; 270 } 271 getIDWriteFontFace() const272 IDWriteFontFace* getIDWriteFontFace() const noexcept { return dwFontFace; } 273 getUnitsToHeightScaleFactor() const274 float getUnitsToHeightScaleFactor() const noexcept { return unitsToHeightScaleFactor; } 275 276 private: 277 SharedResourcePointer<Direct2DFactories> factories; 278 ComSmartPtr<IDWriteFontFace> dwFontFace; 279 float unitsToHeightScaleFactor = 1.0f, heightToPointsFactor = 1.0f, ascent = 0; 280 int designUnitsPerEm = 0; 281 AffineTransform pathTransform; 282 283 struct PathGeometrySink : public ComBaseClassHelper<IDWriteGeometrySink> 284 { PathGeometrySinkjuce::WindowsDirectWriteTypeface::PathGeometrySink285 PathGeometrySink() : ComBaseClassHelper<IDWriteGeometrySink> (0) {} 286 AddBeziersjuce::WindowsDirectWriteTypeface::PathGeometrySink287 void __stdcall AddBeziers (const D2D1_BEZIER_SEGMENT* beziers, UINT beziersCount) noexcept override 288 { 289 for (UINT i = 0; i < beziersCount; ++i) 290 path.cubicTo (convertPoint (beziers[i].point1), 291 convertPoint (beziers[i].point2), 292 convertPoint (beziers[i].point3)); 293 } 294 AddLinesjuce::WindowsDirectWriteTypeface::PathGeometrySink295 void __stdcall AddLines (const D2D1_POINT_2F* points, UINT pointsCount) noexcept override 296 { 297 for (UINT i = 0; i < pointsCount; ++i) 298 path.lineTo (convertPoint (points[i])); 299 } 300 BeginFigurejuce::WindowsDirectWriteTypeface::PathGeometrySink301 void __stdcall BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN) noexcept override 302 { 303 path.startNewSubPath (convertPoint (startPoint)); 304 } 305 EndFigurejuce::WindowsDirectWriteTypeface::PathGeometrySink306 void __stdcall EndFigure (D2D1_FIGURE_END figureEnd) noexcept override 307 { 308 if (figureEnd == D2D1_FIGURE_END_CLOSED) 309 path.closeSubPath(); 310 } 311 SetFillModejuce::WindowsDirectWriteTypeface::PathGeometrySink312 void __stdcall SetFillMode (D2D1_FILL_MODE fillMode) noexcept override 313 { 314 path.setUsingNonZeroWinding (fillMode == D2D1_FILL_MODE_WINDING); 315 } 316 SetSegmentFlagsjuce::WindowsDirectWriteTypeface::PathGeometrySink317 void __stdcall SetSegmentFlags (D2D1_PATH_SEGMENT) noexcept override {} Closejuce::WindowsDirectWriteTypeface::PathGeometrySink318 JUCE_COMRESULT Close() noexcept override { return S_OK; } 319 320 Path path; 321 322 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathGeometrySink) 323 }; 324 325 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsDirectWriteTypeface) 326 }; 327 328 #endif 329 330 } // namespace juce 331