Skip to content

Commit

Permalink
initial implementaiton for font installer
Browse files Browse the repository at this point in the history
  • Loading branch information
ryfu-msft committed Dec 3, 2024
1 parent aae3caa commit 42e53bf
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 22 deletions.
35 changes: 30 additions & 5 deletions src/AppInstallerCLICore/FontInstaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,40 @@

namespace AppInstaller::CLI::Font
{
FontInstaller::FontInstaller(const std::string& familyName, Manifest::ScopeEnum scope)
namespace
{
m_scope = scope;
m_familyName = familyName;
constexpr std::wstring_view s_FontsPathSubkey = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
}


// Get the expected state from the family name and scope.
FontInstaller::FontInstaller(Manifest::ScopeEnum scope) : m_scope(scope)
{
if (scope == Manifest::ScopeEnum::Machine)
{
m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsMachineInstallLocation);
m_key = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, std::wstring{ s_FontsPathSubkey });
}
else
{
m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsUserInstallLocation);
m_key = Registry::Key::OpenIfExists(HKEY_CURRENT_USER, std::wstring{ s_FontsPathSubkey });
}
}

void FontInstaller::Install()
void FontInstaller::Install(const std::map<std::wstring, std::filesystem::path> fontFiles)
{
// TODO: Get all font files and check if font file already exists.
for (const auto& [fontName, fontFilePath] : fontFiles)
{
std::filesystem::path fileName = fontFilePath.filename();
std::filesystem::path fontFileInstallationPath = m_installLocation / fileName;

AICLI_LOG(CLI, Info, << "Moving font file to: " << fontFileInstallationPath.u8string());
AppInstaller::Filesystem::RenameFile(fontFilePath, fontFileInstallationPath);

// TODO: Need to fix access permission for writing to the registry.
AICLI_LOG(CLI, Info, << "Creating font subkey for: " << AppInstaller::Utility::ConvertToUTF8(fontName));
m_key.SetValue(fontName, fontFileInstallationPath, REG_SZ);
}
}
}
10 changes: 5 additions & 5 deletions src/AppInstallerCLICore/FontInstaller.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@

namespace AppInstaller::CLI::Font
{
// Object representation of the metadata and functionality required for installing a font package.
struct FontInstaller
{
std::filesystem::path FontFileLocation;
FontInstaller(Manifest::ScopeEnum scope);

FontInstaller(const std::string& familyName, Manifest::ScopeEnum scope);
std::filesystem::path FontFileLocation;

void Install();
void Install(const std::map<std::wstring, std::filesystem::path> fontFiles);

private:
Manifest::ScopeEnum m_scope;
std::string m_familyName;
std::filesystem::path m_installLocation;
Registry::Key m_key;
};
}
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Workflows/DownloadFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ namespace AppInstaller::CLI::Workflow
return L".msix"sv;
case InstallerTypeEnum::Zip:
return L".zip"sv;
case InstallerTypeEnum::Font:
return L".ttf"sv;
default:
THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
}
Expand Down
36 changes: 36 additions & 0 deletions src/AppInstallerCLICore/Workflows/FontFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include "TableOutput.h"
#include <winget/Fonts.h>
#include <AppInstallerRuntime.h>
#include <FontInstaller.h>

