TweetFollow Us on Twitter

Format Driver
Volume Number:4
Issue Number:1
Column Tag:The Visiting Developer

Format Drivers Bring Freedom
of Revison

By David Dunham, James Hopper, Goleta, CA

Introduction

Data interchange is a real problem. Everyone likes to save information in their own format, and no one wants to deal with anyone else’s format (especially not to write it!). Programs like the original version of Acta, David’s desk accessory outline processor, support only limited file format interchange, usually via a separate translator program. Such translation programs are very inconvenient, but there are reasons for writing them. First, the extra code required to read additional file formats can significantly enlarge a program while adding support for formats which most users never need. It’s better to separate this code from the more commonly-used parts of the program. Also, the intermediate file is necessary because many programs don’t use the public clipboard (desk scrap) for formatted text. Second, many of these formats are considered proprietary and are not generally available, for example Microsoft Word 1.05. Finally, there are a lot of file formats out there -- most of us are simply too lazy to cover all of them.

Many people work with a variety of editors, word processors, and desk accessories, and need the ability to exchange files between them. Separate translation programs such as the one used with WriteNow are simply too inconvenient. Macintosh users have grown accustomed to (and deserve) a higher degree of integration between programs. One way this can be done is to ‘teach’ the program what the data must look like by having the program look-up the format externally to itself. In this way, the program does not have to be revised to read or write a new format whenever someone releases a hot new product. In this article, we will discuss an example of such a technique used in Acta 2.0, that can be applied to other developer’s software projects to ease the burden of being compatible. [It is hoped this article will spur someone to write a similar article on how Pagemaker compatibility can be achieved by new word processors without having to wait for Aldus to release a new Pagemaker version. Wouldn’t it be neat if the word processor or graphics developer could make his product Pagemaker compatible without Aldus having to do anything? -Ed]

Acta 2.0 handles additional data formats with format drivers, files which, like printer drivers, reside in the system folder. Because they’re separate from the main program, users can install only the formats required by dragging these driver files to the system folder. As new word processors come out, the new file formats can be supported with new format drivers, rather than a new version of Acta. Given the file format documentation for the new word processing program, drivers to support new formats can be produced by third party programmers such as Jim.

David will first explain format drivers using C as the target language. Jim will then discuss a simple input format driver in Pascal, focusing on the glue required to link functions written in C to units written in Pascal. Although this article deals with format drivers as implemented in Acta, the concept of format drivers can be adopted in most programs.

Additional technical information on Acta can be found in a short document describing the format of Acta files, available from online services such as Delphi or CompuServe, or from Symmetry Corp. For convenience, it is included at the end of this article. This information would be useful if you’re implementing your own Acta format driver, but you won’t need it to read this article.

Input Drivers

Acta supports two kinds of format drivers: input and output. A file can contain resources for both, but we’ll start by looking at input drivers. An input driver’s job is to read a file, parse the data, and pass it to Acta as topics (Acta’s smallest data object). Acta handles everything else, which keeps down the overhead in each format driver. We’ll use the input driver a_TEXT as an example format driver. This example was written in C and compiled using MPW C. This driver reads text files, splitting the text into topics at return characters.

The driver a_TEXT is composed of a variety of resources stored in the resource fork of the file a_TEXT. The file type for a format driver is ‘ACTF,’ which is how Acta recognizes it as a format driver. The file creator ‘Atxt’ associates a unique icon with the driver file in the Finder using the usual additional (bundle) resources. We discuss other resources in the following paragraphs.

ACTf -- The code for the driver is stored as a resource of type ACTf. It’s actually a standard code resource which Acta loads using GetResource.

ICN# -- An input driver file must contain an ICN# 64 resource which, along with the file name, identifies a particular format driver. The user selects an input driver from a scrolling icon list (see Appendix) as part of Acta’s Open dialog (see illustration below). To avoid confusion with application names, format driver files start with ‘a_’ by convention; however, this prefix is not displayed in the scrolling icon list.

FILT -- If a resource FILT 64 is present, it’s assumed to be a code resource containing a file filter function, defined as in SFGetFile:

pascal short fileFilter(pb) ParmBlkPtr pb;

The driver author provides this filter so Acta can open multiple file types using the format driver.

TYPE -- If there is no FILT 64, Acta determines which files to open by examining the input driver’s resource TYPE 64, which is four ASCII characters and specifies a single file type to be converted.

Once the user decides to open a file, Acta calls the driver, assuming it’s a C routine defined as is read_text() in Listing 1.


{1}
/*
Read TEXT documents into Acta
Written by David Dunham
©1987 Maitreya Design -- code may be freely used 
in Acta format drivers 
*/

typedef int word;/* Synonyms for VAX-hackers */
typedef unsigned charbyte;

#define TAB ‘\t’
#define CR‘\r’

#define NIL (0L)
#define TRUE(-1)
#define FALSE    0

#define TEXTtype 1
#define PICTtype 2

struct BLOCK_ATT { /* Attribute portion of a topic */
 word bType;/* TEXTtype, PICTtype,   */
 word bAttributes; /* 0 */
 word bFont;
 word bStyle;
 word bSize;
 byte bColor;    /*Triangle color: index to COL#               
 resource, 0 = black */
 byte filler;    /* 0 */
 byte firstLine; /* Show first line only? 0xFF or 0 */
 byte expanded;  /* Subtopics visible? 0xFF or 0 */
 word hidden;    /* Is block hidden (by Collapse)? count       
 of hide depth */
};
typedef struct BLOCK_ATT  BLOCK_ATT;

