LLX > Neil Parker > Apple II > AMPERFONT

AMPERFONT: Display IIGS Fonts in Applesoft

AMPERFONT is an add-on for Applesoft which adds a set of commands for drawing text on the hi-res screen using Apple IIGS fonts. Any IIGS bitmap font can be used, as long as you can find room for it in memory (Truetype fonts for Pointless cannot be used). It works on any Apple II with at least 64K of RAM and Applesoft in ROM. A machine with a lowercase keyboard is recommended - AMPERFONT can display lowercase characters on any Apple II (if you've selected a font that has them), but no provisions have been made for typing lowercase on uppercase-only systems.

AMPERFONT installs itself above HIMEM, between BASIC.SYSTEM and its file buffers. It consumes 1.25K of memory, lowering HIMEM (if AMPERFONT is the first such module loaded) to 37120 ($9100). If you BRUN it a second time, it will install a second copy of itself, consuming another 1.25K of memory, so a means has been provided by which your program can detect whether AMPERFONT is already installed.

AMPERFONT and its source code are freeware, and may be redistributed in accordance with the terms in the included LICENSE file (a 3-clause BSD license).

Commands

BRUN AMPERFONT

 BRUN AMPERFONT

This installs AMPERFONT. It loads at memory location 8192 ($2000), and then relocates itself above HIMEM, and points the & and USR vectors at itself.

AMPERFONT has been designed to coexist peacefully with other above-HIMEM modules, and unlike some such modules, it need not be the first one loaded.

& "AMPERFONT"

 & "AMPERFONT"

This command (note the quote marks...they're part of the command name) does nothing. Its purpose is to help you detect whether or not AMPERFONT has already been loaded, so you can avoid loading it more than once and wasting 1.25K of memory. For example,

10  ON  PEEK (116) > 145 OR  PEEK (1015) = 190 GOTO 40: ONERR  GOTO 30
20  & "AMPERFONT": POKE 216,0: GOTO 50
30  CALL 62248: POKE 216,0
40  PRINT  CHR$ (4);"BRUN AMPERFONT"
50  REM The rest of your program starts here

(PEEK (116) is the high byte of HIMEM - comparing it to 145 ($91) tests whether HIMEM is already low enough to accommodate a previously-installed AMPERFONT. PEEK (1015) is the high byte of the & vector - if it's 190 ($BE), then no & routine has yet been connected.)

& LOAD

 &  LOAD numeric-expression

This tells AMPERFONT where in memory to find the font you want to use next. The numeric-expression is any expression that evaluates to an address in memory. The supplied address must point to the first byte of an Apple IIGS bitmap font, but AMPERFONT doesn't try to verify this - if you give it a bad address, it will merrily go crazy trying to draw random junk.

You must get the font into memory before using & LOAD, for example,

100  PRINT  CHR$ (4);"BLOAD FONTS/CHICAGO.12,T$C8,A16384"
110  &  LOAD 16384

& LOAD deposits a pointer to a (slightly modified) copy of the font's Macintosh font header in memory locations 6 (low byte) and 7 (high byte). This can be used to PEEK useful information about the font, as described below. The buffer pointed to will not move while your program is running, but its contents will change every time you & LOAD a new font.

& DRAW and & XDRAW

 &  DRAW string-expression [AT numeric-expression,numeric-expression]
 &  XDRAW string-expression [AT numeric-expression,numeric-expression]

& DRAW draws text on the current hi-res graphics screen. Before using it, you must first select a graphics screen (with HGR, HGR2, or POKE 230,value) and load a font with & LOAD.

& DRAW displays the string-expression using the font most recently loaded with & LOAD. If the AT clause is included, drawing starts at the specified coordinates (which are normal hi-res graphics coordinates, i.e., a value in the range 0 to 279, and a value in the range 0 to 191). If there is no AT clause, drawing starts at the most recently plotted point.

The starting point is the point where the character origin point of the string's first character will be placed. This is usually more or less the lower left corner of the character; thus, you should expect that most of what gets drawn will be above the Y-coordinate of the starting point. How far above is determined by the font's ascent, which can be PEEKed out of the font header, as described below.

Note that descenders (the tails of characters like g, p, q, and y) hang below the starting Y-coordinate. How far below is the font's descent, which can also be PEEKed out of the font header.

After the string has been drawn, the "most recently plotted point" will have same Y-coordinate as the starting point, and the X-coordinate will have moved to the right by the width of the string.

The starting point must be within the hi-res screen, but after that, any pixel that ends up past any edge of the screen will be wrapped around to reappear at the opposite edge. This is similar to how shape table shapes behave if they draw past the edge of the screen.

& DRAW draws using the current HCOLOR. You will usually want to use HCOLOR= 3 or 7 for drawing on a black background, or HCOLOR= 0 or 4 on a white background. Other colors are not recommended - if the font has lots of one-pixel vertical stems, the text will probably come out illegible, and if the vertical stems are two or more pixels wide, the text, though probably legible, will usually be ugly.

& DRAW does not treat any character of the string as special, not even control characers (e.g., you can't advance to a new line by putting CHR$ (13) into the string...you need to draw a new string at a new position for that). This may be helpful with certain (mostly third-party) fonts that put useful printable characters in the control character positions.

& XDRAW works exactly like & DRAW, except that the current HCOLOR is ignored, and the text is drawn by reversing the pixels already on the screen.

& SPC()

 &  SPC( numeric-expression)

This selects how wide each character will be drawn by & [X]DRAW. The numeric-expression can evaluate to one of the following:

0 Each character will be drawn with its natural width. This is the default setting.
1 Each character will be drawn as wide as the font's widest character. Characters narrower than that are widened by padding on the right with blank pixels.
2 Each character will be drawn as wide as the font's "0" character (CHR$ (48)). Characters wider than that will be overlapped by the following character. This is useful for aligning columns of numbers.

These values are the same values accepted by the IIGS SetFontFlags routine. & LOAD does an automatic & SPC( 0).

USR

 USR (string-expression)

This function accepts a string expression as an argument, and returns a number giving the width in pixels of that string, calculated using the current font and the current & SPC() setting. It can be used anywhere where Applesoft allows a function with a numeric result.

Technically, USR (string) returns the advance width of the string, which is how far to the right the plotting point would be moved if the string were to be drawn on the screen. Because characters can have pixels that overlap past their width (this is called kerning), this width may miss a few pixels at each end of the string.

& USR

 &  USR

This command points the USR vector at AMPERFONT's USR function handler. This is normally done by BRUN AMPERFONT, but since the USR vector cannot easily be used by more than one handler at a time, this provides a way to reconnect AMPERFONT's USR function after something else has usurped it.

& &

If any other &-handlers were installed before AMPERFONT, they remain available, and can be invoked as if AMPERFONT were not present, unless they happen to have the same name as an AMPERFONT command. In that case, the prior handler can be invoked by putting a second & in front of the statement.

Unfortunately the same convenience is not available for USR (it's not clear how to identify whether a USR call should be passed on to another handler). At least with the & USR command, it's possible to reconnect AMPERFONT's handler if something else replaces it.

Styles

The style modifications (italics, boldface, underline, outline, and shadowed) that the IIGS can apply to fonts are NOT available in AMPERFONT. But in a pinch you can get boldface like this:

 &  DRAW A$ AT X,Y: &  DRAW A$ AT X + 1,Y

This is similar to how the IIGS does boldface.

You can simulate underlining with HPLOT, but you'll have to compute the coordinates of the line yourself. The USR function can help with this - for example,

 W =  USR (A$): HPLOT X,Y + 2 TO X + W, Y + 2: &  DRAW A$ AT X,Y

If you need to underline something in the middle of a string, it will help to know that after most plotting operations, including & [X]DRAW, the X-coordinate of the most recent point is PEEK (224) + 256 * PEEK (225), and the Y-coordinate is PEEK (226).

Font Header Information

The & LOAD command leaves a pointer to a copy of the font's Macintosh header in memory locations 6 and 7. The buffer pointed to by this pointer will remain at the same address as long as your program is running, so you only need to get its address once. But its contents change after every & LOAD command to reflect the newly-loaded font.

Be careful not to use any other AMPERFONT commands between & LOAD and fetching the pointer. AMPERFONT makes heavy use of memory locations 6 and 7 for a variety of purposes.

Several useful pieces of information can be found in the Macintosh header. Suppose the following commands have been executed:

 &  LOAD <address>: FH =  PEEK (6) + 256 *  PEEK (7)

Then PEEKs of the following addresses reveal the following information (when two addresses are listed, the first holds the low byte and the second holds the high byte):

FH, FH+1 The pointer to the font's bitmap. Fonts normally store the Macintosh font type here, but AMPERFONT has no use for that, so it stores the bitmap pointer here instead.
FH+2 firstChar: The CHR$ code for the font's first defined character.
FH+4 lastChar: The CHR$ code for the font's last defined character.
FH+6 widMax: The advance width of the font's widest character. The & SPC( 1) command pretends all characters are this wide.
FH+8, FH+9 kernMax: The greatest number of pixels that any character leans to left of its character origin. Note that this is a signed number, so you'll need a calculation like KM = PEEK (FH + 8) + 256 * PEEK (FH + 9): IF KM > 32767 THEN KM = KM - 65536. Negative values mean at least one character has that many pixels to the left of its origin; positive values mean all characters have at least that many blank pixel columns to the right of the origin.
FH+10, FH+11 The pointer to the location table. Fonts normally store the negative of the descent here, but AMPERFONT has no use for that, so it puts the location table pointer here instead.
FH+12 fRectWidth: The width of the font rectangle.
FH+14 fRectHeight: The height of the font in pixels. Also the number of rows in the font's bitmap.
FH+16, FH+17 The pointer to the offset/width table.
FH+18 ascent: The number of pixels that the font has above the character origin. This includes the character origin row.
FH+20 descent: The number of pixels that the font has below the character origin. This does not include the character origin row. Note that ascent + descent = fRectHeight.
FH+22 leading (pronounced "ledding"): The suggested amount of blank space (in pixels) to leave between adjacent lines.
FH+24, FH+25 The number of bytes in each row of the font's bitmap. Fonts normally store the number of 16-bit words per row here, but AMPERFONT converts it to the more useful number of bytes.

(Actually, all of the fields in the header are two-byte fields. But some fields only have useful information in the low byte, and for some fields, fonts big enough to need the high byte are too big to be used by 8-bit software.)

IIGS fonts also have an additional header at the front, which AMPERFONT ignores, but which some Applesoft programs might find useful. Suppose a font has been loaded at "<address>" - then the IIGS header starts at memory location GH = <address> + PEEK (<address>) + 1.

GH+2, GH+3 The Macintosh/IIGS font family number.
GH+4 A bit mask indicating style modifications that have been pre-applied to the font by its designer. A sum of the following values:
1 Bold
2 Italic
4 Underline
8 Outline
16 Shadow
GH+6 The font's official declared size. Usually matches the number at the end of the font's filename, but may or may not have any other connection to reality. (The font's true vertical size is in the fRectHeight field described above.)
GH+10, GH+11 fbrExtent: The greatest number of pixels that any character of the font can extend left or right of the character origin.

Additionally, the font file begins with font's name, as it appears in the Fonts menu or font chooser dialog box in Apple IIGS programs. AMPERFONT ignores it, but Applesoft programs can find it with PEEK - the font's first byte is the number of characters in the name, followed immediately by the ASCII characters of the name. For example,

200 L =  PEEK (<address>):N$ = ""
210  FOR I = 1 TO L
220 N$ = N$ +  CHR$ ( PEEK (<address> + I))
230  NEXT

For more information on the font format and the meanings of its fields, see the Apple IIGS Toolbox Reference: Volume 2, pages 16-41 through 16-54, and the Apple II File Type Note for File Type $C8 (200), Auxiliary Type $0000.

DOS 3.3 Version

The documentation above describes the ProDOS version of AMPERFONT. The DOS 3.3 version is almost, but not quite, the same.

The DOS 3.3 version can run with 48K of RAM. It might also work with as little as 32K, but that would be a tight squeeze. Because DOS 3.3 doesn't require HIMEM to be page-aligned, the DOS 3.3 version consumes 1120 bytes, instead of the 1280 bytes of the ProDOS version.

The DOS 3.3 version installs itself between HIMEM and the DOS file buffers. This memory is safe from being trampled on by Applesoft veriables, but not from DOS commands like FP, INT, or MAXFILES. Therefore, rather than test for its presence as shown under & "AMPERFONT" above, it's probably best for a program under DOS 3.3 to just throw away any already-loaded copy and reload from scratch. For example,

10  POKE 1013,76: POKE 1014,88: POKE 1015,255: REM Reset & vector
20  PRINT  CHR$ (4);"MAXFILES 3": REM Throw away above-HIMEM buffer
30  PRINT  CHR$ (4);"BRUN AMPERFONT"
40  REM The rest of your program continues here

Of course MAXFILES can be set to values other than 3 in line 20. If your program doesn't open any text files, MAXFILES 1 would leave some additional memory free.

Because BRUN AMPERFONT moves HIMEM, a DOS 3.3 program should avoid using any string variables until after AMPERFONT is installed.

Getting IIGS fonts onto a DOS 3.3 disk can be a bit tricky. I've found that Copy II Plus 8.4 works better for this than Apple's utilities. For the fonts on the AMPERFONT disk, I made copies of the ProDOS font files, and used a filetype-changing utility to convert them to BIN files before transferring them to the DOS 3.3 disk.

Downloads

In addition to AMPERFONT and its source code (Merlin Pro assembler), these contain two Applesoft programs - a brief demonstration ("DEMO") and a program to display all the characters in a font ("SHOWFONT") - and a set of fonts to play with.

None of the fonts are my work - they're all classic Macintosh and IIGS fonts from Apple Computer, some of them rarely seen since the early days of MacOS.

Due to limited disk space, the documentation file is omitted from the DOS 3.3 disk.

LLX > Neil Parker > Apple II > AMPERFONT

Original: February 22, 2021
Modified: September 26, 2021--Small improvements made to the parser (to play better with other &-handlers), and SHOWFONT fixed to ask for the font file instead of making the user edit the program.
Modified: October 5, 2021--Added DOS 3.3 version.
Modified: January 16, 2022--Added & USR command; DOS 3.3 relocator no longer wastes memory by rounding code size up to a full page.
Modified: September 12, 2022--Fixed bug that made CHR$ (0) always print as the missing character glyph even if it's not missing.