[comp.sys.amiga] Will the REAL SetFont Please Stand Up?

daveh@cbmvax.cbm.UUCP (Dave Haynie) (05/24/87)

Here's the source code for my version of SetFont (V2.0).  I haven't done 
anything to it in months, so if you already have a copy, you probably don't 
need this one.  However, I never posted to this net, so here it is.

If you think that its doing something stupid, you may very well be correct,
but most people seem to have been satisfied with it's performance and it's
not crashing of the system.


Dave Haynie     Commodore-Amiga    Usenet: {ihnp4|caip|rutgers}!cbmvax!daveh
"The A2000 Guy"                    BIX   : hazy
	"These are the days of miracle and wonder" -P. Simon

---------------------- Cut, chop, slice, hack, etc. here --------------------

/* ======================================================================= */

/* SetFont 2.0	-

		by Dave Haynie
			BIX:	hazy
			Usenet:	{allegra|caip|ihnp4}!cbmvax!daveh
			Drink:	Guiness

   BUSINESS (MUNDANE, BUT IMPORTANT):

	This program is written by me, Dave Haynie, as mentioned above.
   I have, however, placed it in the PUBLIC DOMAIN.  That, of course, means
   that you can do (almost) absolutely anything with it.  You may hack it
   to bits, include it with YOUR product, sell it, GIVE IT AWAY, or anything
   else you like.  Of course, since anyone else can also give it away, it
   would be foolish to stick your name on it and try to sell it.  I really
   would like everyone to give it away, nicely, without being mean about it.
   If you want to use it with your product, do that too, new products for
   the Amiga are generally a good thing for the entire Amiga community.  You
   can take bit and pieces and use them in your own programs, or the whole
   thing for that matter.  And if you really want to have fun, you can even
   IMPROVE this program, as its far from perfect, and give YOUR version 
   away too, if you're so inclined.  That's about it for the (mundane)
   business section, let's get on to the (interesting) notes section.

   ABOUT IT:

	This program is designed to allow you to set the font of a console
   window (or really, a general WorkBench window) to any font, ROM or disk
   based, that you have in mind.  This works much better than the first
   version, in that it allows a greater variety of things to be changed
   in the WorkBench.  What, you say, a VARIETY of things?  Well, lemme
   explain....

   	First off, something about fonts.  Amiga fonts all over the place
   default to one of two ROM fonts, as specified in Preferences.  A ROM font
   is just a font that happens to stored in with the KickStart code, instead
   of out on a disk.  Since anything ever running on any Amiga can get to 
   these fonts, they're used very heavily, perhaps too much.  If you've 
   ever used the NotePad, however, you will probably have used some other
   fonts.  These fonts are available on disk, and in fact, you can use
   FontEd to create a variety of disk fonts for your own use.

   Whether disk or ROM, most programs use the Amiga's font management
   utilities, which are part of the KickStart based graphics library.  But
   very few folks just look at the graphics library.  Most everyone looks 
   at Intuition.  Intuition manages things like screens and windows, and
   you can write things to both screens and windows.  And, perhaps not
   coincidentally, both screens and windows have some idea of what font they
   should use, providing they've been given no special directions toward
   using some other fonts.  The screen stores a pointer to a critter called
   a "TextAttr", which is essentially a DESCRIPTOR for a font.  A TextAttr
   contains the name of the font, the point size of the font, and a little
   more information related to the font.  Both screens and windows also
   contains a graphics structure, called a RastPort, which points to the
   memory used to store the displayed memory.  The RastPort sports a pointer
   to a different critter, the "TextFont".  The is the actual font, which is
   the result of passing a TextAttr to one of the font opening functions.

   	In this version I allow you to change the screen's font information,
   like before, but I also know a bit more about the console window now.
   When I open a window within a screen, such as the WorkBench screen, 
   it's RastPort gets its default font from the screen's TextAttr font 
   descriptor.  If this window is going to be a console window, it will
   build a ConUnit, which has its own idea of the current font, based on
   the window's font when it is created.  I can use the ROM Kernal SetFont()
   call to change the font of the Window's RastPort.  Then I send an "<ESC>c"
   to the console which will reinitialize the ConUnit from the window (and
   also clear the screen....oh well).

	I need pointers to the window and screen to do this.  Since the 
   Window structure contains a pointer to its screen, my goal is to find
   the window associated with the calling console.  The best way I've found
   for this is by setting up the current console as an I/O item, then
   calling the DOS Info() function, which will return a pointer to the
   console's window.  From this window I can get a the screen pointer and
   the window's RastPort, so the fonts can be put in place.  When the
   Screen's TextAttr is changed, I only change the values of that font
   descriptor that the user's asked me to change.  Occasionally, this can
   result in a few bytes of unreclaimed memory, as in font names:  I have no
   idea how much memory is reserved in the screen's TextAttr structure on
   startup.  I use the SetFont() function to set the window's or screen's
   RastPort fonts.

	There are a few potential problems with my scheme.  First of all,
   many programs are written to support only the topaz 8 (80 column) font
   (sloppy, I know, but that's life).  If you're a 60 column user, you've 
   probably experienced this before.  It's not a problem with the Amiga as
   a whole, since most of the system will adjust itself.  But it may be a
   problem with programs that have a fixed idea of what a font should look
   like.  Most 80 column fonts work with most applications, and an 80 column
   8x8 font will work just about everywhere.  Some programs, like CLI for 
   instance, have trouble with proportionally-spaced fonts.  The best thing to
   to is try out the font you like.  One final problem is that some
   applications ask the WorkBench screen to close when they start up.  It'll
   close if there's nothing else open on it, but when it re-opens, it'll
   restart with the Preferences-selected font, not the SetFont selected font.
   Of course, preferences doesn't support arbitrary fonts (which is why this
   program is even necessary).  Oh well, maybe in 1.3?  The solution to this
   is to  always keep at least one window open, so that WorkBench will never 
   be closed on you.
*/