/*
READ_TEXT -- Read generic TEXT file to Acta outline
 infil  : refNum of input file
 resfil : refNum of input file’s resource fork
 att    : default topic attributes
 add_topic: pointer to routine to call
*/
OSErr read_text(infil,resfil,att,add_topic)
word infil, resfil; BLOCK_ATT *att; ProcPtr add_topic; {
 register char   **text;  /* Handle for  text */
 register OSErr  error;
 long   count;   /* Used in FS calls */
 register word   level;   /* Topic’s level */
 word i_count, last_count, spaces;
 word last_level;/* Level of previous topic */
 word space_indent;/* No. of spaces = to a tab */
 char c;
 long size; /* Length of text */
 Handle h;
 word oldResFile;

 oldResFile = CurResFile(); /* Previous top resource file */
 UseResFile(resfil); /* Just look in document */
 h = GetIndResource(‘EFNT’,1);
 /* MDS Edit/QUED/miniWRITER font */
 if (h != NIL) { /* There is such a resource */
 att->bSize = **(word **)h;
 GetFNum(*(char **)h + 2,&att->bFont);
 }
 UseResFile(oldResFile);  /* Restore search order */
 level = -1;/* We’ve never parsed a topic */
 last_level = 0;
 space_indent = 5; /* SHOULD be rsrc; 5 for MORE 1.0 */
 last_count = i_count = spaces = 0;
 text = NIL;
 att->bType = TEXTtype; /* Everything is text! */
 while (TRUE) {  /* Keep going until EOF */
 /* Will this die with empty file? */
 count = 1L;/* Read a single character */
 error = FSRead(infil,&count,&c);
 if (error) {    /* Probably hit EOF before RETURN */
 if (error == eofErr) return(noErr); 
 /* Finished with the file */
 return(error);  /* Uh-oh  */
 }
 if (c == TAB) {
 i_count++; /* Increment indent count */
 spaces = 0;/* Start count over */
 } else if (c == ‘ ‘) {
 spaces++;
 if (spaces == space_indent) {/* A pseudo-tab */
 i_count++; /* Increment indent count */
 spaces = 0;/* Start count over */
 }
 } else { /* Not space or tab */
 if (level == -1)/* Very first topic */
 level = 1;
   else
 level = level + i_count - last_count;
 if (level < 1)
 level = 1; /* Minimum indent */
 if (level > last_level + 1)
 level = last_level + 1;   /*go one level deeper */
 if (level == 1) { /* Left-margin topic? */
 att->expanded = TRUE;
 att->hidden = 0;
     } else {    /* Subtopics aren’t expanded */
 att->expanded = FALSE;
 att->hidden = level - 2; 
 /* # of collapsed levels above */
 }
 text = NIL;/* No text so far */
 /* Read until end of line or end of file */
 while (c != CR && error == noErr) {
 if (text == NIL) {/* First character? */
 text = NewHandle(1L);
 **text = c;/* Copy it in */
 } else
 PtrAndHand(&c,text,1L);  /* Add to the end */
 count = 1L;/* Read a single character */
 error = FSRead(infil,&count,&c);
 }
 if (error != noErr && error != eofErr)
 return(error);  /* Uh-oh  */
 if (text == NIL) {/* Empty topic */
 (*add_topic)(NIL,0L,level,att,NIL);
   } else {
 size = GetHandleSize(text);
 HLock(text);    /* Keep dereference valid */
 (*add_topic)(*text,size,level,att,NIL);
 DisposHandle(text); /* Free the memory */
 text = NIL;/* Mark it as empty */
 }
 if (error == eofErr) {
 return(noErr);  /* Finished with the file */
 }
 last_count = i_count;  /* input file’s indent */
 i_count = spaces = 0;
 last_level = level; /* Remember topic’s level */
 } /* end if */
 } /* end while */
}

«listing 1»

The actual code in our example is straightforward. First, it looks in the resource fork for the EFNT resource (where MDS Edit, QUED, and miniWRITER save a text file’s font and size). Then it reads the data fork a character at a time, appending them to the end of a block of memory until it reaches either the end of the file or a return character. When this happens, it calls add_topic, a routine in one of Acta’s code resources. If there are any errors, it just returns the error to Acta, which takes care of informing the user. A driver can display its own error messages, in which case it returns -2048.

Add_topic inserts the data into Acta’s family tree structure. It’s declared as:

{2}
/*
ADD_TOPIC -- Create a new topic and link it in
 data   : topic data
 len    : length of data
 level  : level of topic (1 = left margin)
 attributes : topic’s attributes
 styleHandle   : RESERVED
*/
void add_topic(data,length,level,attributes,styleHandle)
Ptr data; long length; word level; BLOCK *attributes; Handle styleHandle;

The code is compiled into the resource ACTf 64:

{3}
c a_TEXT.c
link -rt ACTf=64 -sn Main=a_TEXT -c Atxt -t ACTF 
 a_TEXT.c.o -o d20:MPW:a_TEXT 
 “{Libraries}”Interface.o 
 “{CLibraries}”CInterface.o

Listing 2 summarizes the resources which make up an input driver, in REZ format:

{4}
resource ‘BNDL’ (128) {
 ‘Atxt’,
 0,
 { /* array TypeArray: 2 elements */
 ‘ICN#’,
 { /* array IDArray: 1 elements */
 0, 128
 };
 ‘FREF’,
 { /* array IDArray: 1 elements */
 0, 128
 }
 }
};

resource ‘FREF’ (128, “a_TEXT”) {
 ‘ACTF’,
 0,
 “”
};

data ‘Atxt’ (0, purgeable) {
 “©1987 Maitreya Design 23 January 1987”
};

data ‘TYPE’ (64, purgeable) {
 ‘TEXT’
};

and the ICN#

«listing 2 -- Rez input»

Output Drivers

An output driver is responsible for taking the data from an Acta topic and writing it to a file. Again, Acta handles most of the overhead, but the process is slightly more complicated, because most word processor formats involve tables and pointers. Acta calls the output driver before passing any topic data, once for each topic, and again after all the data. The driver generally builds any tables during the first and last call. For example, a word processor format may begin with a list of paragraphs (conveniently written during the first call) and end with a pointer to free space (best written after all the topics have been output).

The example output driver is a_RTF, which converts an outline to Rich Text Format, Microsoft’s interchange format. It creates a file which is as true as possible to the screen appearance of the Acta outline. (The RTF specification is available from Microsoft. It’s beyond the scope of this article to explain in detail, but it’s a format for representing formatting information using only the standard 7-bit printing ASCII characters. Backslashes precede formatting words, and braces group items. This format is also used for Microsoft’s MSDOS word processor.)

ACTf -- The actual code for the output driver is in the ACTf 0 resource and is loaded by Acta using GetResource.