namespace AppInstaller::CLI::Workflow
{
using namespace AppInstaller::CLI::Execution;
using namespace AppInstaller::CLI::Font;

namespace
{
Expand Down Expand Up @@ -124,5 +126,39 @@ namespace AppInstaller::CLI::Workflow

void FontInstallImpl(Execution::Context& context)
{
Manifest::ScopeEnum scope = AppInstaller::Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope));
FontInstaller fontInstaller = FontInstaller(scope);
std::filesystem::path& installerPath = context.Get<Execution::Data::InstallerPath>();
std::map<std::wstring, std::filesystem::path> fontFileMap{};

try
{
context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl;

// InstallerPath will point to a directory if it is extracted from an archive.
if (std::filesystem::is_directory(installerPath))
{
const std::vector<Manifest::NestedInstallerFile>& nestedInstallerFiles = context.Get<Execution::Data::Installer>()->NestedInstallerFiles;

for (const auto& nestedInstallerFile : nestedInstallerFiles)
{
const std::filesystem::path& fontFilePath = installerPath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath);
auto fontFileEntryName = AppInstaller::Fonts::GetFontFileTitle(fontFilePath);
fontFileMap.emplace(fontFileEntryName, fontFilePath);
}
}
else
{
auto fontName = AppInstaller::Fonts::GetFontFileTitle(installerPath);
fontFileMap.emplace(fontName, installerPath);
}

fontInstaller.Install(fontFileMap);
context.Add<Execution::Data::OperationReturnCode>(ERROR_SUCCESS);
}
catch (...)
{
context.Add<Execution::Data::OperationReturnCode>(Workflow::HandleException(context, std::current_exception()));
}
}
}
6 changes: 0 additions & 6 deletions src/AppInstallerCLICore/Workflows/FontFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,4 @@ namespace AppInstaller::CLI::Workflow
// Inputs: Manifest, Scope, Rename, Location
// Outputs: None
void FontInstallImpl(Execution::Context& context);

// Initializes the font installer.
// Required Args: None
// Inputs: Scope, Manifest, Installer
// Outputs: None
void InitializeFontInstaller(Execution::Context& context);
}
3 changes: 2 additions & 1 deletion src/AppInstallerCLICore/Workflows/InstallFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "pch.h"
#include "InstallFlow.h"
#include "DownloadFlow.h"
#include "FontFlow.h"
#include "UninstallFlow.h"
#include "UpdateFlow.h"
#include "ResumeFlow.h"
Expand Down Expand Up @@ -284,7 +285,6 @@ namespace AppInstaller::CLI::Workflow
void FontInstall(Execution::Context& context)
{
context <<
InitializeFontInstaller <<
FontInstallImpl <<
ReportInstallerResult("Font"sv, APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED, true);
}
Expand Down Expand Up @@ -457,6 +457,7 @@ namespace AppInstaller::CLI::Workflow
break;
case InstallerTypeEnum::Font:
context << details::FontInstall;
break;
default:
THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
}
Expand Down
44 changes: 40 additions & 4 deletions src/AppInstallerCommonCore/Fonts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <AppInstallerStrings.h>
#include <winget/Fonts.h>
#include <winget/Locale.h>
#include <ShObjIdl_core.h>
#include <propkey.h>

Check failure on line 8 in src/AppInstallerCommonCore/Fonts.cpp

View workflow job for this annotation

GitHub Actions / Check Spelling

`propkey` is not a recognized word. (unrecognized-spelling)

namespace AppInstaller::Fonts
{
Expand Down Expand Up @@ -49,15 +51,13 @@ namespace AppInstaller::Fonts
FontCatalog::FontCatalog()
{
m_preferredLocales = AppInstaller::Locale::GetUserPreferredLanguagesUTF16();
THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(m_factory), m_factory.put_unknown()));
}

std::vector<FontFamily> FontCatalog::GetInstalledFontFamilies(std::optional<std::wstring> familyName)
{
wil::com_ptr<IDWriteFactory7> factory;
THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), factory.put_unknown()));

wil::com_ptr<IDWriteFontCollection> collection;
THROW_IF_FAILED(factory->GetSystemFontCollection(collection.addressof(), FALSE));
THROW_IF_FAILED(m_factory->GetSystemFontCollection(collection.addressof(), FALSE));

std::vector<FontFamily> installedFontFamilies;

Expand Down Expand Up @@ -85,6 +85,20 @@ namespace AppInstaller::Fonts
return installedFontFamilies;
}

bool FontCatalog::IsValidFontFile(const std::filesystem::path& filePath)
{
wil::com_ptr<IDWriteFontFile> fontFile;
THROW_IF_FAILED(m_factory->CreateFontFileReference(filePath.c_str(), NULL, &fontFile));

BOOL isValid;
DWRITE_FONT_FILE_TYPE fileType;
DWRITE_FONT_FACE_TYPE faceType;
UINT32 numOfFaces;
THROW_IF_FAILED(fontFile->Analyze(&isValid, &fileType, &faceType, &numOfFaces));

return isValid;
}