#include <exec/types.h>
#include <exec/io.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <graphics/gfxbase.h>
#include <graphics/text.h>
#include <graphics/rastport.h>
#include <libraries/diskfont.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/diskfont.h>
#include <intuition/intuitionbase.h>
#include <stdio.h>

/* ========================================================================= */
 
/* External things */

extern struct Library *OpenLibrary();
extern struct TextFont *OpenFont();
extern struct TextFont *OpenDiskFont();
extern struct Window *OpenWindow();

extern char *strcat();
extern char *strcpy();
extern long strlen();

/* ========================================================================= */

/* Global variables */

struct GfxBase *GfxBase = NULL;
struct Library *DiskfontBase = NULL;
struct IntuitionBase *IntuitionBase = NULL;

struct MsgPort portblk = { 
   {0L, 0L, NT_MSGPORT, 0, 0L}, 0, -1, 0L, 
   {&portblk.mp_MsgList.lh_Tail, 0L, &portblk.mp_MsgList.lh_Head, 0, 0} 
}; 
struct MsgPort *port = &portblk;

struct IOStdReq reqblk = { 
    {{0L, 0L, 0, 0, 0L}, &portblk, 0}, 0L, 0L, 0, 0, 0, 0L, 0L, 0L, 0L
}; 
struct IOStdReq *request = &reqblk;

struct Window *window = NULL;
struct Screen *screen = NULL;

/* ========================================================================= */ 

/* This section contains startup and shutdown functions. */

/* This function opens the required Amiga libraries, devices, and other
   good stuff.  It returns TRUE if successful.  Graphics, Intuition, and
   DiskFont libraries are opened. The console device is opened and the 
   I/O request message port is initialized.  I also do a check to make sure
   that the associated Task is really a Process.  This is absolutely 
   necessary, as some of the functions that I'll be using later are DOS
   functions, which require Processes, not Tasks.  */

BOOL OpenUp()
{
   /* The Libraries */

   if ((GfxBase = (struct GfxBase *) 
      OpenLibrary("graphics.library", 0L)) == NULL) return FALSE;
   if ((DiskfontBase = OpenLibrary("diskfont.library",0L)) == NULL) return FALSE;
   if ((IntuitionBase = (struct IntuitionBase *)
      OpenLibrary("intuition.library", 0L)) == NULL) return FALSE;

   /* The Devices */

   if ((OpenDevice("console.device", -1L, request, 0L)) != 0L) return FALSE;
   if ((port->mp_SigBit = AllocSignal(-1L)) < 0L) return FALSE;
   port->mp_SigTask = (struct Task *) FindTask((char *) NULL); 
   if (port->mp_SigTask->tc_Node.ln_Type != NT_PROCESS) return FALSE;
   return TRUE;
}