ICN# -- An output driver file must contain an ICN# 0 resource which, along with the file name, identifies the format driver to the user. The user selects an output driver from a scrolling icon list (see Appendix) as part of Acta’s Save as dialog (see illustration below). When the user clicks on this icon, Acta highlights the ruler and label buttons depending on the RULR 0 resource.

RULR -- This is a word, in which the following bits have meaning:

0x8000 format has rulers

0x4000 format doesn’t use labels

As usual, don’t set unused bits.

TYPE and CREA -- The resources TYPE 0 and CREA 0 are OSTypes, 4 ASCII characters Acta uses to set the output file’s type and creator.

Acta passes a pointer to a parameter block which indicates the topic’s level, data, attributes, label, and other information; this is defined in Listing 3.

When the example driver gets the -1 message (indicating the initial call), it writes the RTF header, including the name of every installed font. When it gets the 0 message (indicating a topic’s data), it outputs the topic’s style, ruler, label, and data. If it encounters an error, it must return the error (or -2048 if it doesn’t want Acta to put up an error dialog). Since a code resource can’t have globals, it sets already_error in the parameter block. A_RTF ignores the +1 message, which follows all the 0 messages.


{5}
/*
Outputs Acta outline as Rich Text Format (Word 3.x) document
Written by David Dunham
©1987 Maitreya Design -- code may be freely used 
in Acta format drivers 
*/

typedef int word;/* Synonyms for VAX-hackers */
typedef unsigned charbyte;

#define TAB ‘\t’
#define CR‘\r’

#define NIL (0L)
#define TRUE(-1)
#define FALSE    0

/* Label types */
#define NONE10
#define DECIMAL  11
#define ROMAN    12
#define BULLETS  14

/* Topic types */
#define TEXTtype 1
#define PICTtype 2

struct BLOCK_ATT { /* Attribute portion of a topic */
 word bType;/* TEXTtype, PICTtype,   */
 word bAttributes; /* 0 */
 word bFont;
 word bStyle;
 word bSize;
 byte bColor;    /* Triangle color: index to COL#              
 resource, 0 = black */
 byte filler;    /* 0 */
 byte firstLine; /* Show first line only? 0xFF or 0 */
 byte expanded;  /* Subtopics visible? 0xFF or 0 */
 word hidden;    /* Is block hidden (by Collapse)? count       
 of hide depth */
};
typedef struct BLOCK_ATT  BLOCK_ATT;

struct OUT_PARMS {
 word   fRef;    /* Data fork refNum */
 word   rRef;    /* Resource fork refNum */
 word   message;
 word   level;
 word   n;/* Topic’s absolute number */
 char   *data;
 long   len;/* Length of data */
 BLOCK_ATT*att;
 THPrintprint_rec;
 word   already_error;
 char   *label;
 word   labelType;
 word   *raw_label;
 word   rulers;
 word   error_reporting;  /* not used */
 Handle reserved;/* Currently NIL */
 word   last_level;
 word   private;
};
typedef struct OUT_PARMS  *P_PTR;

/* Function prototypes */
void    file_error();
void    warp();
void    w_byte();
void    w_str();
OSErr   write_RTF();

/*
WRITE_RTF -- Write Rich Text Format document
*/
OSErr write_RTF(p) register P_PTR p; {
 char   *s;
 long   count;
 OSErr  error;
 Handle handle;
 register word i;
 word   id, last_space, rsrc_fonts;
 Str255 name, string;
 WindowPtrfront;

 switch (p->message) {
   case -1:
 w_str(p,”\P{\\rtf\\mac”);
 /* Output font table (every installed font) */
 w_str(p,”\P{\\fonttbl”);
 rsrc_fonts = CountResources(‘FONT’);
 for (i = 1; i <= rsrc_fonts; i++) {
 SetResLoad(FALSE);/* Don’t actually load fonts! */
 handle = GetIndResource(‘FONT’,i);
 GetResInfo(handle,&id,&count,&name);
 SetResLoad(TRUE); /* Back to normal */
 if (name.length != 0) {
 w_str(p,”\P\\f”);
 NumToString((long)id >> 7,&string);
 w_str(p,&string);
 w_byte(p,’ ‘);
 w_str(p,&name);
 w_byte(p,’;’);
 }
 }
 w_str(p,”\P}\r”);
 break;
   case 0:
 w_str(p,”\P{\\sl0\\f”);
 NumToString((long)p->att->bFont,&string);
 w_str(p,&string);
 w_str(p,”\P\\fs”);
 NumToString((long)p->att->bSize << 1,&string);
 w_str(p,&string);
 if (p->att->bStyle & bold) {
 w_str(p,”\P\\b”);
 }
 if (p->att->bStyle & italic) {
 w_str(p,”\P\\i”);
 }
 if (p->att->bStyle & underline) {
 w_str(p,”\P\\ulw”);
 }
 if (p->att->bStyle & outline) {
 w_str(p,”\P\\outl”);
 }
 /* Output ruler stuff */
 if (p->level > 1 && p->labelType == DECIMAL)
 i = 48;
   else
 i = 32;
 if (p->labelType == BULLETS) i = 16;
 if (p->labelType == NONE) i = 0;
 /* Output first indent and left indent in twips (1/20 point) */
 w_str(p,”\P\\li”);
 NumToString((long)(p->level * 16 + i) * 20,&string);
 w_str(p,&string);
 w_str(p,”\P\\fi”);
 NumToString((long)i * -20,&string);
 w_str(p,&string);
 w_byte(p,’ ‘);  /* Be sure not to run into text */
 /* Output label */
 if ((p->label)[0] != 0) {
 for (i = 1; i <= p->label[0]; i++)
 w_byte(p,p->label[i]);
 }
 if (p->att->bType == PICTtype) {
 w_str(p,”\P{\\pict\\macpict\\bin”);
 NumToString((long)p->len,&string);
 w_str(p,&string);
 w_byte(p,’ ‘);
 count = p->len;
 error = FSWrite(p->fRef,&count,p->data);
 w_byte(p,’}’);
   } else { /* Not a PICT */
 warp(p,p->len,p->data);
 }
 w_str(p,”\P\\par}\r”);
 break;
   case 1:
 w_str(p,”\P}\r”);
 break;
 }
 return(p->already_error);
}

