Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Font Spec #4842

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
387 changes: 387 additions & 0 deletions doc/specs/#166 - Font Support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,387 @@
---
author: Ryan Fu @ryfu-msft
created on: 2024-10-1
last updated: 2024-10-1
issue id: 166
---

# Support for installation of fonts

For [#66](https://github.com/microsoft/winget-cli/issues/166)

## Abstract
This spec outlines the design for supporting the installation of fonts.

## Context

### Font File Types

This feature will support font files with the following file types:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From our talks, do we actually need to specify the file types that are supported? It seemed like we could support whatever font formats Windows supports with no additional work per-type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just for general knowledge, I don't plan to do anything different based on file type. Installation support will be based on the results from IdWriteFontFile::Analyze

- .ttf (True Type File) - Most common format
- .ttc (True Type Collection)

Check failure on line 21 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

`ttc` is not a recognized word. (unrecognized-spelling)
- .otf (Open Type Font)

Check failure on line 22 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

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

The following font types to my knowledge are not supported:
- .woff & .woff2 (developed by Google for the modern browser)
- .eot (Embedded OpenType font file) Not used because of security issues.

### Installing Fonts
Typically, a user would download the font file and drag it to the `C:\Windows\Fonts` directory in the explorer view, but there are a couple things happening behind the scenes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the drag and drop flow trigger the same endpoints as double clicking the file in File Explorer? Just trying to understand if all the current methods of installing fonts end up at the same place


Depending on the scope of the user, the font will be stored in two locations:
- `C:\Windows\Fonts` (Machine )
- `%LOCALAPPDATA%\Microsoft\Windows\Fonts` (User Mode)

In addition, a new registry entry is created in the following registry paths:
The name of the registry key typically matches the title of the font file, and the value is set to the path of the font file.

#### Local Machine Registry
---

`Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`

| Name | Type | Value |
| -------- | ------- | ------- |
| Calibri Bold Italic (TrueType) | REG_SZ | calibriz.ttf |

Check failure on line 45 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

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

Check failure on line 45 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

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

> Note that for the LOCAL_MACHINE registry entry, only the file name is specified, not the full path.

#### Current User Registry
---

`Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`

| Name | Type | Value |
| -------- | ------- | ------- |
| Roboto Bold Italic (TrueType) | REG_SZ | C:\Users\ryfu\AppData\Local\Microsoft\Windows\Fonts\Roboto-BoldItalic.ttf |

Check failure on line 56 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

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


### Uninstalling Fonts
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
To remove a font, the inverse operation of the steps above need to be completed.
1. Remove the registry key entry for that specific font file
2. Remove the file from the font directory


## Manifest Changes
- Addition of `font` to `installerType` and `nestedInstallerType`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way for the manifest to specify the font name and not just the path to it? Thinking of a use case where the file is something like ARandomFont.ttf when it the name of the font is actually something like Really Awesome Font

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that the font files contain face and family metadata already and we are planning to always use that. If that is not always the case, then we do probably need a way to specify the name. The registry value names don't appear to have any actual impact on the enumerated fonts (from what I remember of @ryfu-msft investigation), but maybe they are important in the event that the file doesn't contain the relevant metadata.


Manifest Example:
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
```yaml
PackageIdentifier: Microsoft.CascadiaCode
PackageName: Cascadia
PackageVersion: 2404.23
Installers:
- InstallerType: zip
InstallerUrl: https://github.com/microsoft/cascadia-code/releases/download/v2404.23/CascadiaCode-2404.23.zip
InstallerSha256: a911410626c0e09d03fa3fdda827188fda96607df50fecc3c5fee5906e33251b
NestedInstallerFiles:
- RelativeFilePath: ttf/CascadiaCodeItalic.ttf
- RelativeFilePath: ttf/CascadiaMonoNFItalic.ttf
ManifestType: installer
ManifestVersion: 1.9.0
```

## Font Index
A new `PredefinedSource` will be created for `Fonts`. `PredefinedInstalledFontFactory` will be defined that creates a SQLite index from the installed font information. The table below shows how a font is analogous to a package.

| Property | Package | Font |
| -------- | ------- | ---- |
| Id | MSIX\NotepadPlusPlus_1.0.0.0_neutral__7njy0v32s6xk6 | Machine\Arial\Bold |
| Version | 1.0.0.0 | 7.01 |
| Name | NotepadPlusPlus | Arial Bold |
| PackageFamilyName | NotepadPlusPlus_1.0.0.0_neutral__7njy0v32s6xk6 | Arial |
| InstalledLocation | N/A | %LOCALAPPDATA%/Microsoft/Windows/Fonts/Arial.ttf |
| InstalledScope | | User |

> The unique identifer for each font will be a combination of the scope, font font family name, and font face name.

Check failure on line 96 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

` font font ` matches a line_forbidden.patterns entry: `\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s`. (forbidden-pattern)
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved

> The version of the font only exists at the font face level and not at the family level

## CLI Behavior / Implementation Details

New Command: `winget fonts`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there be any new GPO to prevent the installation of fonts, or uninstallation of specific fonts?

Thinking about how an enterprise could use a custom standard font for all of their branding / marketing and would want to ensure that users at the company can't uninstall it using WinGet

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, how will this interact with existing Font GPOs, if at all?

---
Fonts should be completely separate from the existing package management experience. Fonts aren't as complex as installing packages so having new subcommands can help simplify the necessary arguments for proper functionality. To support this, a new command called `font` will be added. The following subcommands will be available:
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
- `winget fonts list`
- `winget fonts install`
- `winget fonts uninstall`
- `winget fonts upgrade`
- `winget fonts show`

`winget fonts list`
---
The default behavior of `winget fonts list` is to display all installed font families and the number of faces that belong to each font family.

| Family Name | Face Count |
| -------- | ------- |
| Arial | 6 |
| Bahnschrift | 1 |

Check failure on line 118 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

`Bahnschrift` is not a recognized word. (unrecognized-spelling)
| Cascadia Code | 30 |

```c++
// How to enumerate the list of installed font family names.
wchar_t localeNameBuffer[LOCALE_NAME_MAX_LENGTH];
const auto localeName = GetUserDefaultLocaleName(localeNameBuffer, LOCALE_NAME_MAX_LENGTH) ? localeNameBuffer : L"en-US";

wil::com_ptr<IDWriteFactory7> factory;
THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), factory.put_unknown()));

Check failure on line 127 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

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

Check failure on line 127 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

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

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

const auto familyCount = collection->GetFontFamilyCount();

for (UINT32 familyIndex = 0; familyIndex < familyCount; familyIndex++)
{
// Get font family from font collection by index.
wil::com_ptr<IDWriteFontFamily> family;
THROW_IF_FAILED(collection->GetFontFamily(familyIndex, family.addressof()));

// Get font family name.
wil::com_ptr<IDWriteLocalizedStrings> names;
THROW_IF_FAILED(family->GetFamilyNames(names.addressof()));

UINT32 index;
BOOL exists;
if (FAILED(names->FindLocaleName(localeName, &index, &exists)) || !exists)
{
index = 0;
}

UINT32 nameLength;
THROW_IF_FAILED(names->GetStringLength(index, &nameLength));
nameLength += 1; // Account for the trailing null terminator during allocation.

wchar_t name[512];
const auto fontCount = family->GetFontCount();
THROW_HR_IF(E_OUTOFMEMORY, nameLength > ARRAYSIZE(name));
THROW_IF_FAILED(names->GetString(0, &name[0], nameLength));
}
```

If a user specifies a specific name such as :

`winget fonts list --name 'Arial'`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this support a substring match like winget list? Can I do something like winget fonts list --name 'Nerd Font' (display all nerd fonts installed on my system) or winget fonts list 'Italic' (list out all italic fonts)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or also --version, --exact, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking of creating an index in memory that we can use to better search the installed fonts like the examples you have above.


Then the tool will display all of the font faces related to that font family name along with the corresponding font file path.

| Face Name | Face Version | Font Family | Font File Path |
| -------- | ------- | ------- | ------- |
| Regular | 7.01 | Arial | C:\Windows\Fonts\arial.ttf |
| Narrow | 7.01 | Arial | C:\Windows\Fonts\ARIALN.TTF |

Check failure on line 171 in doc/specs/#166 - Font Support.md

View workflow job for this annotation

GitHub Actions / Check Spelling

`ARIALN` is not a recognized word. (unrecognized-spelling)
| Italic | 7.01 | Arial | C:\Windows\Fonts\ariali.ttf |
| Narrow Italic | 7.01 | Arial | C:\Windows\Fonts\ARIALNI.TTF |
| Narrow Bold | 7.01 | Arial | C:\Windows\Fonts\ARIALNB.TTF |
| Narrow Bold Oblique | 7.01 | Arial | C:\Windows\Fonts\ARIALNB.TTF |

> Note that some font faces share the same font file path. This will need to be handled properly during uninstall.

Sample Code:

```c++
// Obtaining all font file paths associated with a font face.
wil::com_ptr<IDWriteFontFace> fontFace;
THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof()));

// https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-getfiles
UINT32 fileCount;
THROW_IF_FAILED(fontFace->GetFiles(&fileCount, nullptr)); // Use null initially to retrieve the number of files.

wil::com_ptr<IDWriteFontFile> fontFiles[8];
THROW_HR_IF(E_OUTOFMEMORY, fileCount > ARRAYSIZE(fontFiles));
THROW_IF_FAILED(fontFace->GetFiles(&fileCount, fontFiles[0].addressof()));

// Iterate through all the files corresponding to that font face name.
for (UINT32 i = 0; i < fileCount; ++i) {
wil::com_ptr<IDWriteFontFileLoader> loader;
THROW_IF_FAILED(fontFiles[i]->GetLoader(loader.addressof()));

const void* fontFileReferenceKey;
UINT32 fontFileReferenceKeySize;
THROW_IF_FAILED(fontFiles[i]->GetReferenceKey(&fontFileReferenceKey, &fontFileReferenceKeySize));

// Retrieve and print the full font file path.
if (const auto localLoader = loader.try_query<IDWriteLocalFontFileLoader>()) {
UINT32 pathLength;
THROW_IF_FAILED(localLoader->GetFilePathLengthFromKey(fontFileReferenceKey, fontFileReferenceKeySize, &pathLength));
pathLength += 1; // Account for the trailing null terminator during allocation.

wchar_t path[512];
THROW_HR_IF(E_OUTOFMEMORY, pathLength > ARRAYSIZE(path));
THROW_IF_FAILED(localLoader->GetFilePathFromKey(fontFileReferenceKey, fontFileReferenceKeySize, &path[0], pathLength));

// Normalize path for better readability
// Use backup semantics so we can access protected files.
const auto h = CreateFileW(path, 0, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
GetFinalPathNameByHandleW(h, path, ARRAYSIZE(path), 0);
CloseHandle(h);

wprintf(L"%s\n", &path[0]);
}
}
```

`winget fonts install`
---

### 1. Initial Validation
Winget will check that `effectiveInstallerType == font` before kicking off the font installation flow.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this check is necessary. My expectation is that we:

  1. Have a separate source for fonts, marked as explicit
  2. winget fonts install is the same as winget install -s winget-fonts (or whatever the name of the source is).
  3. The actual workflow is identical post source selection; the font installer type is not special.

The goal is logically separate fonts for our repository.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated spec to align with this. Made sure to call out that the workflow should not change post source selection.


The flow will start by validating the font file using the [IDWriteFontFile::Analyze](https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontfile-analyze) method. The `isValid` boolean value will be checked first to determine if we can support installing this font file. If it is not valid, the workflow will terminate and an error message will be displayed to the user.

```C++
BOOL isValid;
DWRITE_FONT_FILE_TYPE fileType;
DWRITE_FONT_FACE_TYPE faceType;
UINT32 numOfFaces;
THROW_IF_FAILED(localFontFile->Analyze(&isValid, &fileType, &faceType, &numOfFaces));
```

### 2. Determining expected states
Based on the `--scope` argument, this will be used to determine where the font file will be copied to, and where we will create a new registry key.
We will check whether those files/entries already exist and return an error to the user. The user can bypass these checks by including the `--force` argument, which will overwrite these files/entries.

| | User Scope | Machine Scope |
| --- | --- | --- |
| Font Name | Roboto | Roboto |
| Font Path | `%LOCALAPPDATA%/Microsoft/Windows/Fonts/Roboto.ttf` | `C:\Windows\Fonts\Roboto.ttf` |
| Registry Path | `HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts` | `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts` |
| Registry Entry Name | `Roboto (TrueType)` | `Roboto (TrueType)` |
| Registry Entry Value | `%LOCALAPPDATA%/Microsoft/Windows/Fonts/Roboto.ttf` | `Roboto.ttf` |


> Nearly all of the font types are .ttf and append the `(True Type)` font type to the end of the font name. We will follow the same pattern if the font type is True Type.

> Machine scope font folder has a known folder id for the fonts directory: `CSIDL_FONTS`, which typically points to `C:\Windows\Fonts`.

**Registry Entry Name**

The font's registry name will be acquired from the file's title (right click-> properties -> details -> title). There are a few font titles that have multiple font names separated by a semicolon. The semicolon will be replaced with an `&` character. `TrueType` will be appended if the file is a true type font.

| Font File | Font Title | Font Registry Name|
| --- | --- | --- |
| timesbd.ttf | Times New Roman Bold | Times New Roman Bold (TrueType) |
| simsun.ttc | SimSun; NSimSun | SimSun & NSimSun (TrueType) |
| YuGothL.ttc | Yu Gothic Light; Yu Gothic UI Light | Yu Gothic Light & Yu Gothic UI Light (TrueType) |

```c++
// Retrieving the title of the font file.
#include <ShObjIdl_core.h>
#include <propkey.h>
CoInitialize(nullptr);
IPropertyStore* pps = nullptr;
HRESULT hr = SHGetPropertyStoreFromParsingName(fontFilePath.c_str(), nullptr, GPS_DEFAULT, IID_PPV_ARGS(&pps));

if (SUCCEEDED(hr)) {
PROPVARIANT prop;
PropVariantInit(&prop);
hr = pps->GetValue(PKEY_Title, &prop);
if (SUCCEEDED(hr)) {
std::wstring title = prop.pwszVal;
PropVariantClear(&prop);
pps->Release();
wprintf(L"%s\n", &title[0]);
}

PropVariantClear(&prop);
pps->Release();
}
```

### 3. Applying desired state
Once all checks have been verified, we will apply the desired state so that the font file will exist in the appropriate location and a new registry entry will be created along with updating the font index.

`winget fonts uninstall`
---

1. Determine which font file(s) matches the font family/face name

Utilizing the enumeration code from [`winget fonts list`](#winget-fonts-list), we will determine which file(s) corresponds to the specified font family. During enumeration, it is possible to have duplicate entries for the same font family name if the font is installed in both user and system scope. The user will need to provide the `--scope` argument to filter down to exactly one font family.

`std::vector<std::filesystem::path> GetFontFilePathsByName(familyName, faceName (optional))`

2. Check the registry values for matching font path

We will iterate through all font registry keys to create a map of all registered fonts and font files and determine if there is key value that matches each font path.

There is no guarantee that the key name must match the intended name of the font. The registry key name has no impact on how the font is registered by the OS. Because of this, the only guaranteed solution is to scan through all font registry entries and look for a single match.

3. Remove the file and the registry entry.

Once we have determined the matching font file and registry key entry, both of these items will be removed from the system and the font index will be updated.

### Uninstall Scenarios:
---

1. **Uninstalling a font family with a single font face:**

`winget fonts uninstall --name 'Baskerville Old Face'`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to make things work throughout, we should treat font family as the "package" and faces as "versions". Alternatively, we need a completely new set of COM APIs to expose fonts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that:

font face == package
font face version == version
font family == package family name

How should we reason about the versioning of these faces?

I haven't thought much about COM APIs but that is a good callout. Will address that later once the client implementation is a little more concrete.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your model is how I would put it into the existing index schema (if package == identifier). But with the current code (at least my belief in how it should work) and a font available remotely, the search for the family would return one package result with multiple installed versions. I would argue that this is a better model generally, and unlike executable packages we have a well-defined mechanism to do the correlation even without external data to drive it. Thus, I would put some additional wrapper in place to always join the face "packages" together into one result based on the family name. I feel like the existing composite code can already handle this.


Winget will uninstall a font family with a single font face without any warnings.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A warning should only be generated when the set of things being removed would be larger than the set of things requested to be removed. Specifying a single face might require removing multiple faces or families because they are all in the same file. This applies to all uninstall command permutations.

So generally:

  1. determine the set of files that back the target
  2. determine the set of faces that this will remove
  3. if there are faces outside of the target, warn and require --force

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated spec to align with above.


2. **Uninstalling a font family with multiple font faces:**

`winget fonts uninstall --name 'Bahnschrift' --remove-multiple-fonts`

If a font family contains multiple font faces, the user must include the `--remove-multiple-fonts`. Otherwise, a blocking warning will be shown to the user.
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved

3. **Uninstalling a specific font face:**

`winget fonts uninstall --name 'Bahnschrift' --font-face 'Semibold'`

4. **Uninstalling a font face that shares the same font file as another font face:**

`winget fonts uninstall --name 'Arial' --font-face 'Bold'`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this example and example 1 exist together. It doesn't seem like name has a consistent meaning across these commands.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that using a font family that includes the word 'face' was slightly confusing...

I've updated the arguments to more accurately reflect the difference between --family-name and --face-name


If a specific font face that is being removed shares a font file with another font face, an error will be shown to the user and the workflow will be terminated.

The user must include the `--force` argument to remove both files. This means that the registry entry from the other font face must be removed as well. The client should notify the user, which font faces are removed.

`winget fonts upgrade`
---
Identifying the version only applies at the font face level.

Retrieving the version of the font face:
```c++
wil::com_ptr<IDWriteLocalizedStrings> versionString;
BOOL versionStringExists;
THROW_IF_FAILED(font->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_VERSION_STRINGS, &versionString, &versionStringExists));
UINT32 versionStringIndex;
if (FAILED(versionString->FindLocaleName(localeName, &versionStringIndex, &versionStringExists)) || !versionStringExists) {
versionStringIndex = 0;
}

UINT32 versionStringLength = 0;
THROW_IF_FAILED(versionString->GetStringLength(versionStringIndex, &versionStringLength));
versionStringLength += 1; // Account for the trailing null terminator during allocation.

wchar_t result[512];
THROW_HR_IF(E_OUTOFMEMORY, versionStringLength > ARRAYSIZE(result));
THROW_IF_FAILED(versionString->GetString(versionStringIndex, &result[0], versionStringLength));

wprintf(L"%s\n", &result[0]);
```

We will compare this font version with what is the latest available version from the repository. If `all current font face versions < latest available font version`, we will remove the previous font and install the latest.

## Validation
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
- Fonts should be submitted in their own directory in the winget-pkgs repository in order to maintain separation between fonts and manifests. Package manifests will continue to live in the `manifests` directory, while font manifests will be added to a new `fonts` directory with a similar directory structure (sorted by starting letter)
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved

```
winget-pkgs
│ README.md
│ file001.txt
└───manifests
│ └───a
│ └───b
└───fonts
│ └───a
| | American Kestrel
| | Autumn Voyage
│ └───b
```

- PRSS validation is required to verify that the downloaded fonts file does not contain potential malware.
- DAAS validation is not completely necessary. If we decide to go this route, DAAS will need to be updated to avoid checking for a valid executable and just verify that the font file/entries were correctly placed.
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
Loading