FONT resource
- The
<id>in the<id> FONTdefinition must be an ordinal, not a string. - The
<id>of eachFONTmust be unique, but this does not fail the compilation, only emits an error. The firstFONTdefined with the<id>takes precedence, all others with the same<id>are ignored. - Each
FONTis stored with typeRT_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.
This is the format that the Win32 RC compiler writes:
| 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. Any missing bytes are padded with 0x00 |
| At least 1 byte | NUL-terminated string containing the ‘device name’. The device name is the string located at the offset specified by the u32 value at offset 140 of the file (e.g. if the u32 at file offset 140 contains the value 500, then the string is at offset 500 from the start of the file) |
| At least 1 byte | NUL-terminated string containing the ‘face name’. The face name is the NUL-terminated string located at the offset specified by the u32 value at the offset 144 of the file (e.g. if the u32 at file offset 140 contains the value 500, then the string is at offset 500 from the start of the file) |
What follows is a more complete explanation of the FONTDIR resource, including why the above format may be undesirable/wrong in some sense.
What is FONT used for anyway?
The FONT resource has two possible use-cases that I am aware of.
.FNT bitmap files
The original and primary use-case of the FONT resource is bundling .fnt bitmap font files 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).
Generating .fon files is somewhat obsolete, and most information online that I could find will direct you to third-party applications that will generate them for you, but in the 16-bit rc.exe, generating .fon files was something that was more directly supported.
From TN318C.txt: 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, where rc.exe is used to generate a .res which then gets linked with a DLL that has no entry point.
Modern Windows versions do still use
.fonfiles (e.g. quite a few.fonfiles are distributed with the default Windows 10 installation inC:\Windows\Fonts). For example, theTerminalfont distributed with Windows 10 is a.fonfile with a copyright year of 1991.
Including custom fonts in a program
There is one Windows-classic-samples example program that uses FONT resources with .TTF files to include custom fonts in a program: Win7Samples/multimedia/DirectWrite/CustomFont. This is meant to be an example of using the DirectWrite APIs described here, but it’s very possible that this is a misuse of the FONT resource.
There is another example that does something similar using some older APIs (Samples/DirectWriteCustomFontSets) that uses user-defined resources for its font files. This seems like a better choice for reasons that will be detailed later.
So what should go in the FONTDIR?
Practically, it doesn’t seem to matter what’s in the FONTDIR; it’s likely not even read/parsed since it’s possible to just iterate over/access 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 only look at the relevant documentation for what should go in a FONTDIR, it does not match up with what the Win32 RC compiler produces:
According to the available documentation, 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-terminated strings (so at the very least they will have a NUL byte).
This would look something 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 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 (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 (105 bytes into the file) |
If we run the 16-bit version of rc.exe, this is exactly what you see. However, in 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
FONTDIRENTRYdocs that accommodate this new size, so anyone trying to enumerate theFONTDIRENTRYs in aFONTDIRwill end up reading garbage after the first entry (since the offset to the nextFONTDIRENTRYwon’t match theFONTDIRENTRYsize as specified in the documentation) - The
szDeviceNameandszFaceNameare wrong. The Win32 compiler seems to be doing subtraction from end of the.FNTheader to get thedfDeviceanddfFacefields (which themselves contain offsets from the beginning of the file to where the associatedNUL-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 doing113 - 12 = 101fordfDeviceit’s doing148 - 12 = 136and reading from there instead). Subsequently, the 32-bit version ofrc.exebasically never writes theszDeviceNameorszFaceNameto theFONTDIRENTRYsince it’s looking in the wrong place for thedfDeviceanddfFacefields. This can be confirmed by modifying byte offset136and140to contain an offset of aNUL-terminatedstring in the file and seeing thatrc.exewill use them asszDeviceName/szFaceName.
The 148 byte size corresponds to the format specified here up to the end of
dfReserved1. Those last 16 reserved bytes are the section in which the 32-bitrc.exeis erroneously interpreting as thedfDevice/dfFacefields.
rc.exeactually seems to interpret the [incorrectly located]dfFaceanddfNamevalues as signed integers. If a value in either field that is interpreted as negative it will lead to afatal error RW1023: I/O error seeking in fileerror.
So really, what should go in the FONTDIR?
My conclusion is that this is almost the wrong question to be asking. Instead, the question becomes: what will cause the least friction? Let’s look at some possibilities.
Do the right thing
One possible idea would be to “do the right thing” with regards to the available documentation and what the .FNT format seems to suggest–write each FONTDIRENTRY as 113 bytes plus the real szDeviceName/szFaceName. However, this is complicated by the potential for non-.FNT font formats to be used in a FONT resource.
Treating these non-.FNT formats as a .FNT file and attempting to read szDeviceName/szFaceName from them is guaranteed to lead to bogus results. This is actually what the Win32 RC compiler does, though, and it means that it could error on otherwise valid .TTF/.OTF/etc font files (if the dfDevice/dfFace values are interpreted as negative; see the note above). This is the reason that using FONT for non-.FNT font files is likely a mistake (alluded to previously in the ‘Including custom fonts in a program’ section).
In any case, it becomes unclear exactly how non-.FNT files should be handled if the goal is to “do the right thing,” since there’s no available precedent besides the obviously wrong current Win32 RC compiler behavior.
Emulate the Win32 behavior
Another possible idea would be to emulate the Win32 RC compiler behavior exactly, bug-for-bug. This has the downside of having to intentionally misinterpret all FONT files and treat the wrong offsets as dfDevice/dfFace, meaning that the szDeviceName/szFaceName written to FONTDIRENTRY are basically guaranteed to always be wrong and therefore useless.
If the font file is 140 bytes or fewer, the Win32 RC compiler seems to default to a
dfFaceof0(as the [incorrect] location of thedfFacefield is past the end of the file).
- If the file is 75 bytes or smaller with no null bytes, the
FONTDIRdata for it will be 149 bytes (the firstnbeing the bytes from the file, then the rest are0x00padding bytes). After that, there will benbytes from the file again, and then a final0x00.- If the file is between 76 and 140 bytes long with no
0x00bytes, the MSVC++rctool will crash.
Semi-compatibility while avoiding the sharp edges
The other possible idea would be to do something similar enough to the Win32 compiler in the common case, but avoid emulating the buggy behavior where it makes sense. In this case, that would look like a FONTDIRENTRY with the following format:
- The first 148 bytes from the file verbatim, with no interpretation whatsoever, followed by two
NULbytes (corresponding to ‘device name’ and ‘face name’ both being zero length strings)
This would allow the FONTDIR to match byte-for-byte with the Win32 RC compiler in the common case (since very often the misinterpreted dfDevice/dfFace will be 0 or point somewhere outside the bounds of the file and therefore will be written as a zero-length string anyway), and only differ in the case where the Win32 RC compiler writes some bogus string(s) to the szDeviceName/szFaceName.
This also enables the use-case of non-.FNT files without any loose ends.
Always writing
szDeviceName/szFaceNameas zero length is very likely to not cause any problems since the Win32 RC compiler already writes bogus data for those fields, so there’s no real way that anything expects thoseFONTDIRfields to contain reliable information.
What does resinator do?
resinator has gone with the “Semi-compatibility while avoiding the sharp edges” possibility.