/*
FILE_ERROR - Display the appropriate message for a file error
*/
void file_error(error,p) register OSErr error; P_PTR p; {

 if (p->already_error != noErr) return;/* Only once */
 p->already_error = error;/* Indicate error */
}

/*
WARP -- Escape {,\,},CR,TAB
*/
void warp(p,len,s) P_PTR p; long len; register char *s; {
 long   count;
 register word i;
 register char c;
 
 if (p->already_error) return;/* Already have error */
 for (i = 0; i < len; i++) {
 c = *s++;
 switch (c) {
   case ‘{‘:
   case ‘}’:
   case ‘\\’:
 w_byte(p,’\\’); w_byte(p,c);
 break;
   case CR:
 w_str(p,”\P\\line “);
 break;
   case TAB:
 w_str(p,”\P\\tab “);
 break;
   default:
 w_byte(p,c);
 }
 }
}

/*
W_BYTE
*/
void w_byte(p,b) P_PTR p; unsigned char b; {
 long   count;
 register OSErr  error;
 
 if (p->already_error) return;/* Already have error */
 count = sizeof(char);
 error = FSWrite(p->fRef,&count,&b);
 if (error) {
 file_error(error,p);
 return;
 }
}

/*
W_STR -- Write a Pascal string
*/
void w_str(p,s) P_PTR p; char s[]; {
 long   count;
 register OSErr  error;

 if (p->already_error) return;/* Already have error */
 count = s[0];
 error = FSWrite(p->fRef,&count,&s[1]);
 if (error) {
 file_error(error,p);
 return;
 }
}

«listing 3»

Listing 4 summarizes the additional resources which make up an output driver.

{6}
resource ‘BNDL’ (128) {
 ‘Artf’,
 0,
 { /* array TypeArray: 2 elements */
 /* [1] */
 ‘ICN#’,
 { /* array IDArray: 1 elements */
 /* [1] */
 0, 128
 };
 /* [2] */
 ‘FREF’,
 { /* array IDArray: 1 elements */
 /* [1] */
 0, 128
 }
 }
};

resource ‘FREF’ (128, “a_TEXT”) {
 ‘ACTF’,
 0,
 “”
};

data ‘TYPE’ (0, purgeable) {
 ‘TEXT’
};

data ‘CREA’ (0, purgeable) {
 ‘MSWD’
};

data ‘RULR’ (0, purgeable) {
 $”0000"
};

data ‘Artf’ (0, purgeable) {/* Signature */
 “©1987 Maitreya Design 11 March 1987”
};

and the ICN#s

«listing 4 -- Rez input»

Doing it with Pascal

It’s a little known fact that Acta makes a superior Scrapbook. The example in this section is a relatively simple input format driver (in Lightspeed Pascal [LSP]) which reads a scrapbook file into an Acta outline, converting each text block and picture in the scrapbook to a separate topic. With this driver, those of you with a lot invested in scrapbooks can painlessly convert to Acta.

Unfortunately (for Pascal programmers) Acta assumes its format drivers are C functions. This is because Acta is written in C, and because David takes a perverse pleasure in making Pascal programmers read C declarations. (The authors of Inside Macintosh no doubt have a similar feeling.) But some Pascal compilers won’t generate code that can call or be called by code written in C. This section will discuss a relatively general method of coercing a standard Pascal compiler to generate code compatible with C. Note that MPW Pascal allows you to declare procedures as C functions so they can talk directly to Acta.

Linking Code Produced by Different Compilers

Except for a few systems like MPW, it’s not possible to link code compiled by different compilers. On the Macintosh, one way to overcome this is to compile code into code resources. These can then be loaded using the segment loader, or in the case of a DA such as Acta, with GetResource. Using this method it’s possible to separately compile and link blocks of code totally independent of which compiler generated them, using virtually any compiler desired by the programmer.

Argument Order

In both C (at least Aztec C, in which Acta is written; MPW C, used in the examples; and LightspeedC, another compiler we’re familiar with) and Pascal, arguments are passed to procedures on the stack. However the order in which the arguments are pushed onto the stack is different for the two languages. Pascal pushes arguments onto the stack in the order in which they are declared. C pushes the arguments in the reverse order of declaration. (Note that this allows C routines to have a variable number of arguments; the first argument is always in a known place [the top of the stack] and can indicate how many other arguments there are.)

If a Pascal procedure is to be callable from C, it must declare its arguments reversed from the C function call, for example:

The C declaration for the format driver main procedure read_Scrap is:

OSErr read_Scrap(infil,resfil,att,add_topic)
word infil, resfil; BLOCK_ATT *att; ProcPtr add_topic;

while the Pascal declaration is:

Function read_Scrap(add_topic:ProcPtr; att:Block_Att; resfil,infil:Integer) 
: OSErr;

When calling a C function from Pascal the arguments must be passed in the reverse order from the C function declaration, for example:

If the C declaration for add_topic is:

void add_topic(data,length,level,attributes,styleHandle)
Ptr data; long length; word level; BLOCK *attributes; Handle styleHandle;

the Pascal call to the function add_topic is:

add_topic(Nil,att,level,size,^text);

while the C call is:

(*add_topic)(*text,size,level,att,NIL);

Argument Passing

C allows a variable number of arguments to be passed to a function, so the function can’t simply remove a fixed number of bytes from the stack before returning as is done in Pascal. When a C function calls a Pascal procedure (Acta calls the a_Scrap main procedure) this creates problems as both the Pascal procedure and the calling C function will remove the arguments from the stack. The converse problem occurs when a Pascal procedure calls a C function (a_Scrap calls add_topic) -- no one removes the arguments from the stack.

For some Macintosh Pascal compilers (Lightspeed and TML) this problem can only be solved by resorting to assembly language “glue” routines. Those of you who don’t care for assembly code please just hold your nose and read on. The result of this discussion is two small, relatively general assembly procedures. One procedure lets Acta call an input format driver written in Pascal and the other allows a Pascal driver to call Acta’s add_topic procedure. You won’t have to write any assembly language to add your own input format driver (well, as long as the format driver interface doesn’t change anyway), though output format drivers are left as an exercise for the reader (or a future column)!

