2025-11-28 00:35:46 +09:00

874 lines
39 KiB
C++

//*********************************************************
//
// 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