std::wstring FontCatalog::GetLocalizedStringFromFont(const wil::com_ptr<IDWriteLocalizedStrings>& localizedStringCollection)
{
UINT32 index = 0;
Expand Down Expand Up @@ -172,4 +186,26 @@ namespace AppInstaller::Fonts
fontFamily.Faces = std::move(fontFaces);
return fontFamily;
}

std::wstring GetFontFileTitle(const std::filesystem::path& fontFilePath)
{
auto co_unitialize = wil::CoInitializeEx();

Check failure on line 192 in src/AppInstallerCommonCore/Fonts.cpp

View workflow job for this annotation

GitHub Actions / Check Spelling

`unitialize` is not a recognized word. (unrecognized-spelling)
IPropertyStore* pps = nullptr;
std::wstring title;
HRESULT hr = SHGetPropertyStoreFromParsingName(fontFilePath.c_str(), nullptr, GPS_DEFAULT, IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr)) {
PROPVARIANT prop;

Check failure on line 197 in src/AppInstallerCommonCore/Fonts.cpp

View workflow job for this annotation

GitHub Actions / Check Spelling

`PROPVARIANT` is not a recognized word. (unrecognized-spelling)
PropVariantInit(&prop);
hr = pps->GetValue(PKEY_Title, &prop);

Check failure on line 199 in src/AppInstallerCommonCore/Fonts.cpp

View workflow job for this annotation

GitHub Actions / Check Spelling

`PKEY` is not a recognized word. (unrecognized-spelling)
if (SUCCEEDED(hr)) {
title = prop.pwszVal;

Check failure on line 201 in src/AppInstallerCommonCore/Fonts.cpp

View workflow job for this annotation

GitHub Actions / Check Spelling

`pwsz` is not a recognized word. (unrecognized-spelling)
PropVariantClear(&prop);
pps->Release();
}
PropVariantClear(&prop);
pps->Release();
}

return title;
}
}
9 changes: 8 additions & 1 deletion src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ namespace AppInstaller::Manifest
{
result = InstallerTypeEnum::Portable;
}
else if (inStrLower == "font")
{
result = InstallerTypeEnum::Font;
}

return result;
}
Expand Down Expand Up @@ -583,6 +587,8 @@ namespace AppInstaller::Manifest
return "msstore"sv;
case InstallerTypeEnum::Portable:
return "portable"sv;
case InstallerTypeEnum::Font:
return "font"sv;
}

return "unknown"sv;
Expand Down Expand Up @@ -962,7 +968,8 @@ namespace AppInstaller::Manifest
nestedInstallerType == InstallerTypeEnum::Wix ||
nestedInstallerType == InstallerTypeEnum::Burn ||
nestedInstallerType == InstallerTypeEnum::Portable ||
nestedInstallerType == InstallerTypeEnum::Msix
nestedInstallerType == InstallerTypeEnum::Msix ||
nestedInstallerType == InstallerTypeEnum::Font
);
}

Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCommonCore/Public/winget/Fonts.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,26 @@ namespace AppInstaller::Fonts
std::vector<FontFace> Faces;
};

std::wstring GetFontFileTitle(const std::filesystem::path& fontFilePath);

struct FontCatalog
{
FontCatalog();

// Gets all installed font families on the system. If an exact family name is provided and found, returns the installed font family.
std::vector<FontFamily> GetInstalledFontFamilies(std::optional<std::wstring> familyName = {});

// Returns a boolean value indicating whether the specified file path is a valid font file.
bool IsValidFontFile(const std::filesystem::path& filePath);

private:
FontFamily GetFontFamilyByIndex(const wil::com_ptr<IDWriteFontCollection>& collection, UINT32 index);
std::wstring GetLocalizedStringFromFont(const wil::com_ptr<IDWriteLocalizedStrings>& localizedStringCollection);
std::wstring GetFontFamilyName(const wil::com_ptr<IDWriteFontFamily>& fontFamily);
std::wstring GetFontFaceName(const wil::com_ptr<IDWriteFont>& font);
Utility::OpenTypeFontVersion GetFontFaceVersion(const wil::com_ptr<IDWriteFont>& font);

wil::com_ptr<IDWriteFactory7> m_factory;
std::vector<std::wstring> m_preferredLocales;
};
}

0 comments on commit 42e53bf

Please sign in to comment.