Calling a Pascal procedure from C involves declaring the Pascal procedure as having no arguments, and copying the arguments from the stack into local variables using an assembly procedure (Listing 5). By doing this Pascal will not mess with the stack by deleting the arguments. This is one of those areas where languages like C have it all over Pascal, which was designed to keep the programmer as far from the machine as possible. This promotes machine independence, but like most blessings extracts a price. When you absolutely must get down to the bare metal of the machine, you’re forced to use assembly language to do it.

;Procedure GetArguments( VAR add_topic_Ptr : ProcPtr;
;VAR att : Topic_Attributes;
;VAR ResRefNum : Integer;
;VAR Refnum : Integer);

This procedure will pull the arguments passed to input routines from the stack and put them in the input routines internal variables. This procedure will not change the stack from what C expects on return from the input routine.

This procedure is called as the first statement in the input module. it assumes that A6 was linked at the beginning of the code module. this occurs if there are any local variables at the top level of the code module main entry point routine.

{7}
;
;Stack looks like this:
;------------------------------------------------
;input parameters to input module from Acta
;------------------------------------------------
;Return Address
;------------------------------------------------
;A6===> Old A6
;------------------------------------------------
;arguments for GetArguments
;------------------------------------------------
;GetArguments calling procedure return address
;------------------------------------------------
;A0===> Old A0     <==== SP
;------------------------------------------------
;
;
 link   A0,#0    ; A0 points to getarguments stack frame
 move.L A2,-(SP) ; save A2
 move.L 8(A0),A1 ; A1 = ptr to RefNum
 move.W 8(A6),(A1) ; move refnum from c frame to pascal frame
 move.L 12(A0),A1; A1 = Ptr to ResRefNum
 move.W 10(A6),(A1); move ResRefNum from c to pascal frame
 move.L 16(A0),A1; A1 = Ptr to ATT
 move.L 12(A6),A2; A2 = ptr to C ATT
 move.L (A2)+,(A1)+; move C ATT to pascal ATT
 move.L (A2)+,(A1)+
 move.L (A2)+,(A1)+
 move.L (A2)+,(A1)+
 move.L 20(A0),A1; A1 = ptr to Pascal Procptr
 move.L 16(A6),(A1); move procptr from c to pascal
 move.L (SP)+,A2 ; restore A2
 unlk   A0
 rts

«listing 5»

Function Results

Pascal allocates space on the stack for the functional result and places the arguments on the stack. When the function returns the result is on top of the stack. A function written in C returns the functional value in register D0. Acta (a C program) calls input format drivers as functions. A format driver written in Pascal must conform to this standard for passing functional results.

A Pascal format driver must be defined as a procedure so it doesn’t try to write its function result to the stack. An assembly language procedure is called as the driver’s last statement to load the functional result into register D0 (Listing 6).

;Procedure CFunctionReturn(Error:OsErr);
;

This procedure is a kludge to allow C and Pascal routines to work together Acta calls the a_Text Main routine and expects its functional value to be in the register D0, however Pascal expects there to be space reserved on the stack for it, and would save it there if allowed. This procedure takes an argument which would normally be set equal to the functional result and puts it in D0. The calling procedure will then not set the function equal to this value.

 move.L (SP)+,A0 ; Get Return address 
 move.W (SP)+,D0 ; put functional result in D0 for C
 jmp    (A0); RTS

«listing 6»

Calling a Procedure Passed as an Argument

Acta passes the address of add_topic as an argument to the input module. Unfortunately Pascal can’t call a procedure by pointer directly. The solution, once again, is an assembly glue routine to take this procedure address as an argument and jump to the procedure (Listing 7).

; add_topic(text: Ptr;size: LongInt;level:Integer;
;att:Topic_Attributes; add_topic_Ptr:ProcPtr);
;

This procedure is the glue routine between pascal call to add_topic and Acta’s add_topic routine. Pascal can not call a procedure by using the procptr so a ASM glue routine is required. In addition C requires the calling routine to take its own arguments off the stack. The procptr was added to the add_topic argument list on the end so that this routine could easily pull it off and be left with the arguments on the stack as C wants to see them. This procedure must then remove the arguments off the stack as the C procedure will not do it.

{8}
;
add_topic
 lea    SaveReturn,A0
 move.L (SP)+,(A0) ; save return
 move.L (SP)+,A0 ; get subroutine address 
 jsr    (A0); JSR to C add_topic routine
 lea    18(SP),SP; pull arguments off stack
 lea    SaveReturn,A0; restore return address
 move.L (A0),-(SP)
 rts
SaveReturnDC.L 0 ; storage for return address

«listing 7»

Putting it All Together in Pascal

Listing 8 is a shell which can be called from Acta. A call to the user’s input driver is inserted in this shell. The user’s format driver code is stored in a separate unit and a uses clause which references it is added to the shell replacing the Scrapbook Uses. The assembly language procedures are contained in a file called ASM LIB Library which is compiled using Consulair’s Assembly Development System (formerly MDS) and converted into an LSP library using REL Convert. This library must be added to the project in build order before the format driver code (see figure). See the LSP manual for more information on how to build a code resource.


{9}
UNIT InputDriverShell;

INTERFACE
 USES
 Globals, Scrapbook;

   { main entry point for the code unit }

PROCEDURE Main;

IMPLEMENTATION

PROCEDURE Main;

 VAR
 anErr  : OsErr;
 add_topic_Ptr   : ProcPtr;
 att    : Topic_Attributes;
 ResRefNum  : Integer;
 Refnum : Integer;

  { def for ASM C/Pascal glue for Acta input format driver }
 PROCEDURE CFunctionReturn (anErr : OsErr); external;
 PROCEDURE GetArguments (VAR add_topic_Ptr : ProcPtr;
 VAR att : Topic_Attributes;
 VAR ResRefNum : Integer;
 VAR Refnum : Integer); External;

 BEGIN

 { get the funtion arguments from the stack, 
 behind compiler’s back }
 GetArguments(add_topic_Ptr, att, ResRefNum, Refnum);

 { call the specific input format driver     }
 anErr := a_ScrapBook(add_topic_Ptr, att, 
 ResRefNum, Refnum);

 { put the functional result in register D0 so 
 Acta can get it }
 CFunctionReturn(anErr);

 END;
END.

«listing 8»

Finally, here’s the actual source code which converts TEXT and PICT resources in a scrapbook file (actually, in any resource file) into Acta topics.