/* This function frees up dynamically opened things, like the devices
   and the libraries, opened above. */

void ShutDown()
{ 
   if (request->io_Device != NULL) { 
      if (port->mp_SigBit != -1) FreeSignal(port->mp_SigBit); 
      CloseDevice(request); 
   } 
   if (IntuitionBase != NULL) CloseLibrary(IntuitionBase); 
   if (DiskfontBase != NULL) CloseLibrary(DiskfontBase);
   if (GfxBase != NULL) CloseLibrary(GfxBase);
} 

/* ========================================================================= */

/* Attribute based functions. */

/* This function allocates space for the given font descriptor, converting
   the point size into a numeric form at the same time. */

struct TextAttr *AllocAttr(fontname, pointname)
char *fontname, *pointname;
{
   short point;
   char *str;
   struct TextAttr *attr = NULL;

   attr = (struct TextAttr *) AllocMem(sizeof(*attr),0L);

   if (pointname == NULL) point = 8;
   else point = atoi(pointname);

   str = (char *) AllocMem(strlen(fontname)+6L,0L);
   strcpy(str,fontname);
   strcat(str,".font");
   attr->ta_Name = str;
   attr->ta_YSize = point;
   attr->ta_Style = 0;
   attr->ta_Flags = 0;
   return attr;
}

/* This function frees a TextAttr allocated with AllocAttr(). */

void FreeAttr(attr)
struct TextAttr *attr;
{
   if (attr == NULL) return;
   FreeMem(attr->ta_Name, strlen(attr->ta_Name));
   FreeMem(attr,sizeof(*attr));
}
/* ========================================================================= */

/* This function allocates a packet and associated info block.  It returns
   a pointer to this packet if successful, NULL otherwise. */

struct StandardPacket *AllocInfoPacket()
{
   struct StandardPacket *pac = NULL;
   struct InfoData *inf = NULL;

   pac = (struct StandardPacket *) AllocMem(sizeof(*pac), MEMF_CLEAR);
   if (pac == NULL) return NULL;
   inf = (struct InfoData *) AllocMem(sizeof(*inf), MEMF_CLEAR);
   if (inf == NULL) {
      FreeMem(pac,sizeof(*pac));
      return NULL;
   }
   pac->sp_Msg.mn_Node.ln_Name = (char *) &(pac->sp_Pkt); 
   pac->sp_Pkt.dp_Link = &(pac->sp_Msg); 
   pac->sp_Pkt.dp_BufAddr = ((ULONG) inf) >> 2; 
   pac->sp_Pkt.dp_Type = ACTION_DISK_INFO; 

   return pac;
}

/* ========================================================================= */

/* This function gets the Window pointer for the current console.  It 
   takes the message port for the console.  It uses the fact that the I/O
   device's message port points to the process associated with this CLI.  
   This process needs a console window, and it should have a pointer to a
   message port for this purpose.  We use DOS packets to request an
   InfoData block on this console window.  The window pointer is passed 
   back as the volume name of the console.  We're getting into DOS, so look
   out for BCPL pointers and longword alignment! */

struct Window *GetConsoleWindow(port)
struct MsgPort *port;
{
   struct MsgPort *cp; 			/* Console port */
   struct InfoData *inf;		/* Packet data block */
   struct StandardPacket *pac = NULL;   /* Packet for DOS request */
   struct Window *w = NULL;		/* Resulting Window */

   cp = (struct MsgPort *)((struct Process *)port->mp_SigTask)->pr_ConsoleTask; 
   if (cp == 0L) return NULL;

   if ((pac = AllocInfoPacket()) == NULL) return NULL;
   pac->sp_Pkt.dp_Port = port; 
   inf = (struct InfoData *) (((ULONG) pac->sp_Pkt.dp_BufAddr) << 2);
   PutMsg(cp, pac); 
   WaitPort(port); 
   w = (struct Window *) inf->id_VolumeNode;
   FreeMem(inf, sizeof(*inf));
   FreeMem(pac, sizeof(*pac));

   return w;
}

/* ========================================================================= */

/* This function tries to get a requested font from an attribute descriptor;
   it returns FALSE if the font doesn't exit. */

struct TextFont *GetFont(attr)
struct TextAttr *attr;
{
   struct TextFont *font;
   
