-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A lengthy deep dive just to realize that FONTDIR doesn't really do anything at all. At least now I'm confident in the FONT/FONTDIR behavior of resinator, though. I believe the resinator behavior is now more 'correct' than the 32-bit rc.exe has ever been.
- Loading branch information
Showing
10 changed files
with
635 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
--- | ||
layout: default | ||
title: FONT | ||
parent: Resources | ||
--- | ||
|
||
# `FONT` resource | ||
|
||
- The `<id>` in the `<id> FONT` definition **must** be an ordinal, not a string. | ||
- The `<id>` of each `FONT` must be unique, but this does not fail the compilation, only emits an error. The first `FONT` defined with the `<id>` takes precedence, all others with the same `<id>` are ignored. | ||
- Each `FONT` is stored with type `RT_FONT`. The entire binary contents of the specified file are the data. No validation/parsing is done of the data. | ||
|
||
At the end of the .res, a single `RT_FONTDIR` resource with the name `FONTDIR` is written. The `FONTDIR` resource doesn't seem to matter at all; it can even be entirely omitted seemingly without any practical consequences. | ||
|
||
What follows is a more complete explanation of the `FONTDIR` resource: | ||
|
||
## What is `FONT` used for anyway? | ||
|
||
The `FONT` resource is basically obsolete--the only purpose of it is to bundle `.fnt` bitmap fonts into `.fon` files (which are resource-only DLLs renamed to have a `.fon` extension; they are recognized as fonts and can be installed like any other font). Although modern Windows versions still *use* `.fon` files (e.g. quite a few `.fon` files are distributed with the default Windows 10 installation in `C:\Windows\Fonts`), it seems that `rc.exe` is no longer really equipped to *generate* `.fon` files in the same way as the 16-bit version did. | ||
|
||
From [TN318C.txt: How to create Windows .FON font libraries for use](https://community.embarcadero.com/article/technical-articles/149-tools/14454-how-to-create-windows-fon-font-libraries-for-use): | ||
|
||
> To complete the .FON file, you must append the font resources onto the font library. This is done with: RC xxxxxxxx.rc xxxxxxxx.fon | ||
This usage of `rc.exe` is only possible in the 16-bit version of `rc.exe`, where it would do the linking of the resources itself (and the `.res` file was an optional intermediate output). The modern equivalent would be [Creating a resource-only DLL](https://learn.microsoft.com/en-us/cpp/build/creating-a-resource-only-dll?view=msvc-170), where `rc.exe` is used to generate a `.res` which then gets linked with a DLL that has no entry point. | ||
|
||
## So what should go in the `FONTDIR`? | ||
|
||
Practically, it doesn't seem to really matter what's in the `FONTDIR`--it's likely not even read/parsed since it's possible to just iterate over the `FONT` resources directly instead. A `.res` without a `FONTDIR` is seemingly treated the same at link-time, and a resource-only DLL without a `FONTDIR` is seen the same by the Windows font system as one with a `FONTDIR`. | ||
|
||
If we *do* want to write a `FONTDIR` resource, though, the format that the 32-bit `rc.exe` writes seems to be incorrect on a fundamental level. | ||
|
||
First, let's look at some of the relevant documentation: | ||
- [`FONTGROUPHDR`](https://learn.microsoft.com/en-us/windows/win32/menurc/fontgrouphdr) | ||
- [`DIRENTRY`](https://learn.microsoft.com/en-us/windows/win32/menurc/direntry) | ||
- [`FONTDIRENTRY`](https://learn.microsoft.com/en-us/windows/win32/menurc/fontdirentry) | ||
|
||
So, the `FONTDIR`'s data should be a `FONTGROUPHDR` with the total number of fonts, followed by a `DIRENTRY` and a `FONTDIRENTRY` for each font. The `FONTDIRENTRY` is not an actual struct--all fields are contiguous with no padding--so each `FONTDIRENTRY` is at least `115` bytes: `113` for the statically sized fields, and then `szDeviceName` and `szFaceName` are both variable-length `NUL`-terimated strings (so at the very least they will have a `NUL` byte). | ||
|
||
If we run the 16-bit version of `rc.exe`, this is exactly what you see for each font. However, for the 32-bit version of `rc.exe`, it will output at least `150` bytes for each `FONTDIRENTRY`. This difference has an explanation: the Windows 3.0 `.FNT` specification has the header size as `148`, so `rc.exe` is writing the full v3.0 header and then the `szDeviceName` and `szFaceName` as `NUL`-terminated strings. However, this seems incorrect for two reasons: | ||
|
||
- There does not seem to be any updated `FONTDIRENTRY` docs that accommodate this new size, so anyone trying to enumerate the `FONTDIRENTRY`s in a `FONTDIR` will end up reading garbage after the first entry (since the offset to the next `FONTDIRENTRY` won't match the `FONTDIRENTRY` size as specified in the documentation) | ||
- The `szDeviceName` and `szFaceName` are wrong. The Win32 compiler seems to be doing subtraction from end of the `.FNT` header to get the `dfDevice` and `dfFace` fields (which themselves contain offsets from the beginning of the file to where the associated `NUL`-terminated string is located). Since the 32-bit version is using a header size of 148, this means it is grabbing the fields from within a reserved section of the header instead of where the fields actually are (that is, instead of doing `113 - 12 = 101` for `dfDevice` it's doing `148 - 12 = 136` and reading from there instead). Subsequently, the 32-bit version of `rc.exe` basically never writes the `szDeviceName` or `szFaceName` to the `FONTDIRENTRY` since it's looking in the wrong place for the `dfDevice` and `dfFace` fields. This can be confirmed by modifying byte offset `136` and `140` to contain an offset of a `NUL-terminated` string in the file and seeing that `rc.exe` will use them as `szDeviceName`/`szFaceName`. | ||
|
||
{: .note } | ||
> The 148 byte size corresponds to the format specified [here](https://web.archive.org/web/20080115184921/http://support.microsoft.com/kb/65123) up to the end of `dfReserved1`. Those last 16 reserved bytes are the section in which the 32-bit `rc.exe` is erroneously interpreting as the `dfDevice`/`dfFace` fields. | ||
## So really, what should go in the `FONTDIR`? | ||
|
||
There are a few possibilities: | ||
|
||
The first possibility is that the current `rc.exe` behavior should be emulated, even if it seems wrong. If that's the case, then the format would look like this: | ||
|
||
| Size/Type | Description | | ||
|-----------|--------| | ||
| `u16` | Number of total font resources | | ||
| - | *Below is repeated for each `RT_FONT` in the `.res`* | | ||
| `u16` | ID of the `RT_FONT` | | ||
| 148 bytes | The first 148 bytes of the `FONT`'s file | | ||
| At least 1 byte | `NUL`-terminated string containing the 'device name'. To match the 32-bit `rc.exe`, the device name is the string located at the offset dictated by the `u32` at offset `140` of the file | | ||
| At least 1 byte | `NUL`-terminated string containing the 'face name'. To match the 32-bit `rc.exe`, the face name is the string located at the offset dictated by the `u32` at the offset `144` of the file | | ||
|
||
The second possibility is that the current `rc.exe` is miscompiling the `FONTDIR` and the 16-bit `rc.exe` behavior is correct. If that's the case, then the format would look like this: | ||
|
||
| Size/Type | Description | | ||
|-----------|--------| | ||
| `u16` | Number of total font resources | | ||
| - | *Below is repeated for each `RT_FONT` in the `.res`* | | ||
| `u16` | ID of the `RT_FONT` | | ||
| 113 bytes | The first 113 bytes of the `FONT`'s file (aka all of the fields of [`FONTDIRENTRY`](https://learn.microsoft.com/en-us/windows/win32/menurc/fontdirentry) without `szDeviceName` and `szFaceName` and no padding between any of the fields) | | ||
| At least 1 byte | `NUL`-terminated string containing the 'device name'. The device name is the string located at the offset dictated by the [`dfDevice` field of `FONTDIRENTRY`](https://learn.microsoft.com/en-us/windows/win32/menurc/fontdirentry) (101 bytes into the file) | | ||
| At least 1 byte | `NUL`-terminated string containing the 'face name'. The face name is the string located at the offset dictated by the [`dfFace` field of `FONTDIRENTRY`](https://learn.microsoft.com/en-us/windows/win32/menurc/fontdirentry) (105 bytes into the file) | | ||
|
||
There is a third possibility that the Win32 behavior of writing the full 148-byte long `.FNT` header is intended, but the 'device name'/'face name' bug should be fixed. This would be a combination of the two formats above. | ||
|
||
{: .note } | ||
> If the font file is 140 bytes or fewer, the 32-bit `rc.exe` seems to default to a `dfFace` of `0` (as the [incorrect] location of the `dfFace` field is past the end of the file). | ||
> - If the file is 75 bytes or smaller with no null bytes, the `FONTDIR` data for it will be 149 bytes (the first `n` being the bytes from the file, then the rest are `0x00` padding bytes). After that, there will be `n` bytes from the file again, and then a final `0x00`. | ||
> - If the file is between 76 and 140 bytes long with no `0x00` bytes, the MSVC++ `rc` tool will crash. | ||
{: .note } | ||
> `rc.exe` actually seems to interpret the [incorrectly located] `dfFace` and `dfName` values as signed integers. If a value in either field that is interpreted as negative it will lead to a `fatal error RW1023: I/O error seeking in file` error. | ||
## What does `resinator` do? | ||
|
||
`resinator` has gone with the second possibility: write each `FONTDIRENTRY` as specified in the available documentation (so 113 bytes + the device/type name as trailing `NUL`-terminated strings). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
layout: default | ||
title: Resources | ||
nav_order: 2 | ||
has_children: true | ||
permalink: /docs/resources | ||
--- | ||
|
||
# Resources |
Oops, something went wrong.