{10}
UNIT Scrapbook;
{ this unit will read a scrapbook file into a set of Acta topics for 
use with Acta 2.0 or higher }

INTERFACE

 USES
 Globals; { Pascal def of Topic_Attributes record }

 FUNCTION a_ScrapBook (add_topic_Ptr : ProcPtr;
 att : Topic_Attributes;
 ResRefNum : Integer;
 Refnum : Integer) : OsErr;

IMPLEMENTATION

 FUNCTION a_ScrapBook;

 CONST
 ActaTextTopic = 1;
 ActaPictTopic = 2;
 TopicLevel1 = 1;
 TopicLevel2 = 2;

 VAR
 ResHandle : Handle;
 TempPtr : Ptr;
 anErr : OSErr;
 ErrorString : Str255;
 RError : Integer;
 AppResNumber : Integer;
 ResCount : Integer;
 Size : LongInt;
 TType : OSType;
 i, j, k : Integer;

   { Acta internal procedure for adding a topic to the active window. 
This procedure was written in C, so arguments must be put on the stack 
in the reverse order of Dave’s documentation. This procedure is really 
linked to an ASM glue routine which pulls the pointer to the add_topic 
procedure off the stack and jumps to it and pulls the arguments off the 
stack before returning as well }

 PROCEDURE add_topic (empty : Ptr;
 att : Topic_Attributes;
 level : Integer;
 size : LongInt;
 text : Ptr;
 add_topic_Ptr : ProcPtr);external;


 PROCEDURE Init;

 { initialize a_Scrapbook }

 VAR
 Watch : CursHandle;

 BEGIN

 Watch := GetCursor(WatchCursor);
   { change cursor to watch while converting file }
 SetCursor(Watch^^);

      { save away the current resource file }
 AppResNumber := CurResFile;

   { Read from the input file }
 UseResFile(ResRefNum);

   { start out with the text blocks }
 ATT.bType := ActaTextTopic;
 TType := ‘TEXT’;

 END; { Init }

