diskfont V47 Direct Outline Font API¶
diskfont.library V47 (AmigaOS 3.2) gives applications direct access to outline font engines such as bullet.library without going through a bitmap TextFont. Seven functions make this work. OpenOutlineFont() and CloseOutlineFont() find, load, validate and relocate an .otag file and open its engine. EOpenEngine(), ECloseEngine(), ESetInfoA(), EObtainInfoA() and EReleaseInfoA() are versions of the bullet.library engine calls that take the engine's library base from a structure instead of a global BulletBase.
The same release adds WriteFontContents(), WriteDiskFontHeaderA() and ObtainCharsetInfo() to the SFD (see the V47 SFD block).
Version requirement: V47 only¶
The functions first appeared in diskfont 46.x, but the diskfont 47.1 release notes are explicit:
As of V47, the functions dealing with direct outline font handling, namely EOpenEngine(), ECloseEngine(), ESetInfoA(), EObtainInfoA(), EReleaseInfoA(), OpenOutlineFont() and CloseOutlineFont(), are no longer compatible with their previous (V46) implementation. […] Please do NOT use the outline font API in your applications if the version of diskfont.library is lower than 47.
The headers diskfont/diskfont.h, clib/diskfont_protos.h, pragmas/diskfont_pragmas.h and diskfont_lib.sfd changed with this release, so build against the NDK 3.2 headers and open the library with version 47:
DiskfontBase = OpenLibrary("diskfont.library", 47);
47.1 also fixed OpenOutlineFont() being unable to open .otag fonts outside FONTS: or the root directory, and added "the still missing autodoc entries".
Why the E-functions exist¶
The bullet.library calls (OpenEngine(), SetInfoA(), ObtainInfoA(), ReleaseInfoA(), CloseEngine()) are called through the base variable BulletBase (==base _BulletBase in bullet_lib.sfd). The .otag file names its engine in OT_Engine, and bullet.doc describes appending .library to that name and opening it with OpenLibrary(). As a result, different fonts may need different engine bases. The diskfont autodocs state that each E-function "is similar to bullet.library/…() but does not use a (hidden) "BulletBase" variable that may hold the library base of the wrong font engine if not set properly. It uses EEngine->ege_BulletBase as base of the font engine library to be called."
Structures¶
<diskfont/diskfont.h>:
struct EGlyphEngine {
APTR ege_Reserved; /* System reserved, don't touch */
struct Library *ege_BulletBase;
struct GlyphEngine *ege_GlyphEngine;
};
/* flags for OpenOutlineFont() (V47) */
#define OFB_OPEN 0
#define OFF_OPEN 0x00000001L
/* structure returned by OpenOutlineFont() (V47) */
struct OutlineFont {
STRPTR olf_OTagPath; /* full path & name of the .otag file */
struct TagItem *olf_OTagList; /* relocated .otag file in memory */
STRPTR olf_EngineName; /* OT_Engine name */
STRPTR olf_LibraryName; /* OT_Engine name + ".library" */
struct EGlyphEngine olf_EEngine; /* All NULL if OFF_OPEN not specified */
APTR olf_Reserved; /* for future expansion */
APTR olf_UserData; /* for private use */
};
<diskfont/glyph.h>:
struct GlyphEngine {
struct Library *gle_Library; /* engine library */
STRPTR gle_Name; /* library basename: e.g. "bullet" */
/* private library data follows... */
};
typedef LONG FIXED; /* 32 bit signed w/ 16 bits of fraction */
struct GlyphMap {
UWORD glm_BMModulo; /* # of bytes in row: always multiple of 4 */
UWORD glm_BMRows; /* # of rows in bitmap */
UWORD glm_BlackLeft; /* # of blank pixel columns at left */
UWORD glm_BlackTop; /* # of blank rows at top */
UWORD glm_BlackWidth; /* span of contiguous non-blank columns */
UWORD glm_BlackHeight; /* span of contiguous non-blank rows */
FIXED glm_XOrigin; /* distance from upper left corner of bitmap */
FIXED glm_YOrigin; /* to initial CP, in fractional pixels */
WORD glm_X0, glm_Y0; /* approximation of XOrigin/YOrigin in whole pixels */
WORD glm_X1, glm_Y1; /* approximation of XOrigin + Width ... */
FIXED glm_Width; /* character advance, as fraction of em width */
UBYTE *glm_BitMap; /* actual glyph bitmap */
};
struct GlyphWidthEntry (16-bit code) and struct GlyphWidthEntry32 (32-bit gwe32_Code) are the nodes of the lists returned by OT_WidthList and OT_WidthList32.
Function reference¶
| Function | LVO | Registers | Prototype (SFD 47.1) |
|---|---|---|---|
EOpenEngine 3.2+ |
-72 | A0 | LONG EOpenEngine(struct EGlyphEngine *EEngine) |
ECloseEngine 3.2+ |
-78 | A0 | VOID ECloseEngine(struct EGlyphEngine *EEngine) |
ESetInfoA 3.2+ |
-84 | A0, A1 | ULONG ESetInfoA(struct EGlyphEngine *EEngine, CONST struct TagItem *taglist) |
EObtainInfoA 3.2+ |
-90 | A0, A1 | ULONG EObtainInfoA(struct EGlyphEngine *EEngine, CONST struct TagItem *taglist) |
EReleaseInfoA 3.2+ |
-96 | A0, A1 | ULONG EReleaseInfoA(struct EGlyphEngine *EEngine, CONST struct TagItem *taglist) |
OpenOutlineFont 3.2+ |
-102 | A0, A1, D0 | struct OutlineFont *OpenOutlineFont(CONST_STRPTR name, struct List *list, ULONG flags) |
CloseOutlineFont 3.2+ |
-108 | A0, A1 | VOID CloseOutlineFont(struct OutlineFont *olf, struct List *list) |
ESetInfo(), EObtainInfo() and EReleaseInfo() are the varargs forms. The LVOs are from Include_I/lvo/diskfont_lib.i. WriteFontContents (-114), WriteDiskFontHeaderA (-120) and ObtainCharsetInfo (-126) follow.
OpenOutlineFont()¶
This function "finds the .otag file for the specified outline font on disk, loads it into memory, validates that OT_FileIdent exists and matches the file size, validates that OT_Engine exists and relocates all OT_Indirect tags". With OFF_OPEN it also opens the engine library and a glyph engine, and calls ESetInfo() with OT_OTagPath and OT_OTagList.
- name: if it contains a
:, the.otagfile is searched in that path. Otherwise it is searched inFONTS:. The name may end in.font,.otagor nothing (e.g."Nimbus Sans L Regular Condensed Italic"), and there is no length limit. - list: ignored without
OFF_OPEN. Otherwise it isNULLor aNewList()-initialisedstruct Listthat caches engine libraries already opened by this task. Pass the same list (or always NULL) to everyOpenOutlineFont()/CloseOutlineFont()call. To share the list between tasks, you must lock it yourself (for example, with a semaphore). - flags: only
OFF_OPENis defined. Without it, you get the loaded and relocated tag list only, so you can examine it, for example to checkOT_IsFixedorOT_StemWeight, before deciding to use the font. - Result:
NULLif the file cannot be found, loaded or validated, if the engine or glyph engine cannot be opened, or if theESetInfo()of path and list fails.
Opening later. If you loaded without OFF_OPEN and decide to use the font, the autodoc gives the sequence:
OpenLibrary(olf->olf_LibraryName, 0)and store the result inolf->olf_EEngine.ege_BulletBase.EOpenEngine(&olf->olf_EEngine).ESetInfo(&olf->olf_EEngine, OT_OTagPath, olf->olf_OTagPath, OT_OTagList, olf->olf_OTagList, TAG_END).- When done, call
ECloseEngine()andCloseLibrary()yourself, then set allolf_EEnginemembers back to NULL beforeCloseOutlineFont(). "Or just use your own EGlyphEngine structure instead."
EOpenEngine() / ECloseEngine()¶
EOpenEngine() needs ege_BulletBase set to an open engine library. It returns TRUE with ege_GlyphEngine set, or FALSE with it NULL. A context "can be shared among different tasks if the caller uses a locking mechanism … that ensures that the context is not accessed concurrently and that e.g. a ESetInfo(), EObtainInfo() sequence cannot be interrupted by another task".
ECloseEngine() first releases any EObtainInfoA() data still held, then sets ege_GlyphEngine to NULL. It does nothing if the engine or ege_GlyphEngine is NULL. It does not close ege_BulletBase. That is your job.
ESetInfoA()¶
This function sets attributes from a tag list. Data passed through OT_Indirect tags "is copied … into the internal memory of the library". It returns 0 (OTERR_Success) or an error code. "The function may return with a non-zero error code before it has parsed the whole tag list."
EObtainInfoA() / EReleaseInfoA()¶
Each data field of the tag list is a pointer to a longword destination: "All destinations are longwords, whether they are pointers or values". Indirect results are read-only and must be returned with EReleaseInfoA(), using the same tag and the pointer you received. Data from non-indirect tags need not be released, but releasing it is not an error.
Warning (autodoc): EObtainInfoA() may stop at the first error and may leave your pointers unchanged. Either initialise them to NULL (NULL pointers are "quietly accepted and ignored" by EReleaseInfoA()), or obtain one indirect item per call and release it only on success.
Tags¶
From <diskfont/diskfonttag.h>. "!" means valid for specification (ESetInfo) and "?" means valid for inquiry (EObtainInfo):
| Tag | Value | Use | Notes |
|---|---|---|---|
OT_DeviceDPI 2.0+ |
OT_Level0+0x01 |
! | X DPI high word, Y DPI low word (== TA_DeviceDPI) |
OT_DotSize 2.0+ |
+0x02 |
! | Percent, default 0x00640064; not all engines accept others |
OT_PointHeight 2.0+ |
+0x08 |
! | FIXED points (1/72") |
OT_SetFactor 3.1+ |
+0x09 |
! | Width distortion, default 1.0 |
OT_ShearSin/OT_ShearCos |
+0x0a/+0x0b |
! | Set sine first, then cosine |
OT_RotateSin/OT_RotateCos |
+0x0c/+0x0d |
! | Set sine first, then cosine |
OT_EmboldenX/OT_EmboldenY |
+0x0e/+0x0f |
! | Fraction of em, negative lightens |
OT_PointSize 2.0+ |
+0x10 |
! | Old form, points × 16 |
OT_GlyphCode/OT_GlyphCode2 |
+0x11/+0x12 |
! | 16-bit codes |
OT_GlyphCode_32/OT_GlyphCode2_32 |
+0x18/+0x19 |
! | 32-bit codes; old engines return OTERR_UnknownTag |
OT_GlyphWidth 3.1+ |
+0x13 |
! | Force escapement; 0 = font-defined |
OT_OTagPath/OT_OTagList |
+0x14/+0x15 | OT_Indirect |
! | Select typeface: path first, then list |
OT_MemPtr/OT_MemSize |
+0x16 | OT_Indirect / +0x17 |
! | Raw font in memory; not all engines |
OT_GlyphMap 3.1+ |
+0x20 | OT_Indirect |
? | struct GlyphMap *, 1 bit per pixel |
OT_GlyphMap8Bit 3.2+ |
+0x1a | OT_Indirect |
? | Anti-aliased: 1 byte per pixel, 0 = background, 0xff = text colour |
OT_WidthList/OT_WidthList32 |
+0x21/+0x1b | OT_Indirect |
? | MinList of width entries from GlyphCode to GlyphCode2 |
OT_TextKernPair/OT_DesignKernPair |
+0x22/+0x23 | OT_Indirect |
? | Kern between GlyphCode and GlyphCode2 |
OT_UnderLined 3.1+ |
+0x24 |
OTUL_None 0, _Solid 1, _Broken 2, _DoubleSolid 3, _DoubleBroken 4 |
|
OT_StrikeThrough 3.1+ |
+0x25 |
OT_Level0 = TAG_USER, and OT_Indirect = 0x8000. .otag files carry Level 1 and Level 2 tags such as OT_FileIdent (must be first, matches file size), OT_Engine (OTE_Bullet = "bullet"), OT_Family, OT_YSizeFactor, OT_IsFixed, OT_SerifFlag, OT_StemWeight (OTS_UltraThin 8 … OTS_UltraBlack 248), OT_SlantStyle (OTS_Upright/OTS_Italic/OTS_LeftItalic) and OT_AvailSizes (at most OT_MAXAVAILSIZES = 20).
Notes from the header:
- For screens, "use 72 DPI for Y and an aspect-corrected value for X". You can get the aspect from
GetDisplayInfoData(). - Some engines return an error for blank glyphs such as space. Others return a 1×1 empty bitmap with spacing.
OT_GlyphCode_32makes sense only when the.otaghasOT_UnicodeRangeswithUCR_SURROGATESset.
Error codes (<diskfont/oterrors.h>)¶
| Code | Value | Meaning |
|---|---|---|
OTERR_Failure 3.1+ |
-1 | Catch-all |
OTERR_Success 3.1+ |
0 | No error |
OTERR_BadTag 3.1+ |
1 | Inappropriate tag for function |
OTERR_UnknownTag 3.1+ |
2 | Unknown tag for function |
OTERR_BadData 3.1+ |
3 | Bad tag data |
OTERR_NoMemory 3.1+ |
4 | Insufficient memory |
OTERR_NoFace 3.1+ |
5 | No typeface specified |
OTERR_BadFace 3.1+ |
6 | Typeface specification problem |
OTERR_NoGlyph 3.1+ |
7 | No glyph specified |
OTERR_BadGlyph 3.1+ |
8 | Bad glyph code or range |
OTERR_NoShear 3.1+ |
9 | Shear only partially specified |
OTERR_NoRotate 3.1+ |
10 | Rotate only partially specified |
OTERR_TooSmall 3.1+ |
11 | Metrics yield tiny glyphs |
OTERR_UnknownGlyph 3.1+ |
12 | Glyph not known by engine |
Example: glyph metrics of an outline font¶
#include <exec/types.h>
#include <diskfont/diskfont.h>
#include <diskfont/diskfonttag.h>
#include <diskfont/glyph.h>
#include <diskfont/oterrors.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/diskfont.h>
struct Library *DiskfontBase;
int main(int argc, char **argv)
{
struct OutlineFont *olf;
struct GlyphMap *gm;
ULONG err, code;
if (argc < 2) return RETURN_ERROR;
if (!(DiskfontBase = OpenLibrary("diskfont.library", 47)))
{
PutStr("diskfont.library V47 required\n");
return RETURN_FAIL;
}
if ((olf = OpenOutlineFont(argv[1], NULL, OFF_OPEN)) != NULL)
{
Printf("%s via %s\n", olf->olf_OTagPath, olf->olf_LibraryName);
err = ESetInfo(&olf->olf_EEngine,
OT_DeviceDPI, (72 << 16) | 72,
OT_PointHeight, 24 << 16, /* 24.0 points */
TAG_END);
for (code = 'A'; err == OTERR_Success && code <= 'E'; code++)
{
if (ESetInfo(&olf->olf_EEngine, OT_GlyphCode, code, TAG_END)
!= OTERR_Success)
continue;
gm = NULL;
if (EObtainInfo(&olf->olf_EEngine, OT_GlyphMap, &gm, TAG_END)
== OTERR_Success && gm != NULL)
{
Printf("'%lc': black %ldx%ld at (%ld,%ld), "
"advance %ld/65536 em, modulo %ld\n",
code,
(LONG)gm->glm_BlackWidth, (LONG)gm->glm_BlackHeight,
(LONG)gm->glm_BlackLeft, (LONG)gm->glm_BlackTop,
gm->glm_Width, (LONG)gm->glm_BMModulo);
EReleaseInfo(&olf->olf_EEngine, OT_GlyphMap, gm, TAG_END);
}
}
CloseOutlineFont(olf, NULL);
}
else
Printf("cannot open outline font %s\n", argv[1]);
CloseLibrary(DiskfontBase);
return RETURN_OK;
}
OpenOutlineFont() with OFF_OPEN and a NULL list opens the engine for you, and CloseOutlineFont() with the same NULL list closes it again. The program never touches a BulletBase variable.
Finding outline fonts¶
AvailFonts() gained filters in V46 that combine with this API:
| Flag | Value | Effect |
|---|---|---|
AFF_OTAG 3.1.4+ |
0x0010 | Only fonts with an .otag file, i.e. "accessible via the bullet API" |
AFF_OTAG \| AFF_SCALED |
Plus an extra entry with ta_YSize 0 for each freely scalable font |
|
AFF_TYPE 3.1.4+ |
0x0040 | [t]af_Type becomes AFF_DISK\|AFF_BITMAP, AFF_DISK\|AFF_OTAG, or AFF_DISK\|AFF_OTAG\|AFF_SCALED |
AFF_BITMAP 3.1+ |
0x0008 | The opposite: filter out outline fonts |
Pass the font name from such an entry to OpenOutlineFont(). The .font suffix is accepted.
The V47 SFD block¶
The ==version 47 section of SFD/diskfont_lib.sfd 47.1 lists, in vector order:
LONG EOpenEngine( struct EGlyphEngine *EEngine ) (A0)
VOID ECloseEngine( struct EGlyphEngine *EEngine ) (A0)
ULONG ESetInfoA( struct EGlyphEngine *EEngine, CONST struct TagItem *taglist ) (A0,A1)
ULONG EObtainInfoA( struct EGlyphEngine *EEngine, CONST struct TagItem *taglist ) (A0,A1)
ULONG EReleaseInfoA( struct EGlyphEngine *EEngine, CONST struct TagItem *taglist ) (A0,A1)
struct OutlineFont *OpenOutlineFont( CONST_STRPTR name, struct List *list, ULONG flags ) (A0,A1,D0)
VOID CloseOutlineFont( struct OutlineFont *olf, struct List *list ) (A0,A1)
LONG WriteFontContents( BPTR fontsLock, CONST_STRPTR fontName,
CONST struct FontContentsHeader *fontContentsHeader ) (A0,A1,A2)
LONG WriteDiskFontHeaderA( CONST struct TextFont *font, CONST_STRPTR fileName,
CONST struct TagItem *tagList ) (A0,A1,A2)
ULONG ObtainCharsetInfo( ULONG knownTag, ULONG knownValue, ULONG wantedTag ) (D0,D1,D2)
The last three have no entries in diskfont.doc. The release notes say only that WriteDiskFontHeader() and WriteFontContents() were "Added" in 46.x, and that ObtainCharsetInfo() was added because it was "needed by asl.library" (46.10).
Gotchas¶
- Check the version (≥ 47) before calling any of these functions. The V46 entry points have different semantics.
- Path before list.
OT_OTagPathmust be set beforeOT_OTagList, sine before cosine for shear and rotation, andOT_MemPtrbeforeOT_MemSize. Setting only the first part leaves the state undefined. - Release what you obtain, with the same tag, or let
ECloseEngine()clean up. - Don't change
DFCTRL_*settings from applications.SetDiskFontCtrlA()is meant for the Font preferences editor ("applications MUST NOT call this to enable or disable the cache setting").
See Also¶
- Bullet Library and Outline Fonts
- diskfont.library Reference
- bullet.library Reference
- Preferences File Formats (
font.prefs) - AmigaOS 3.2 — What Changed for Developers
Sources: NDK 3.2 R4 Autodocs/diskfont.doc, Autodocs/bullet.doc, SFD/diskfont_lib.sfd, Include_H/diskfont/{diskfont,diskfonttag,glyph,oterrors}.h, ReleaseNotes/diskfont-RelNotes Updated: 2026-11-13