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