BEGIN { a_Scrapbook}

 Init;  { Initialize }

 FOR j := 1 TO 2 DO
 BEGIN
 { if the current output type is text then count TEXT }
 {  resource, else count number of picture resources }
 ResCount := CountResources(TType);
 RError := ResError;
 IF RError = noErr THEN
 BEGIN
 { loop through all resources of current type }
 FOR i := 1 TO ResCount DO
 BEGIN
 ResHandle := GetIndResource(TType, i);
 RError := ResError;
 { convert only resources actually in 
 the scrapbook file }
 IF (RError = noerr) AND (HomeResFile( ResHandle) = ResRefNum) THEN
 BEGIN
 Hlock(ResHandle);
 Size := Longint(GetHandleSize(ResHandle));
 add_topic(NIL, ATT, TopicLevel2, Size, ResHandle^, add_topic_ptr);
 HUnLock(ResHandle);
 END; { If (RError = ...  }
 ReleaseResource(ResHandle);
 END; { For (rescount)  }
 END; { RError }
 ATT.bType := ActaPictTopic;
 TType := ‘PICT’;
 END; { for (1 to 2) }
 { Use the application resource fork }
 UseResFile(AppResNumber);
 InitCursor;{ initialize cursor }
 { somewhat simplistic error reporting }
 a_ScrapBook := RError;
 END; { a_ScrapBook}
END. { ScrapBook }

«listing 9»

Conclusion

Format drivers allow programs to be extended easily, adding new features without an entirely new program. They also allow the user to customize the program’s features. On the implementation side, they are easy to write and allow packages to be written in multiple languages, even with development systems that do not normally support this.

Extensibility need not be limited to handling file formats. Future versions of Acta may have feature drivers.

The shrewd reader will have noticed that the techniques we’ve discussed allow you to break up your program into pieces which can be sold separately. This means you can get the program out the door quicker, and add the features later. And your marketing experts can have a field day packaging the different pieces.

Exercises

The answers to these exercises will not appear in next month’s MacTutor. But, you may be able to release them as free or shareware programs.

1. Modify a_TEXT to support MPW’s font and size specification.

2. Write an input driver which reads a tab (or comma) delimited file, creating a new level at each delimiter. (This format driver should have a name such as a_TEXT/tab, so the user has the choice of how to split topics.)

3. Write an input driver which reads a text file into a single topic. Acta uses TextEdit, so check for files which are too large. (This format driver should have a name such as a_TEXT/1 topic, so the user has the choice of how to split topics.)

4. Adopt the format driver technique in your own program.

5. (extra credit) Write a format driver for Microsoft Word 3.0 format.

Appendix -- Scrolling Icon List

Implementing the user interface really isn’t in the scope of this article, but we haven’t seen many examples of custom list definition functions (LDEFs).

Adding a scrolling list to the Standard File dialogs is pretty easy. Custom (SFPGetFile/ SFPPutFile) dialogs have a userItem, and the List Manager handles the work (with the aid of a custom LDEF). The dlgHook initializes the list when it gets passed item -1, and the filterProc checks for mouseDown events in the list. The only tricky part is in disposing of the list after the SFPPutFile call. If you call LDispose after the call, the List Manager tries to call DisposControl for the scroll bar but DisposDialog has already disposed of all the controls. If you try to call LDispose in the dlgHook after a click on the OK button, you’re faced with the problem that the dialog may not really go away -- the user may decide not to replace a file with the same name. Our solution was to set the list’s vScroll field to NIL before calling LDispose, since after the return from SFPPutFile, the control no longer exists.

Flying in the face of Inside Macintosh volume IV, the LDEF is written in C. The routine presented here shows only how to draw labelled icons (Acta’s LDEF uses additional data structures, and disposes them when it receives an lCloseMsg).

{11}
struct ListEntry {
 Handle icon;
 char   name[64]; /* Icon label (file name) */
};
typedef struct ListEntry  ListEntry;

/*
LDEF -- List Definition Procedure
*/
pascal void ldef(message,select,rect,cell,dataOffset,dataLen,handle)
word    message;
word    select;
Rect    *rect;
Cell    cell;
word    dataOffset;
word    dataLen;
ListHandlehandle;
{
 Rect   box;
 ListEntrylistData;
 BitMap iconBits;
 GrafPtrthePort;
 char   displayName[64];

 if (dataLen == 0) return;/* Nothing to draw (excludes         
 lCloseMsg) */
 GetPort(&thePort);/* Get current grafPort */
 /* Get our rectangle (only lDrawMsg, lHiliteMsg should get    
 past */
 /*  the dataLen test) */
 box = *rect;  /* Make a copy so we can change it */
 box.top += 2;
 box.left += 16;
 box.bottom = box.top + 32; /* Icons are 32 pixels */
 box.right = box.left + 32;
 /* dataLen is already set */
 LGetCell(&listData,&dataLen,cell,handle);
 switch (message) {
  case lDrawMsg:
 EraseRect(rect);
 PlotIcon(&box,listData.icon);
 /* If name begins with “a_” then ignore prefix */
 if ((listData.name[1] == ‘a’ || listData.name[1] == ‘A’) &&
 listData.name[2] == ‘_’) {
 /* Make a copy, since listData has filename */
 displayName[0] = listData.name[0] - 2;
 BlockMove(&listData.name[3],&displayName[1],(long)displayName[0]);
  } else {
 BlockMove(&listData.name[0],&displayName[0],
 (long)listData.name[0] + 1L);
 }
 TextFont(geneva);
 TextSize(9);
 MoveTo(rect->left + 32 - StringWidth(displayName) / 2,
 rect->top + 32 + 12);  /* Center the name */
 DrawString(displayName);
 /* Restore typestyle to assumed value */
 TextFont(systemFont);
 TextSize(12);
 if (!(select & 0x100))
 break;
 /* else fall through */
  case lHiliteMsg:
 /* Set up the bit map */
 iconBits.rowBytes = 4;
 SetRect(&iconBits.bounds,0,0,32,32);

 /* According to Steve Brecher, no need to lock, since         
 CopyBits won’t alter the heap configuration                   unless 
a picture or region is being recorded. */

 iconBits.baseAddr = (char *)(*listData.icon) + 128;
 CopyBits(&iconBits,&thePort->portBits, &iconBits.bounds,&box,
 srcXor,NIL);    /* Blit the icon */
 break;
 }
} 

Acta Document Format

Written by David Dunham

©25 September 1987 Maitreya Design

Maitreya Design

POB 1480

Goleta, CA 93116

This note assumes you are familiar with Acta and its terminology. Acta documents have the type ‘OTLN’ and creator ‘ACTA.’

An outline on disk consists of a data fork. This is simply the data for each topic in sequence (top-to-bottom order). The file is terminated with a zero word.

Each topic is preceded by its level. Topics at the left margin are level 1, their daughters 2, etc. The topic’s attributes indicate its type and formatting. Data is preceded by its length as a longword. The data itself is either text (not a Pascal string!) or a QuickDraw picture, and is not padded.

The same format is used for the clipboard; Acta uses a scrap type of ‘ACTA.’ (It also writes a TEXT scrap.)

{12}
struct TOPIC_ATTRIBUTES {
 word bType;/* TEXTtype, PICTtype */
 word bAttributes; /* currently 0, except for                  
 optionWord bits */
 word bFont;/* Typestyle */
 word bStyle;
 word bSize;
 byte bColor;    /* color index */
 byte bExtra;    /* reserved; currently 0 */
 byte firstLine; /* Show first line only? */
 byte expanded;  /* Y/N */
 word hidden;    /* Is block hidden (by Collapse)?             
 count of hide depth */
}

BType is 1 for a text topic, 2 if the topic is a QuickDraw picture. Other values are reserved for future use. BAttributes is currently unused and should be left 0, except that the bAttributes for the first topic is used to determine the options. BFont, bStyle, and bSize determine the typestyle of a topic, and have their usual QuickDraw interpretation. BColor is an index into a table of color values; 0 is black. BExtra is reserved and should be 0. FirstLine should be set to TRUE (-1) if the topic is shrunken, FALSE (0) otherwise. Expanded is TRUE if the daughters are visible, FALSE if they are hidden. If a topic is visible, hidden is 0, otherwise it is the number of collapsed ancestors.

If bAttributes of the first topic is 0, the default options of alphabetic sort A-Z, no ancestress constraint, and outline current topic are used. You can choose different options by setting the following bits (all other bits should be left reset). These bits are ignored if set on topics other than the first.

0x0100 numeric sort

0x0001 Z-A sort

0x0002 single ancestress

0x0004 “smart quotes”

0x0020 don’t label pictures

0x0040 don’t outline current topic

0x0080 don’t put bullets in Clipboard

In future releases, additional information (such as TextEdit ‘styl’ records) may be added. It will follow the zero word. This data can be ignored, as the above format will be used for backward compatibility.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Minecraft 1.20.2 - Popular sandbox build...
Minecraft allows players to build constructions out of textured cubes in a 3D procedurally generated world. Other activities in the game include exploration, gathering resources, crafting, and combat... Read more
HoudahSpot 6.4.1 - Advanced file-search...
HoudahSpot is a versatile desktop search tool. Use HoudahSpot to locate hard-to-find files and keep frequently used files within reach. HoudahSpot is a productivity tool. It is the hub where all the... Read more
coconutBattery 3.9.14 - Displays info ab...
With coconutBattery you're always aware of your current battery health. It shows you live information about your battery such as how often it was charged and how is the current maximum capacity in... Read more
Keynote 13.2 - Apple's presentation...
Easily create gorgeous presentations with the all-new Keynote, featuring powerful yet easy-to-use tools and dazzling effects that will make you a very hard act to follow. The Theme Chooser lets you... Read more
Apple Pages 13.2 - Apple's word pro...
Apple Pages is a powerful word processor that gives you everything you need to create documents that look beautiful. And read beautifully. It lets you work seamlessly between Mac and iOS devices, and... Read more
Numbers 13.2 - Apple's spreadsheet...
With Apple Numbers, sophisticated spreadsheets are just the start. The whole sheet is your canvas. Just add dramatic interactive charts, tables, and images that paint a revealing picture of your data... Read more
Ableton Live 11.3.11 - Record music usin...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Affinity Photo 2.2.0 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
SpamSieve 3.0 - Robust spam filter for m...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
WhatsApp 2.2338.12 - Desktop client for...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more

Latest Forum Discussions

See All

‘Resident Evil 4’ Remake Pre-Orders Are...
Over the weekend, Capcom revealed the Japanese price points for both upcoming iOS and iPadOS ports of Resident Evil Village and Resident Evil 4 Remake , in addition to confirming the release date for Resident Evil Village. Since then, pre-orders... | Read more »
Square Enix commemorates one of its grea...
One of the most criminally underused properties in the Square Enix roster is undoubtedly Parasite Eve, a fantastic fusion of Resident Evil and Final Fantasy that deserved far more than two PlayStation One Games and a PSP follow-up. Now, however,... | Read more »
Resident Evil Village for iPhone 15 Pro...
During its TGS 2023 stream, Capcom showcased the Following upcoming ports revealed during the Apple iPhone 15 event. Capcom also announced pricing for the mobile (and macOS in the case of the former) ports of Resident Evil 4 Remake and Resident Evil... | Read more »
The iPhone 15 Episode – The TouchArcade...
After a 3 week hiatus The TouchArcade Show returns with another action-packed episode! Well, maybe not so much “action-packed" as it is “packed with talk about the iPhone 15 Pro". Eli, being in a time zone 3 hours ahead of me, as well as being smart... | Read more »
TouchArcade Game of the Week: ‘DERE Veng...
Developer Appsir Games have been putting out genre-defying titles on mobile (and other platforms) for a number of years now, and this week marks the release of their magnum opus DERE Vengeance which has been many years in the making. In fact, if the... | Read more »
SwitchArcade Round-Up: Reviews Featuring...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 22nd, 2023. I’ve had a good night’s sleep, and though my body aches down to the last bit of sinew and meat, I’m at least thinking straight again. We’ve got a lot to look at... | Read more »
TGS 2023: Level-5 Celebrates 25 Years Wi...
Back when I first started covering the Tokyo Game Show for TouchArcade, prolific RPG producer Level-5 could always be counted on for a fairly big booth with a blend of mobile and console games on offer. At recent shows, the company’s presence has... | Read more »
TGS 2023: ‘Final Fantasy’ & ‘Dragon...
Square Enix usually has one of the bigger, more attention-grabbing booths at the Tokyo Game Show, and this year was no different in that sense. The line-ups to play pretty much anything there were among the lengthiest of the show, and there were... | Read more »
Valve Says To Not Expect a Faster Steam...
With the big 20% off discount for the Steam Deck available to celebrate Steam’s 20th anniversary, Valve had a good presence at TGS 2023 with interviews and more. | Read more »
‘Honkai Impact 3rd Part 2’ Revealed at T...
At TGS 2023, HoYoverse had a big presence with new trailers for the usual suspects, but I didn’t expect a big announcement for Honkai Impact 3rd (Free). | Read more »

Price Scanner via MacPrices.net

New low price: 13″ M2 MacBook Pro for $1049,...
Amazon has the Space Gray 13″ MacBook Pro with an Apple M2 CPU and 256GB of storage in stock and on sale today for $250 off MSRP. Their price is the lowest we’ve seen for this configuration from any... Read more
Apple AirPods 2 with USB-C now in stock and o...
Amazon has Apple’s 2023 AirPods Pro with USB-C now in stock and on sale for $199.99 including free shipping. Their price is $50 off MSRP, and it’s currently the lowest price available for new AirPods... Read more
New low prices: Apple’s 15″ M2 MacBook Airs w...
Amazon has 15″ MacBook Airs with M2 CPUs and 512GB of storage in stock and on sale for $1249 shipped. That’s $250 off Apple’s MSRP, and it’s the lowest price available for these M2-powered MacBook... Read more
New low price: Clearance 16″ Apple MacBook Pr...
B&H Photo has clearance 16″ M1 Max MacBook Pros, 10-core CPU/32-core GPU/1TB SSD/Space Gray or Silver, in stock today for $2399 including free 1-2 day delivery to most US addresses. Their price... Read more
Switch to Red Pocket Mobile and get a new iPh...
Red Pocket Mobile has new Apple iPhone 15 and 15 Pro models on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide service using all the major... Read more
Apple continues to offer a $350 discount on 2...
Apple has Studio Display models available in their Certified Refurbished store for up to $350 off MSRP. Each display comes with Apple’s one-year warranty, with new glass and a case, and ships free.... Read more
Apple’s 16-inch MacBook Pros with M2 Pro CPUs...
Amazon is offering a $250 discount on new Apple 16-inch M2 Pro MacBook Pros for a limited time. Their prices are currently the lowest available for these models from any Apple retailer: – 16″ MacBook... Read more
Closeout Sale: Apple Watch Ultra with Green A...
Adorama haș the Apple Watch Ultra with a Green Alpine Loop on clearance sale for $699 including free shipping. Their price is $100 off original MSRP, and it’s the lowest price we’ve seen for an Apple... Read more
Use this promo code at Verizon to take $150 o...
Verizon is offering a $150 discount on cellular-capable Apple Watch Series 9 and Ultra 2 models for a limited time. Use code WATCH150 at checkout to take advantage of this offer. The fine print: “Up... Read more
New low price: Apple’s 10th generation iPads...
B&H Photo has the 10th generation 64GB WiFi iPad (Blue and Silver colors) in stock and on sale for $379 for a limited time. B&H’s price is $70 off Apple’s MSRP, and it’s the lowest price... Read more

Jobs Board

Housekeeper, *Apple* Valley Villa - Cassia...
Apple Valley Villa, part of a 4-star senior living community, is hiring entry-level Full-Time Housekeepers to join our team! We will train you for this position and Read more
Housekeeper, *Apple* Valley Village - Cassi...
Apple Valley Village Health Care Center, a 4-star rated senior care campus, is hiring a Part-Time Housekeeper to join our team! We will train you for this position! Read more
Optometrist- *Apple* Valley, CA- Target Opt...
Optometrist- Apple Valley, CA- Target Optical Date: Sep 23, 2023 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 796045 At Target Read more
Senior *Apple* iOS CNO Developer (Onsite) -...
…Offense and Defense Experts (CODEX) is in need of smart, motivated and self-driven Apple iOS CNO Developers to join our team to solve real-time cyber challenges. Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.