-
Notifications
You must be signed in to change notification settings - Fork 3.2k
/
CustomFontSetManager.cpp
874 lines (721 loc) · 39.2 KB
/
CustomFontSetManager.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************
#include "stdafx.h"
#include "BinaryResources.h" // Used in CreateFontSetUsingInMemoryFontData() for loading font data embedded in the app binary.
#include "CustomFontSetManager.h"
#include "Document.h" // Used in CreateFontSetUsingInMemoryFontData() to simulate a document with embedded font data.
#include "FileHelper.h"
#include "FontDownloadListener.h" // Used in GetFontDataDetails() when there are remote fonts (scenario 3)
#include "PackedFontFileLoader.h" // Used in CreateFontSetUsingPackedFontData() for a custom font file loader that handles packed font container formats.
using Microsoft::WRL::ComPtr;
namespace DWriteCustomFontSets
{
//**********************************************************************
//
// Constructors, destructors
//
//**********************************************************************
CustomFontSetManager::CustomFontSetManager()
{
HRESULT hr;
// IDWriteFactory3 supports APIs available in any Windows 10 version (build 10240 or later).
DX::ThrowIfFailed(
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), &m_dwriteFactory3)
);
#ifndef FORCE_TH1_IMPLEMENTATION
// IDWriteFactory5 supports APIs available in Windows 10 Creators Update (preview build 15021 or later).
hr = m_dwriteFactory3.As(&m_dwriteFactory5);
if (hr == E_NOINTERFACE)
{
// Let this go. Later, if we might use the interface, we'll branch gracefully.
}
else
{
DX::ThrowIfFailed(hr);
}
#endif // !FORCE_TH1_IMPLEMENTATION
} // end CustomFontSetManager::CustomFontSetManager()
CustomFontSetManager::~CustomFontSetManager()
{
// Some scenarios register loaders. These need to be unregistered before exiting.
// This will be relevant in scenario 3, when CreateFontSetUsingKnownRemoteFonts() is called.
UnregisterFontFileLoader(m_remoteFontFileLoader.Get());
// This will be relevant in scenario 4, when CreateFontSetUsingInMemoryFontData() is called.
UnregisterFontFileLoader(m_inMemoryFontFileLoader.Get());
// This will be relevant in scenario 5, when CreateFontSetUsingPackedFontData() is called.
UnregisterFontFileLoader(m_packedFontFileLoader.Get());
} // end CustomFontSetManager::~CustomFontSetManager()
//**********************************************************************
//
// Method for checking API availability.
//
//**********************************************************************
bool CustomFontSetManager::IDWriteFactory5_IsAvailable()
{
return m_dwriteFactory5 != nullptr;
}
//**********************************************************************
//
// Methods for the creating a font set under the various scenarios.
//
//**********************************************************************
void CustomFontSetManager::CreateFontSetUsingLocalFontFiles(const std::vector<std::wstring>& selectedFilePathNames)
{
// Requires any version of Windows 10.
// Creates a custom font set for font files at paths in local storage. If a file is
// an OpenType collection file, which contains multiple fonts, all of the fonts will
// be added to the collection.
//
// If running on Windows 10 Creators Update (preview build 15021 or later), the
// IDWriteFontSetBuilder1::AddFontFile method will be used. This method handles all of
// the fonts in an OpenType collection file in a single call, and it also supports
// OpenType variable fonts, which can be realized as many different font faces -- all
// named instances in the variable font will be added in a single call. This method is
// recommended when available.
//
// If running on earlier Windows 10 versions, the method used will be
// IDWriteFontSetBuilder::AddFontFaceReference. This does not support OpenType variable
// fonts, and also requires that a font file first be analyzed to determine whether it
// is an OpenType collection file, in which case each font must be handled in a separate
// call.
//
// If one of the input path names is not a font file, it will be ignored.
//
// When creating a custom font set with font files that are not assumed to be known by
// the app, DWrite will need to extract some basic font properties, such as names,
// directly from the font files. This will result in a little extra I/O overhead.
// Check if IDWriteFontSetBuilder1 will be available (we're running on preview build 15021 or later)
if (m_dwriteFactory5 != nullptr)
{
// We'll need an IDWriteFontFile for each font file to be added to the font set.
// We won't assume every file is a font file in a supported format; if not, we'll
// ignore the file.
// Get the font set builder -- IDWriteFontSetBuilder1.
ComPtr<IDWriteFontSetBuilder1> fontSetBuilder;
DX::ThrowIfFailed(
m_dwriteFactory5->CreateFontSetBuilder(&fontSetBuilder)
);
// Loop over the file paths.
for (auto& filePath : selectedFilePathNames)
{
ComPtr<IDWriteFontFile> fontFile;
DX::ThrowIfFailed(
m_dwriteFactory5->CreateFontFileReference(filePath.c_str(), /* filetime */ nullptr, &fontFile)
);
// Add to the font set builder. If the file is a collection, all of the fonts will
// get added. If the file is not a supported font file, the call will fail; we'll
// check for that and ignore.
HRESULT hr = fontSetBuilder->AddFontFile(fontFile.Get());
if ((hr != DWRITE_E_FILEFORMAT) && (hr != DWRITE_E_FILENOTFOUND) && (hr != DWRITE_E_FILEACCESS))
{
// Ignore file format or access errors.
DX::ThrowIfFailed(hr);
}
} // for loop
// Now create the custom font set.
DX::ThrowIfFailed(
fontSetBuilder->CreateFontSet(&m_customFontSet)
);
}
else
{
// We're limited to APIs and functionality available on earlier Windows 10 versions, prior
// to the Windows 10 Creators Update (preview build 15021).
//
// Also, we'll need an IDWriteFontFaceReference for each font to be added to the font set.
// If a file is an OpenType collection, it may contain multiple fonts. For that reason,
// we'll need to analyze the file to get the count of fonts, and then provide an index
// when creating each font face reference.
// Get the font set builder - IDWriteFontSetBuilder.
ComPtr<IDWriteFontSetBuilder> fontSetBuilder;
DX::ThrowIfFailed(
m_dwriteFactory3->CreateFontSetBuilder(&fontSetBuilder)
);
// Loop over the file paths.
for (auto& filePath : selectedFilePathNames)
{
ComPtr<IDWriteFontFile> fontFile;
DX::ThrowIfFailed(
m_dwriteFactory3->CreateFontFileReference(filePath.c_str(), /* filetime */ nullptr, &fontFile)
);
// Confirm the file is a supported font file and get the collection face count.
BOOL isSupported;
DWRITE_FONT_FILE_TYPE fileType;
UINT32 numberOfFonts;
DX::ThrowIfFailed(
fontFile->Analyze(&isSupported, &fileType, /* face type */ nullptr, &numberOfFonts)
);
if (!isSupported)
continue;
// For each font within the font file, get a font face reference and add to the builder.
for (UINT32 fontIndex = 0; fontIndex < numberOfFonts; fontIndex++)
{
ComPtr<IDWriteFontFaceReference> fontFaceReference;
DX::ThrowIfFailed(
m_dwriteFactory3->CreateFontFaceReference(fontFile.Get(), fontIndex, DWRITE_FONT_SIMULATIONS_NONE, &fontFaceReference)
);
// If fonts were assumed known, we could set custom properties, and would do that here.
// But these are not assumed known, so we'll leave it to DirectWrite to read properties
// directly out of the font files.
DX::ThrowIfFailed(
fontSetBuilder->AddFontFaceReference(fontFaceReference.Get())
);
} // for loop -- over fonts with font file
} // for loop -- over font files
// Now create the custom font set
DX::ThrowIfFailed(
fontSetBuilder->CreateFontSet(&m_customFontSet)
);
} // end if (IDWriteFactory5_IsAvailable())
} // end CustomFontSetManager::CreateFontSetUsingLocalFontFiles()
void CustomFontSetManager::CreateFontSetUsingKnownAppFonts()
{
// Requires any version of Windows 10.
// Creates a custom font set using fonts known by and bundled with the app. Since the fonts
// are known, we can apply custom font properties when the fonts are added to the font set,
// which will be the properties used within the app to reference the fonts. This saves a
// bit of file I/O, and makes it easier to change the fonts used in the app since details
// can be changed in one place.
//
// The details for the set of app-provided fonts are defined in the g_appFonts[] array
// within Statics.cpp.
//
// If a file is an OpenType collection file, which contains multiple fonts, we can specify
// which of the fonts within the file is to be used.
//
// As of Windows 10 Creators Update, OpenType variable fonts are not supported in this scenario --
// specifically, there is no way to add a specific variation instance with custom properties
// to a custom font set.
// Get the application install path.
std::wstring applicationPath;
if (!FileHelper::GetApplicationPath(applicationPath))
{
OutputDebugString(L"\nThere was an unexpected error attempting to get the app install path, so the custom font set with app fonts cannot be created.\n\n");
return;
}
// Get the font set builder.
ComPtr<IDWriteFontSetBuilder> fontSetBuilder;
DX::ThrowIfFailed(
m_dwriteFactory3->CreateFontSetBuilder(&fontSetBuilder)
);
// Add the known app fonts to the font set.
for (uint32_t fontIndex = 0; fontIndex < g_appFontsCount; fontIndex++)
{
AppFontInfo const& fontInfo = g_appFonts[fontIndex];
std::wstring fontFilePath(applicationPath + fontInfo.fontRelativeLocation);
// Check that the font got deployed with the app. (If not, CreateFontFaceReference would fail.)
if (!FileHelper::PathExists(fontFilePath))
{
std::wstring debugString(L"\nApp font file is missing: " + fontFilePath + L"\n\n");
OutputDebugString(debugString.c_str());
continue;
}
// Create a font face reference for the specific font (requires file plus collection index).
ComPtr<IDWriteFontFaceReference> fontFaceReference;
DX::ThrowIfFailed(
m_dwriteFactory3->CreateFontFaceReference(fontFilePath.c_str(), /* filetime*/ nullptr, fontInfo.fontIndex, DWRITE_FONT_SIMULATIONS_NONE, &fontFaceReference)
);
// Set up custom font properties for app-internal use.
DWRITE_FONT_PROPERTY props[] =
{
// We're only using names to reference fonts programmatically, so won't worry about localized names.
{ DWRITE_FONT_PROPERTY_ID_FAMILY_NAME, fontInfo.familyName, L"en-US" },
{ DWRITE_FONT_PROPERTY_ID_FULL_NAME, fontInfo.fullName, L"en-US" },
{ DWRITE_FONT_PROPERTY_ID_WEIGHT, fontInfo.fontWeight, nullptr }
};
// Now add the font to the font set with the custom properties.
DX::ThrowIfFailed(
fontSetBuilder->AddFontFaceReference(fontFaceReference.Get(), props, ARRAYSIZE(props))
);
} // end for loop
// Now create the custom font set.
DX::ThrowIfFailed(
fontSetBuilder->CreateFontSet(&m_customFontSet)
);
return;
} // CustomFontSetManager::CreateFontSetUsingKnownAppFonts()
void CustomFontSetManager::CreateFontSetUsingKnownRemoteFonts()
{
// Requires Windows 10 Creators Update (preview build 15021 or later).
// Creates a font set using fonts known by the app, but remote -- located on the Web.
// Custom font properties will be applied, allowing the font set to be created without
// needing to download any of the font data.
//
// The details for the set of app-specified fonts are defined in the g_remoteFfonts[]
// array within Statics.cpp.
//
// This uses a system-provided implementation of IDWriteRemoteFontFileLoader. For each
// remote font file, we create an IDWriteFontFile, and from that create an
// IDWriteFontFaceReference, and then add that into the font set with pre-defined
// properties. The font set will be created without needing to download any font data
// beforehand.
//
// Note: In CreateFontSetUsingLocalFontFiles(), we pass the IDWriteFontFile objects into
// IDWriteFontSetBuilder1::AddFontFile() to add all of the fonts in a collection in one
// call. If we try to do that with a remote font, the AddFontFile call will fail,
// returning DWRITE_E_REMOTEFONT. The sequence shown here, using AddFontFaceReference()
// with custom properties, is required when creating a font set with remote fonts.
//
// Before using the IDWriteRemoteFontFileLoader, it must be registered with a DirectWrite
// factory object. The loader will be needed for as long as the fonts may be used within
// the app, and so it will be stored as a CustomFontSetManager member. It must be
// unregistered before it goes out of scope; that will be done in the CustomFontSetManager
// destructor.
// Get and register the system-implemented remote font file loader.
DX::ThrowIfFailed(
m_dwriteFactory5->CreateHttpFontFileLoader(
/* referrerURL */ nullptr,
/* extraHeaders */ nullptr,
&m_remoteFontFileLoader
)
);
DX::ThrowIfFailed(
m_dwriteFactory5->RegisterFontFileLoader(m_remoteFontFileLoader.Get())
);
// Get a font set builder.
ComPtr<IDWriteFontSetBuilder> fontSetBuilder;
DX::ThrowIfFailed(
m_dwriteFactory5->CreateFontSetBuilder(&fontSetBuilder)
);
// Add the remote fonts to the font set.
for (uint32_t fontIndex = 0; fontIndex < g_remoteFontsCount; fontIndex++)
{
AppFontInfo const& fontInfo = g_remoteFonts[fontIndex];
// Get an IDWriteFontFile.
ComPtr<IDWriteFontFile> fontFile;
DX::ThrowIfFailed(
m_remoteFontFileLoader->CreateFontFileReferenceFromUrl(
m_dwriteFactory5.Get(),
g_remoteFontBaseUrl,
fontInfo.fontRelativeLocation, // Can point to a raw font file (.ttf, .ttc, .otf, .otc), or to a WOFF or WOFF2 packed-format file.
&fontFile
)
);
// Get an IDWriteFontFaceReference for a font within the file.
ComPtr<IDWriteFontFaceReference> fontFaceReference;
DX::ThrowIfFailed(
m_dwriteFactory5->CreateFontFaceReference(fontFile.Get(), fontInfo.fontIndex, DWRITE_FONT_SIMULATIONS_NONE, &fontFaceReference)
);
// Set up custom font properties for app-internal use.
DWRITE_FONT_PROPERTY props[] =
{
// We're only using names to reference fonts programmatically, so won't worry about localized names.
{ DWRITE_FONT_PROPERTY_ID_FAMILY_NAME, fontInfo.familyName, L"en-US" },
{ DWRITE_FONT_PROPERTY_ID_FULL_NAME, fontInfo.fullName, L"en-US" },
{ DWRITE_FONT_PROPERTY_ID_WEIGHT, fontInfo.fontWeight, nullptr }
};
// Now add the font to the font set with the custom properties.
DX::ThrowIfFailed(
fontSetBuilder->AddFontFaceReference(fontFaceReference.Get(), props, ARRAYSIZE(props))
);
} // end for loop
// Now create the custom font set.
DX::ThrowIfFailed(
fontSetBuilder->CreateFontSet(&m_customFontSet)
);
return;
} // CustomFontSetManager::CreateFontSetUsingKnownRemoteFonts()
void CustomFontSetManager::CreateFontSetUsingInMemoryFontData()
{
// Requires Windows 10 Creators Update (preview build 15021 or later).
// Creates a custom font set using in-memory font data.
//
// The implementation will use in-memory font data from two sources:
//
// - a font embedded within the app binary as a resource; and
// - a document with embedded font data.
//
// These are two common app scenarios, but the implementation can be adapted
// to other scenarios in which font data is loaded into memory.
//
// The BinaryResources class handles loading of the font embedded in the app
// as a binary resource.
//
// The Document class simulates a document with embedded font data. In a real
// scenario, the document would be read from a stream. As a simplification,
// this simulation uses static data.
//
// The data in the memory buffer is expected to be raw, OpenType font data,
// not data in a compressed, packed format such as WOFF2. For support of
// packed-format font data, see scenario 5.
// This will use a system implementation of IDWriteInMemoryFontFileLoader.
// Before a font file loader can be used, it must be registered with a
// DirectWrite factory object. The loader will be needed for as long as the
// fonts may be used within the app, and so it will be stored as a
// CustomFontSetManager member. It must be unregistered before it goes out of
// scope; that will be done in the CustomFontSetManager destructor.
// Get and register the system-implemented in-memory font file loader.
DX::ThrowIfFailed(
m_dwriteFactory5->CreateInMemoryFontFileLoader(&m_inMemoryFontFileLoader)
);
DX::ThrowIfFailed(
m_dwriteFactory5->RegisterFontFileLoader(m_inMemoryFontFileLoader.Get())
);
// Get a font set builder. We're already dependent on Windows 10 Creators Update,
// so will use IDWriteFontSetBuilder1, which will save work later (won't need to
// check for an OpenType collection and loop over the individual fonts in the
// collection).
ComPtr<IDWriteFontSetBuilder1> fontSetBuilder;
DX::ThrowIfFailed(
m_dwriteFactory5->CreateFontSetBuilder(&fontSetBuilder)
);
// Load fonts embedded in the app binary as resources into memory.
ComPtr<BinaryResources> binaryResources = new BinaryResources();
std::vector<MemoryFontInfo> appFontResources;
binaryResources->GetFonts(appFontResources);
// Add the in-memory fonts to the font set.
for (uint32_t fontIndex = 0; fontIndex < appFontResources.size(); fontIndex++)
{
MemoryFontInfo fontInfo = appFontResources[fontIndex];
// For each in-memory font, get an IDWriteFontFile using the in-memory font
// file loader. Then use that to get an IDWriteFontFaceReference, and add
// the font face reference to the font set.
ComPtr<IDWriteFontFile> fontFileReference;
DX::ThrowIfFailed(
m_inMemoryFontFileLoader->CreateInMemoryFontFileReference(
m_dwriteFactory5.Get(),
fontInfo.fontData,
fontInfo.fontDataSize,
binaryResources.Get(), // Passing the binaryResources object as the data owner -- data lifetime is managed by the owner, so DirectWrite won't make a copy.
&fontFileReference
)
);
// The data may represent an OpenType collection file, which would include multiple
// fonts. By using IDWriteFontSetBuilder1::AddFontFile, all of the fonts in a
// collection and all of the named instances in variable fonts are added in a single
// call.
// We're assuming here that the in-memory data is font data in a supported format.
// Otherwise, we should check for the AddFontFile call failing with error
// DWRITE_E_FILEFORMAT.
// Since the fonts are embedded in an app binary, they are known in advance, and so
// custom font properties could be used. In that case, the custom properties would
// be specified here, and AddFontFaceReference would be used instead of AddFontFile.
// See CreateFontSetUsingKnownAppFonts in this file for how that would be done.
DX::ThrowIfFailed(
fontSetBuilder->AddFontFile(fontFileReference.Get())
);
}
// Get our simulated document that has embedded font data, and get the document
// text and a vector of embedded font data.
ComPtr<Documents::Document> document = new Documents::Document();
std::wstring text = document->GetText();
std::vector<MemoryFontInfo> documentFonts;
document->GetFonts(documentFonts);
// Add the in-memory fonts to the font set.
for (uint32_t fontIndex = 0; fontIndex < documentFonts.size(); fontIndex++)
{
MemoryFontInfo fontInfo = documentFonts[fontIndex];
ComPtr<IDWriteFontFile> fontFileReference;
DX::ThrowIfFailed(
m_inMemoryFontFileLoader->CreateInMemoryFontFileReference(
m_dwriteFactory5.Get(),
fontInfo.fontData,
fontInfo.fontDataSize,
document.Get(), // Passing the document object as the data owner -- data lifetime is managed by the owner, so DirectWrite won't make a copy.
&fontFileReference
)
);
DX::ThrowIfFailed(
fontSetBuilder->AddFontFile(fontFileReference.Get())
);
} // end for -- loop over fonts
// Now create the custom font set.
DX::ThrowIfFailed(
fontSetBuilder->CreateFontSet(&m_customFontSet)
);
return;
} // CustomFontSetManager::CreateFontSetUsingInMemoryFontData()
void CustomFontSetManager::CreateFontSetUsingPackedFontData()
{
// Requires Windows 10 Creators Update (preview build 15021 or later).
// Creates a font set using data in packed, WOFF2 format to demonstrate DirectWrite APIs
// for unpacking font data in packed WOFF or WOFF2 formats.
//
// The font data for this scenario is static data defined in Statics.cpp.
//
// This uses a custom implementation of IDWriteFontFileLoader that utilizes the APIs for
// unpacking the packed font data. The font file loader can also handle font data not in
// a packed format.
//
// An IDWriteFontFileLoader implementation needs to provide access to the data via a
// callback to an IDWriteFontFileStream object. In the case of packed font data, the
// method for unpacking returns an IDWriteFontFileStream, making this case simple to
// handle. If the font data is not contained in a packed format, then a custom
// implementation of IDWriteFontFileStream would need to be used.
// Before a font file loader can be used, it must be registered with a
// DirectWrite factory object. The loader will be needed for as long as the
// fonts may be used within the app, and so it will be stored as a
// CustomFontSetManager member. It must be unregistered before it goes out of
// scope; that will be done in the CustomFontSetManager destructor.
// Get and register the custom-implementation of IDWriteFontFileLoader that we'll
// use to handle unpacking of packed font data.
m_packedFontFileLoader = new PackedFontFileLoader(m_dwriteFactory5.Get());
DX::ThrowIfFailed(
m_dwriteFactory5->RegisterFontFileLoader(m_packedFontFileLoader.Get())
);
// Get a font set builder. We're already dependent on Windows 10 Creators Update,
// so will use IDWriteFontSetBuilder1, which will save work later (won't need to
// check for an OpenType collection and loop over the individual fonts in the
// collection).
ComPtr<IDWriteFontSetBuilder1> fontSetBuilder;
DX::ThrowIfFailed(
m_dwriteFactory5->CreateFontSetBuilder(&fontSetBuilder)
);
// For each font to be added to the font set, we need to create an IDWriteFontFile
// object that carries the custom font file loader that handles the font data.
for (uint32_t fontIndex = 0; fontIndex < g_packedFontsCount; fontIndex++)
{
// For each font, we get an IDWriteFontFile using CreateCustomFontFileReference.
// This takes a key, which is used by our custom loader implementation as a
// private ID for each of the fonts managed by the loader. The only requirement
// on the keys is that they are unique in the context of the loader. The set of
// fonts managed by the loader are in the g_packedFonts array; we'll use an
// index into the array as a key.
ComPtr<IDWriteFontFile> fontFileReference;
DX::ThrowIfFailed(
m_dwriteFactory5->CreateCustomFontFileReference(
&fontIndex, // fontFileReferenceKey
sizeof(fontIndex), // fontFileReferenceKeySize
m_packedFontFileLoader.Get(),
&fontFileReference
)
);
// We're assuming here that the font data is a known and supported container format,
// or is not in a container. And if not in a container, then we assume that it is a
// supported font format. Otherwise, we could use IDWriteFactory5::AnalyzeContainerType
// on the raw data to check the container format; and IDWriteFontFile::Analyze to
// verify the font is a supported format.
// In the case of WOFF2 or non-packed font data, the file could be an OpenType
// collection, or a variable font with multiple named instances. (The WOFF format
// does not support OpenType collections, however.) In this case, the file would
// include multiple font faces. By using IDWriteFontSetBuilder1::AddFontFile, all of
// the font faces are added in a single call.
DX::ThrowIfFailed(
fontSetBuilder->AddFontFile(fontFileReference.Get())
);
} // end for -- loop over fonts
// Now create the custom font set.
DX::ThrowIfFailed(
fontSetBuilder->CreateFontSet(&m_customFontSet)
);
return;
} // CustomFontSetManager::CreateFontSetUsingPackedFontData()
//***********************************************************
//
// Other public methods
//
//***********************************************************
uint32_t CustomFontSetManager::GetFontCount() const
{
if (m_customFontSet == nullptr)
return 0;
return m_customFontSet->GetFontCount();
}
std::vector<std::wstring> CustomFontSetManager::GetFullFontNames() const
{
// Call GetPropertyValuesFromFontSet to get an IDWriteStringList with en-US (or default) full
// names. IDWriteStringList is a dictionary with entries that include a language tag and the
// property value. We only care about the latter.
ComPtr<IDWriteStringList> fullNamePropertyValues = GetPropertyValuesFromFontSet(DWRITE_FONT_PROPERTY_ID_FULL_NAME);
std::vector<std::wstring> fullNames;
for (UINT32 i = 0; i < fullNamePropertyValues->GetCount(); i++)
{
std::wstring propertyValueString;
UINT32 length;
DX::ThrowIfFailed(
fullNamePropertyValues->GetStringLength(i, &length)
);
propertyValueString.resize(length);
DX::ThrowIfFailed(
fullNamePropertyValues->GetString(i, &propertyValueString[0], length + 1)
);
fullNames.push_back(std::move(propertyValueString)); // Use move to avoid a copy.
}
return fullNames;
} // end CustomFontSetManager::GetFullFontNames()
std::vector<std::wstring> CustomFontSetManager::GetFontDataDetails(HANDLE cancellationHandle) const
{
// Report some representative details that require actual font data. If fonts are remote, a
// download will be required. Since latency or success are uncertain, we'll give some ways to
// interrupt the operation. The cancellationHandle parameter is for a caller-determined object
// that we can wait on. We'll also set a 15-second timeout. In either case, we'll return an
// empty vector.
std::vector<std::wstring> resultVector;
// We'll enqueue a download request for data from each font in the font set. If the font is
// already local, this will be a no-op.
//
// Note that, depending on actual app scenarios, direct enqueueing may not be a typical usage
// pattern. For instance, in apps that display text using IDWriteTextLayout, the layout will
// automatically enqueue download requests as needed when measuring or drawing actions are
// done, using fallback fonts in the meantime. After drawing or getting metrics, the app can
// then check the font download queue to see if its non-empty, and initiate a download if
// needed.
for (uint32_t fontIndex = 0; fontIndex < m_customFontSet->GetFontCount(); fontIndex++)
{
ComPtr<IDWriteFontFaceReference> fontFaceReference;
DX::ThrowIfFailed(
m_customFontSet->GetFontFaceReference(fontIndex, &fontFaceReference)
);
DX::ThrowIfFailed(
fontFaceReference->EnqueueFontDownloadRequest()
);
} // end for loop
// Check the font download queue to see if we have remote fonts to download.
ComPtr<IDWriteFontDownloadQueue> fontDownloadQueue;
DX::ThrowIfFailed(
m_dwriteFactory3->GetFontDownloadQueue(&fontDownloadQueue)
);
if (!fontDownloadQueue->IsEmpty())
{
// We need a download listener. It will set an event when the download task is
// completed; we need to get the event handle and wait on it after initiating
// the download.
ComPtr<FontDownloadListener> fontDownloadListener = new FontDownloadListener();
HANDLE downloadCompletedHandle = fontDownloadListener->GetDownloadCompletedEventHandle();
// Now begin the download, and then wait on our primary or alternate event.
DX::ThrowIfFailed(
fontDownloadQueue->BeginDownload(fontDownloadListener.Get())
);
const HANDLE handles[] = { downloadCompletedHandle, cancellationHandle };
DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, 15000);
// Check the wait result. If the alternate event fired, the user wants to exit early
// so just return the empty result vector. If download success, we'll build up results
// using the font data.
switch (waitResult)
{
case WAIT_OBJECT_0:
// DownloadCompleted was called on our listener; check for errors.
if (fontDownloadListener->GetDownloadResult() != S_OK)
{
// Download failed for some reason; return empty result vector.
OutputDebugString(L"\nUnexpected error within GetFontDataDetails: downloading of fonts was initiated, but failed.\n\n");
return resultVector;
}
// Download succeeded; break out of switch.
break;
// Remaining cases are all failure/abort, so just return empty result vector.
case WAIT_OBJECT_0 + 1: // User interrupted flow.
std::wcout << L"You've aborted the process of getting font details.\n";
return resultVector;
case WAIT_TIMEOUT:
std::wcout << L"Downloading of fonts was initiated, but has timed out.\n";
return resultVector;
default: // Shouldn't ever go here.
return resultVector;
} // end switch
} // end if (!fontDownloadQueue->IsEmpty())
// Get representative data for each font: list the full name to identify the font (this will come directly
// from the font data, not from any custom font set properties), and give the font's x-height.
for (uint32_t fontIndex = 0; fontIndex < m_customFontSet->GetFontCount(); fontIndex++)
{
std::wstring fontDetailReport;
// Get IDWriteFontFace3
ComPtr<IDWriteFontFaceReference> fontFaceReference;
DX::ThrowIfFailed(
m_customFontSet->GetFontFaceReference(fontIndex, &fontFaceReference)
);
ComPtr<IDWriteFontFace3> fontFace; // IDWriteFontFace3 or later is needed for the GetInformationalStrings() method.
DX::ThrowIfFailed(
fontFaceReference->CreateFontFace(&fontFace)
);
// Font detail report string begins with full name to identify the font.
ComPtr<IDWriteLocalizedStrings> localizedStrings;
BOOL exists;
DX::ThrowIfFailed(
fontFace->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_FULL_NAME, &localizedStrings, &exists)
);
if (exists) // should always be the case
{
uint32_t stringIndex;
DX::ThrowIfFailed(
localizedStrings->FindLocaleName(L"en-US", &stringIndex, &exists)
);
if (!exists)
{
stringIndex = 0;
}
uint32_t stringLength;
DX::ThrowIfFailed(
localizedStrings->GetStringLength(stringIndex, &stringLength)
);
fontDetailReport.resize(stringLength);
DX::ThrowIfFailed(
localizedStrings->GetString(stringIndex, &fontDetailReport[0], static_cast<UINT32>(fontDetailReport.size() + 1))
);
fontDetailReport.append(L": ");
}
else // In case we didn't get the full name, just give the font set index.
{
fontDetailReport.assign(L"Font ");
fontDetailReport.append(std::to_wstring(fontIndex));
fontDetailReport.append(L": ");
}
// Add to the font detail report the font's x-height.
DWRITE_FONT_METRICS1 fontMetrics;
fontFace->GetMetrics(&fontMetrics);
fontDetailReport.append(L"x-height = ");
fontDetailReport.append(std::to_wstring(fontMetrics.xHeight));
// Add font detail report string to the result vector.
resultVector.push_back(fontDetailReport);
} // end for loop
return resultVector;
} // end CustomFontSetManager::GetFontDataDetails()
bool CustomFontSetManager::CustomFontSetHasRemoteFonts() const
{
// Used to determine if there are any fonts in the font set for which data is currently
// remote, thus requiring a download before it can be used. If all the data is already
// local (was always local or has already been downloaded), then this will return FALSE.
for (uint32_t fontIndex = 0; fontIndex < m_customFontSet->GetFontCount(); fontIndex++)
{
ComPtr<IDWriteFontFaceReference> fontFaceReference;
DX::ThrowIfFailed(
m_customFontSet->GetFontFaceReference(fontIndex, &fontFaceReference)
);
if (fontFaceReference->GetLocality() != DWRITE_LOCALITY_LOCAL)
return true;
}
return false;
} // end CustomFontSetHasRemoteFonts
//***********************************************************
//
// Private helper methods
//
//***********************************************************
void CustomFontSetManager::UnregisterFontFileLoader(IDWriteFontFileLoader* fontFileLoader)
{
if (fontFileLoader != nullptr)
{
// Will be call from destructor. Ignore any errors.
m_dwriteFactory3->UnregisterFontFileLoader(fontFileLoader);
}
}
ComPtr<IDWriteStringList> CustomFontSetManager::GetPropertyValuesFromFontSet(DWRITE_FONT_PROPERTY_ID propertyId) const
{
// We can iterate over the font faces within the font set, but IDWriteFontSet has a convenient
// GetPropertyValues method that allows us to get a list of information-string property values
// from all of the fonts in the font set in one call.
//
// A font can have multiple localized variants for a given informational string. If we call
// using the overload that doesn't include a parameter for language preference, the returned
// list will include all localized variants from all fonts. But if we indicate a language
// preference, the list will include only the best language match from a font, with en-US as
// a fallback.
//
// The list includes unique values from across the set, so in general isn't guaranteed to have
// as many values as there are fonts. Certain properties -- full name or Postscript name --
// are likely to be unique to each font, however.
ComPtr<IDWriteStringList> propertyValues;
DX::ThrowIfFailed(
m_customFontSet->GetPropertyValues(propertyId, L"en-US", &propertyValues)
);
return propertyValues;
} // end CustomFontSetManager::GetPropertyValuesFromFontSet()
} // namespace DWriteCustomFontSets