   if ((font = OpenDiskFont(attr)) != NULL) return font;
   if ((font = OpenFont(attr)) != NULL) return font;
   return NULL;
}

/* ========================================================================= */

/* The main function */
 
main(argc, argv) 
int argc; 
char *argv[]; 
{ 
   short i;
   BOOL printmode = FALSE;
   BOOL mode_screen = FALSE;
   BOOL mode_title = FALSE;
   BOOL mode_window = FALSE;
   struct TextAttr *attr = NULL;
   struct TextFont *newfont, *oldfont;

   /* Automatic help command */

   if (argc == 1) printmode = TRUE;
   else if (argv[1][0] == '?') {
      printf("SetFont 2.0 by Dave Haynie\n\n");
      printf("Usage: SetFont [fontname [point [place]]], where\n\n");
      printf("  \2331mfontname\2330m  is the font's name (e.g. \"topaz\")\n");
      printf("  \2331mpoint\2330m     is the point size (default is 8)\n");
      printf("  \2331mplace\2330m     pick the place, one or more of:\n");
      printf("    \2331mSCREEN\2330m    set the screen font only\n");
      printf("    \2331mTITLES\2330m    set the screen titles only\n");
      printf("    \2331mWINDOW\2330m    set the window's font only\n\n");
      printf("If no \2331mplace\2330m switch is given, everything is set.\n");
      Exit(10);
   }

   /* Process the command-line arguments, AmigaDOS style. */

   for (i=3; i<argc; i++)
      if (argv[i][0] == 'T' || argv[i][0] == 't')      mode_title = TRUE;
      else if (argv[i][0] == 'S' || argv[i][0] == 's') mode_screen = TRUE;
      else if (argv[i][0] == 'W' || argv[i][0] == 'w') mode_window = TRUE;
   if (!mode_title && !mode_screen && !mode_window)
      mode_title = mode_screen = mode_window = TRUE;
   
   /* First all, we've got to open all the good stuff. */

   if (!OpenUp() || (window=GetConsoleWindow(port)) == NULL) {
      ShutDown();
      Exit(10);
   }

   /* Process the informational print mode, real simple */

   if (printmode) {
      printf("SetFont 2.0 by Dave Haynie\n\n");
      attr = window->WScreen->Font;
      printf("Screen Font: %s (%ld) ", attr->ta_Name, attr->ta_YSize);
      printf("Style %ld, Flags %ld\n", attr->ta_Style, attr->ta_Flags);
      ShutDown();
      Exit(0);
   }

   /* Get the attribute and font, where available. */

   attr = AllocAttr(argv[1], argv[2]);
   if ((newfont = GetFont(attr)) == NULL) {
      printf("Error: Font not found\n");
      FreeAttr(attr);
      ShutDown(); 
      Exit(10);
   }

   /* Here's where the various options are processed individually. */

   /* The screen titling  font is changed via a SetFont() call to the 
      screen's RastPort.  */

   if (mode_title) {
      oldfont = window->WScreen->RastPort.Font;
      if (SetFont(&window->WScreen->RastPort,newfont) == 0) CloseFont(oldfont);
   }

   /* If the current font is the same font, we can just change the point
      size attribute value.  If its a completely new font, the entire
      attribute pointer is reassigned.  This may not be necessary, but it
      works this way, and I've had trouble with other schemes.  */

   if (mode_screen) {
      if (strcmp(window->WScreen->Font->ta_Name,attr->ta_Name) == 0) {
         window->WScreen->Font->ta_YSize = attr->ta_YSize;
         FreeAttr(attr);
      } else
         window->WScreen->Font = attr;
   }

   /* The window font is changed via a SetFont() call to the window's
      RastPort, much in the same way that the screen's titling fonts are
      changed.  Additionally, the console device must be reset to force
      the ConUnit to be rebuit, which draws the new font from the
      window.  */

   if (mode_window) {
      oldfont = window->RPort->Font;
      if (SetFont(window->RPort,newfont) != 0L) CloseFont(oldfont);
      printf("\033c");
   }
   CloseFont(newfont);
   ShutDown();
   Exit(0);
}
-- 
Dave Haynie     Commodore-Amiga    Usenet: {ihnp4|caip|rutgers}!cbmvax!daveh
"The A2000 Guy"                    BIX   : hazy
	"These are the days of miracle and wonder" -P. Simon