TweetFollow Us on Twitter

EPSF Files
Volume Number:9
Issue Number:4
Column Tag:C Workshop

Related Info: Quickdraw Printing Manager

Creating EPSF Files

Converting PICT files to EPSF, one of the most useful and versatile file formats on the Macintosh.

By Gary McGath, Penacook, New Hampshire

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

About the author

Gary McGath is an independent software consultant and developer in Penacook NH, operating under the business name Conceptual Design.

One of the most useful and versatile file formats on the Macintosh is Encapsulated PostScript Format (EPSF, or sometimes just EPS). Unlike PICT files, EPSF files are resolution-independent, and can be rotated, scaled, and otherwise modified as needed. EPSF files can describe objects (lines, arcs, Bézier curves, etc.), bitmap images, or any combination thereof. Most desktop publishing applications, regardless of processor or operating system, will accept EPSF files; a Mac application can create an EPSF application which is brought into, say, a Unix multi-user publishing system. A PostScript output device is necessary, of course, to take advantage of the format.

In spite of these benefits, comparatively few Mac applications offer the option of creating an EPSF file. The reason for this may well be that EPSF generation is tricky. Doing a full-blown job of it is a major project. This article and its accompanying code will only present the basics, and will discuss some of the more difficult issues. The code provided here does a minimal job of converting PICT files to EPSF.

The Basics

An EPSF file is simply a file of PostScript commands, of type ‘EPSF’, which follows certain conventions. These conventions are designed to allow the file to be executed by a PostScript interpreter without changing the execution environment. This means that an application can generate some PostScript, drop in an EPSF file, and then continue on its way without caring what happened in between.

The full details of creating an EPSF file are found in Adobe Systems’ Postscript® Language Reference Manual, Second Edition, Appendix H. The discussion here will be very brief.

An EPSF file has to be a good camper, cleaning up after itself. Fortunately, this is easy to do, using the save and restore operators. It must not use any operators which would create non-local changes (e.g., exitserver). It has to avoid any assumptions about how it may have been transformed by the application using it; this means that it must avoid operators such as initgraphics which defeat existing changes in the environment.

The file has to make proper use of structured comments. In PostScript, a structured comment is one which begins with two percent signs (%%) and follows certain rules. The one absolute requirement is that the file have a %%BoundingBox comment. This tells the calling application the width and height of the image created by the file.

An EPSF file has to be self-contained. It can’t rely on the definitions in the Laser Prep file; those definitions might not be present on the system which ultimately uses the file. A file which was created under System 6.x should be usable under System 7.x or DOS 5.x, and vice versa. Whatever it is going to use beyond the built-in PostScript operators, it has to define.

Because of these constraints, an EPSF file is significantly different from the PostScript file generated by “printing” to PostScript. Attractive as it might seem, a PostScript file created by the LaserWriter driver can’t easily be turned into a clean EPSF file. The application which wants to create EPSF has to do the work itself.

PostScript vs. QuickDraw

In creating PostScript, the programmer has to take into account the differences between the QuickDraw and PostScript imaging models. QuickDraw was created primarily for drawing pixels to the Macintosh screen, and secondarily for drawing to a medium-resolution output device such as the ImageWriter. PostScript was designed for high-quality typographic work.

Perhaps the most annoying difference between QuickDraw and PostScript is the way they treat the relationship between grid coordinates and pixels. The problem isn’t that QuickDraw places the origin at the top of the image and PostScript places it at the bottom; this is easily dealt with by a transformation. The tougher problem is that QuickDraw locates pixels between grid coordinates, whereas PostScript centers its objects on grid coordinates.

For instance, in QuickDraw, if you position the pen at a vertical position of 35, then draw a horizontal line one pixel thick, the line is drawn between the y-coordinates of 35 and 36. In PostScript, if you do a MoveTo the vertical position of 35, then draw a horizontal line, the line is centered on a vertical position of 35.

You may think that everything comes out in the wash; all that happens is that QuickDraw objects are uniformly shifted over and down by half a point. But it isn’t that easy. If you draw a QuickDraw box between coordinates 35 and 40, its vertical extent between outside edges is 5 points. But if you do the same by drawing horizontal lines at y-coordinates of 35 and 40 respectively (and vertical lines wherever they may go), the distance from top to bottom is 6 points.

To further annoy the PostScript programmer, QuickDraw has a basic capability which PostScript doesn’t: it can look at bits which were previously drawn and change them. For instance, it can invert all the pixels in a given area, changing black to white and white to black. PostScript is blind to the pixels which its previously drawn, so it can’t do this. This means that certain drawing modes in QuickDraw (such as invert) can’t be imitated in PostScript.

The source code provided here is directed at the conversion of PICT files to EPSF, as the simplest way to illustrate the conversion of one model to the other. This code could also serve as the basis of an EPSF generator which could, with little change, be added to any application which makes QuickDraw calls. The key is the use of the GrafPort and the “bottleneck” procedures which can be replaced to modify the actions of QuickDraw. All that’s necessary is to replace the standard bottleneck procedures with ones which generate PostScript.

Some Specific Issues

A look at the description of the PostScript operators will uncover an interesting fact: there are operators to generate circles and arcs of circles, but no operators to generate ellipses or elliptical arcs. QuickDraw, on the other hand, has elliptical arcs built into its basic capabilities. Here the trick is to use the transformation matrix; by scaling the x and y axes unequally, you can get an ellipse of any desired shape. But if you’re framing the ellipse (drawing its outline), you can’t stroke it in the scaled coordinate system, or the thickness of the outline will vary as it goes around the ellipse. You have to save the current transformation matrix, generate the path in the transformed coordinate system, restore the matrix, and then stroke the path. This is illustrated in the PostScript procedure froval (defined in the PostScript strings in DrawEPSF.c); if you understand those few lines thoroughly, you’ll have a good understanding of how to use transformations in PostScript.

Text involves special difficulties. For each font which is used in a document, it’s necessary to obtain the name of the equivalent PostScript font from the FOND resource. The code here bypasses the issue, hard-coding in the Times Roman font. Another issue which this code doesn’t address is conversion between the Macintosh and PostScript character sets. A lot of work is needed to properly put text into an EPSF file.

Patterns are even worse. Mention rendering QuickDraw patterns in EPSF files, and even the hardiest programmer may turn pale and quake. The code presented with this article simply ignores the issue, except for recognizing solid white and solid black patterns and turning them into the respective gray levels.

Many pictures consist largely or entirely of bitmapped images. These can come in several varieties, including 1-bit Bitmaps, indexed (8-bit color or gray scale) Pixmaps, and direct (16-bit or 24-bit) Pixmaps. These can be handled using the image operator. The present version is very limited, dealing only with Bitmaps. Converting color and grayscale images requires converting these values into gray-level equivalents. If you want to output images to color printers, you can use the colorimage operator; but then your images won’t print on many printers. Just to avoid getting a PostScript error on these printers, you’ll have to include PostScript code to test whether colorimage is available.

Apple has defined a number of PicComments which can be used to generate special PostScript effects or insert custom PostScript code when printing. An EPSF generator should ideally recognize all of these comments. They provide standard methods for rotating text, generating Bézier curves and dashed lines, and so on. For simplicity, the code included with this article doesn’t deal with these PicComments.

Every EPSF file created on a Mac is expected to have a PICT resource numbered 256, which represents the image for screen displays and output to non-PostScript devices. For this application, generating the PICT is really easy; it’s simply a matter of copying the original PICT.

The code which accompanies this article addresses the basic issues of creating an EPSF file, but it’s only a beginning toward a useful software product. To do the job, it needs to handle fonts rigorously, rotate and scale objects (using PicComments or some equivalent method), deal with clipping regions, handle gray levels and patterns, and process additional graphic objects such as Bézier curves. None of these problems are insurmountable, though. The routines here provide the starting point; all the rest is just working out the details.

Bibliography

Adobe Systems Incorporated, PostScript® Language Reference Manual, Second Edition. Addison-Wesley Publishing Company, 1990.

Adobe Systems Incorporated, PostScript® Language Tutorial and Cookbook. Addison-Wesley Publishing Company, 1985.

Listing: PSD.h
/* Copyright 1992, Gary D. McGath */

extern void OutputString(char *str, int theFile);
extern void OutputNum(int val, int theFile);
void OutputDouble(double val, int theFile);
void OutputHex(int ch, int theFile);
void OutputChar(int ch, int theFile);
void FlushOutBuf(int theFile);
Listing: Main.c
/* Convert PICT files to EPSF. © 1992, Gary McGath */
/* Mac Resource Defs */
#define APPLMENU 128
#define FILEMENU 180
#define WatchCursor 4
#define NIL 0L

extern void DrawEPSF(PicHandle han, int theFile);

int main (void);
void mainloop(void);
void docommand(long mensel);
void doconvert(void);
PicHandle ReadPict(int theFile);

extern int thePSResFile;

MenuHandle apmenu;
MenuHandle filemenu;
EventRecord myevent;
Handle pictHan;
WindowPtr whichwindow;
int infile;
int thePSFile;
Point gfloc = {40,40};
/* list of acceptable file types */
SFTypeList typelist = {‘PICT’};

main()
{
 InitGraf((Ptr)&thePort);
 InitFonts();
 InitWindows();
 InitDialogs(0L);
 InitMenus();
 InitCursor();
 apmenu = GetMenu(APPLMENU);
 AddResMenu(apmenu,’DRVR’);
 InsertMenu(apmenu,0);
 filemenu = GetMenu(FILEMENU);
 InsertMenu(filemenu,0);
 DrawMenuBar();
 mainloop();
}

void mainloop()
{
 int code;
 int ch;
 for (;;) {
 WaitNextEvent(everyEvent,&myevent,2L,NIL);
 switch (myevent.what) {
 case keyDown:
 ch = myevent.message & charCodeMask;
 if (myevent.modifiers & cmdKey)
 docommand(MenuKey(ch));
 break;
      case mouseDown:
 code =
  FindWindow(myevent.where,&whichwindow);
 switch (code) {
      case inMenuBar:
 docommand(MenuSelect(myevent.where));
 break;
 case inSysWindow:
 SystemClick(&myevent,whichwindow);
 break;
 }
 break;
 }
 }
}

void docommand(long mensel)
{
 register short men, item;
 char accname[64]; 
 GrafPtr saveport;
 men = mensel >> 16;
 item = (short) mensel;
 if (men == 0)
 return;
 if (men == APPLMENU) {
 if (item <= 2)  /* no “about” */
 SysBeep(1);
     else {
      GetItem(apmenu,item,(StringPtr)accname);
 GetPort(&saveport);
 OpenDeskAcc((StringPtr)accname);
 SetPort(saveport);
 }
 }
 else switch(item) {     /*  File */
 case 1:
 doconvert();
 break;
 case 3 :
 default:
 ExitToShell();
 }
 HiliteMenu(0);       /* clean up display */
}

/* Command handler to convert PICT to EPSF. */
void doconvert()
{
 SFReply myreply;
 unsigned char *pt;
 OSErr errcod;
 PicHandle picHan;
 /* specify and open input (PICT) file */
 SFGetFile(gfloc,0L,0L,1,&typelist,0L,&myreply);
 if (!myreply.good)
 return;/* cancelled out */
 errcod = FSOpen
 (&myreply.fName[0],myreply.vRefNum,&infile);
 if (errcod != noErr)
 return;
 /* specify output file */
 for (pt = &myreply.fName[1]; pt < &myreply.fName[32];)
 *pt++ = 0; /* clear out name buffer */
 SFPutFile(gfloc,
 (StringPtr)”\pName of output file:”,
 (StringPtr)”\pEPSF File”,0L,&myreply);
 if (!myreply.good) {
 FSClose(infile);
 return;/* cancelled out */
 }
 SetCursor(*GetCursor(WatchCursor));
 errcod = CreateEPSFFile(&myreply, &thePSFile);
 if (errcod == noErr) {
 picHan = ReadPict(infile);
 if (picHan == 0)
 goto done;
 AddResource(picHan,’PICT’,256,”\p”);
 DrawEPSF(picHan, thePSFile); }
done:
 if (infile)
 FSClose(infile);
 if (thePSResFile)
 CloseResFile(thePSResFile);
 if (thePSFile)
 FSClose(thePSFile);
 FlushVol(0,0);
 InitCursor();
}

/* Read the PICT into a PicHandle */
PicHandle ReadPict(int theFile)
{
 long eofPos;
 long count;
 Handle theHan;
 SetFPos(theFile, fsFromStart, 512L);/* skip PICT header */
 GetEOF(theFile, &eofPos);/* get file length */
 count = eofPos - 512;
 theHan = NewHandle(count); 
 if (MemError())
 return 0;
 HLock(theHan);
 FSRead(theFile, &count, *theHan);
 HUnlock(theHan);
 return ((PicHandle) theHan);
}
listing:  DrawEPSF.c
/* Copyright 1992, Gary D. McGath */

extern void InitOutBuf(void);
extern void OutputString(char *str, int theFile);
extern void OutputChar(int ch,int theFile);
extern void OutputNum(int n, int theFile);
extern void FlushOutBuf(int theFile);

OSErr CreateEPSFFile(SFReply *theReply, int *theFile);
void DrawEPSF(PicHandle han, int theFile);
void WriteBoundingBox
 (PicHandle han,Rect *bbRect, int theFile);
void WritePrologue(int theFile);
void FlipCoords(Rect *RectP, int theFile);
void WriteEpilogue(int theFile);

/* The strings of the prologue. */
char *prologueStrs[] = {
“/_E_save save def\r”,
“/our_dict 70 dict def\r”,
“our_dict begin /bd {bind def} def  /xd {exch def} bd\r”,
“/rdims {/bt xd /rt xd /tp xd /lf xd} bd\r”,
“/fradj {/tp tp phw add def /bt bt phw sub def\r”,
“  /lf lf phw add def /rt rt phw sub def} bd\r”,
“/arctopppp {arcto pop pop pop pop} bd\r”,
 /* fill rect proc: l t r b flrec */
“/flrec {rdims lf tp moveto rt tp lineto rt bt lineto lf bt lineto closepath 
fill} bd\r”,
 /* frame rect proc: l t r b pen.h pen.v frrec */
“/frrec {/pnh xd /pnw xd rdims\r”,
“  lf tp moveto rt tp lineto rt bt lineto lf bt lineto closepath\r”,
“  /lf lf pnw add def /tp tp pnh add def /rt rt pnw sub def /bt bt pnh 
sub def\r”,
“  lf tp moveto rt tp lineto rt bt lineto lf bt lineto closepath\r”,
“ eofill} bd\r”,
 /* fill oval proc: l t r b floval */
“/floval {rdims gsave lf rt add 2 div tp bt add 2 div translate\r”,
“ rt lf sub 2 div bt tp sub 2 div scale\r”,
“ 1 0 moveto 0 0 1 0 360 arc fill grestore} bd\r”,
 /* frame oval proc: l t r b pen.h froval */
“/froval {dup /pnw xd setlinewidth”,
“/phw pnw 0.5 mul def fradj”,
“  rdims /mt matrix currentmatrix def lf rt add 2 div tp bt add 2 div 
translate\r”,
“  rt lf sub 2 div bt tp sub 2 div scale\r”,
“  1 0 moveto 0 0 1 0 360 arc mt setmatrix stroke} bd\r”,
 /* frame arc proc: l t r b pen.h startangle arcangle frarc */
“/frarc {/arca xd /stra xd setlinewidth rdims\r”,
“  /mt matrix currentmatrix def lf rt add 2 div tp bt add 2 div translate\r”,
“  rt lf sub 2 div bt tp sub 2 div scale\r”,
“  0 0 1 stra 90 sub dup arca add arc mt setmatrix stroke} bd\r”,
“/flarc {/arca xd /stra xd rdims\r”,
“  gsave lf rt add 2 div tp bt add 2 div translate\r”,
“  rt lf sub 2 div bt tp sub 2 div scale\r”,
“  0 0 moveto 0 0 1 stra 90 sub dup arca add arc fill grestore} bd\r”,
 /* frame round rect proc: l t r b pen.h rad.h
    This version makes the simplifying assumptions of a square pen and
    circular (not elliptical) corners. */
“/frrrect {/rd xd /pnw xd rdims /phw pnw 0.5 mul def fradj\r”,
“  rt rd sub tp moveto rt tp rt tp rd add rd arctopppp\r”,
“  rt bt rt rd sub bt rd arctopppp\r”,
“  lf bt lf bt rd sub rd arctopppp\r”,
“  lf tp lf rd add tp rd arctopppp\r closepath stroke} bd\r”,
 /* fill round rect: l t r b rad.h */
“/flrrect {/rd xd rdims\r”,
“  rt rd sub tp moveto rt tp rt tp rd add rd arctopppp\r”,
“  rt bt rt rd sub bt rd arctopppp\r”,
“  lf bt lf bt rd sub rd arctopppp\r”,
“  lf tp lf rd add tp rd arctopppp\r closepath fill} bd\r”,
“”           /* last string must be null */
};

char *epilogueStrs[] = {
“end _E_save restore\r”,
“”
};

int thePSResFile;

/* Call this before calling DrawEPSF. */
OSErr CreateEPSFFile
 (SFReply *theReply, int *theFile)
 {
 OSErr err;
 FSDelete
 (&theReply->fName[0],theReply->vRefNum);
 err = Create(&theReply->fName[0], 
 theReply->vRefNum, ‘????’, ‘EPSF’);
 err = FSOpen(&theReply->fName[0], 
 theReply->vRefNum, theFile);
 SetVol((StringPtr) 0, theReply->vRefNum);
 CreateResFile(&theReply->fName[0]);
 thePSResFile = OpenResFile(&theReply->fName[0]);
 if (err != noErr)
 *theFile = 0;
 return err;
 }

/* The main routine for writing the data 
   fork of the EPSF file. */
void DrawEPSF(PicHandle han, int theFile)
 {
 GrafPtr savePort;
 GrafPtr dstPort;
 Rect dstRect;
 Rect *picRectPtr;
 InitOutBuf();
 OutputString
 (“%!PS-Adobe-3.0 EPSF-3.0\r”, theFile);
 picRectPtr = &(**han).picFrame;
 dstRect.left = 0;
 dstRect.top = 0;
 dstRect.right = picRectPtr->right;
 dstRect.bottom = picRectPtr->bottom;
 WriteBoundingBox(han, &dstRect,theFile);
 WritePrologue(theFile);
 FlipCoords(&dstRect, theFile);
 GetPort(&savePort);
 dstPort = (GrafPtr) NewPtr(sizeof(GrafPort));
 if (dstPort == 0)
 return;
 OpenPort(dstPort);
 psdInitPort(dstPort);
 psdSetupProcs(dstPort);
 SetPort((GrafPtr)dstPort);
 DrawPicture(han,picRectPtr);
 WriteEpilogue(theFile);
 FlushOutBuf(theFile);
 SetPort(savePort);
 ClosePort(dstPort);
 DisposPtr(dstPort);
 }

/* Get the dimensions of the PICT, and 
 write them as a BoundingBox comment */
void WriteBoundingBox
 (PicHandle han, Rect *RectP, int theFile)
{
 OutputString(“%%BoundingBox: 0 0 “, theFile);
 OutputNum(RectP->right, theFile);
 OutputNum(RectP->bottom, theFile);
 OutputChar(‘\r’,theFile);
}

/* Write out the prologue code of the file. */
void WritePrologue(int theFile)
{
 register int i;
 for (i=0;; i++) {
 if (prologueStrs[i][0] == 0)
 break;
 OutputString(prologueStrs[i], theFile);
 } 
}

/* Flip the coordinate system so it matches 
   the QuickDraw coordinates */
void FlipCoords(Rect *RectP, int theFile)
{
 OutputString(“1 -1 scale “, theFile);
 OutputNum(0, theFile);
 OutputNum(-RectP->bottom, theFile);
 OutputString(“translate “, theFile);
}

void WriteEpilogue(int theFile)
{
 register int i;
 for (i=0;; i++) {
 if (epilogueStrs[i][0] == 0)
 break;
 OutputString(epilogueStrs[i], theFile);
 } 
}
listing:  PSDSetup.c
/* Copyright 1992, Gary D. McGath */

extern pascal void psdRectProc
 (GrafVerb verb, Rect *theRect);
extern pascal psdTextProc
 (short byteCount, Ptr textBuf, 
 Point numer, Point denom);
extern pascal psdLineProc(Point newPt);
extern pascal psdOvalProc
 (GrafVerb verb, Rect *theRect);
extern pascal psdrRectProc
 (GrafVerb verb, Rect *theRect, 
 short ovalwidth, short ovalheight);
extern pascal psdArcProc
 (GrafVerb verb, Rect *theRect, 
 short startAngle, short arcAngle);
extern pascal psdPolyProc
 (GrafVerb verb, PolyHandle thePoly);
extern pascal psdBitsProc
 (BitMap *srcBits, Rect *srcRect, 
 Rect *dstRect, short mode, RgnHandle maskRgn);

void psdSetupProcs(GrafPtr itsPort);
pascal psdCommentProc
 (short kind, short dataSize, Handle dataHandle);
pascal psdRgnProc
 (GrafVerb verb, RgnHandle theRgn);

/* Create a CGrafPort to draw to, and set up 
   the bottleneck procedures */
void psdSetupProcs(GrafPtr itsPort)
{
 QDProcsPtr psdProcs;
 psdProcs = (QDProcsPtr) NewPtr(sizeof(QDProcs));
 itsPort->grafProcs = psdProcs;
 SetStdProcs(psdProcs);
 psdProcs->textProc = (Ptr) psdTextProc;
 psdProcs->lineProc = (Ptr) psdLineProc;
 psdProcs->rectProc = (Ptr) psdRectProc;
 psdProcs->rRectProc = (Ptr) psdrRectProc;
 psdProcs->ovalProc = (Ptr) psdOvalProc;
 psdProcs->arcProc = (Ptr) psdArcProc;
 psdProcs->polyProc = (Ptr) psdPolyProc;
 psdProcs->rgnProc = (Ptr) psdRgnProc;
 psdProcs->bitsProc = (Ptr) psdBitsProc;
 psdProcs->commentProc = (Ptr) psdCommentProc;
}

/* PicComments aren’t handled in this program */
pascal psdCommentProc
 (short kind, short dataSize, Handle dataHandle)
{
}

/* Neither are regions */
pascal psdRgnProc(GrafVerb verb, RgnHandle theRgn) 
{
}
listing:  PSDGeomProcs.c
/* Here are the Quickdraw plug-in procs which handle the 
   comparatively simple geometric stuff */
/* Copyright 1992, Gary D. McGath */

#include “psd.h”

extern int thePSFile;

pascal void psdRectProc
 (GrafVerb verb, Rect *theRect);
pascal psdOvalProc(GrafVerb verb, Rect *theRect);
pascal psdrRectProc
 (GrafVerb verb, Rect *theRect, 
 short ovalwidth, short ovalheight);
pascal psdLineProc(Point newPt);
pascal psdPolyProc
 (GrafVerb verb, PolyHandle thePoly);
pascal psdArcProc
 (GrafVerb verb, Rect *theRect, 
 short startAngle, short arcAngle);
void psdDrawLine(Point oldPt, Point newPt);
void psdWriteRect(Rect *theRect);
void psdDrawLine(Point oldPt, Point newPt);

/* psdRectProc - one of the easiest (ha!) */
pascal void psdRectProc
 (GrafVerb verb, Rect *theRect)
{
 if (thePort->pnVis < 0)
 return;/* nothing to draw */
 switch(verb) {
 case frame:
 if (thePort->pnSize.h == 0 &&
  thePort->pnSize.v == 0)
 return;
 psdSetPenPat(); 
 psdWriteRect(theRect);
 OutputNum(thePort->pnSize.h,thePSFile);     
 OutputNum(thePort->pnSize.v,thePSFile);     
 OutputString(“frrec\r”,thePSFile);
 break;
 case fill:
 case paint:
 case erase:
 if (verb == paint)
 psdSetPenPat(); 
 else if (verb == erase)
 OutputGray(0);
 else psdSetFillPat();
 psdWriteRect(theRect);
 OutputString(“flrec\r”,thePSFile);
 break;
 case invert:
 break;      /* invert isn’t supported */
 }
}

/* psdOvalProc - draw an oval.  PostScript 
   knows about circles but not
   ellipses, so we have to scale the circular path
   (but *not* the stroking
   of it) to an ellipse.  For the present, we
   assume that the pen is square (equal 
   in horizontal and vertical dimensions). */
pascal psdOvalProc(GrafVerb verb, Rect *theRect)
{
 if (thePort->pnVis < 0)
 return;      /* nothing to draw */
 switch(verb) {
 case frame:
 if (thePort->pnSize.h == 0 && 
 thePort->pnSize.v == 0)
 return;
 psdSetPenPat(); 
 psdWriteRect(theRect);
 OutputNum(thePort->pnSize.h,thePSFile);     
 OutputString(“froval\r”,thePSFile);
 break;
 case fill:
 case paint:
 case erase:
 if (verb == paint)
 psdSetPenPat(); 
 else if (verb == erase)
 OutputGray(0);
 else psdSetFillPat();    
 psdWriteRect(theRect);
 OutputString(“floval\r”,thePSFile);
 break;
 case invert:
 break;      /* invert isn’t supported */
 }
}

/* psdRRectProc - draw a round-cornered
   rectangle. In this case, we make
   two simplifying assumptions: that the pen is
   square and that the corners
   are circular (width = height). */
pascal psdrRectProc
 (GrafVerb verb, Rect *theRect, short ovalwidth, 
 short ovalheight)
{
 if (thePort->pnVis < 0)
 return;/* nothing to draw */
 switch(verb) {
 case frame:
 psdSetPenPat(); 
 psdWriteRect(theRect);
 OutputNum(thePort->pnSize.h,thePSFile);
 OutputDouble(ovalwidth/2.0,thePSFile);
 OutputString(“frrrect\r”,thePSFile);
 break;
 case fill:
 case paint:
 case erase:
 if (verb == paint)
 psdSetPenPat(); 
 else if (verb == erase)
 OutputGray(0);
 else psdSetFillPat();    
 psdWriteRect(theRect);
 OutputDouble(ovalwidth/2.0,thePSFile);
 OutputString(“flrrect\r”,thePSFile);
 break;
 case invert:
 break;      /* invert isn’t supported */
 }
}

/* psdLineProc - draw a line from the current
   point to the new point. “Lines” in Quickdraw
   are actually funny-shaped polygons. */
pascal psdLineProc(Point newPt)
{
 Point oldPt;
 oldPt = thePort->pnLoc;
 thePort->pnLoc = newPt;
 if (thePort->pnVis < 0 ||
  (thePort->pnSize.h == 0 &&
 thePort->pnSize.v == 0)) {
 return;      /* nothing to draw */
 }
 psdSetPenPat(); 
 psdDrawLine(oldPt, newPt);
}

pascal psdPolyProc
 (GrafVerb verb, PolyHandle thePoly)
{
 int npoints;
 register int i;
 PolyPtr thePolyp;
 Point prevPoint;
 HLock(thePoly);
 thePolyp = *thePoly;
 npoints = (thePolyp->polySize - 10) / 4;
 switch(verb) {
 case frame:
 if (thePort->pnSize.h == 0 && 
 thePort->pnSize.v == 0)
 return;
 psdSetPenPat(); 
 prevPoint = thePolyp->polyPoints[0];
 for (i = 1; i < npoints; i++) {
 psdDrawLine(prevPoint,
 thePolyp->polyPoints[i]);
 prevPoint = thePolyp->polyPoints[i];
 }
 break;
 case fill:
 case paint:
 case erase:
 if (verb == fill)
 psdSetFillPat();
 else if (verb == erase)
 OutputGray(0);
 else psdSetPenPat();
 prevPoint = thePolyp->polyPoints[0];
 OutputNum(prevPoint.h, thePSFile);
 OutputNum(prevPoint.v, thePSFile);
 OutputString(“moveto “, thePSFile);
 for (i = 1; i < npoints; i++) {
 prevPoint = thePolyp->polyPoints[i];
 OutputNum(prevPoint.h, thePSFile);
 OutputNum(prevPoint.v, thePSFile);
 OutputString(“lineto “, thePSFile);
 }
 OutputString(“closepath fill\r”, thePSFile);
 break;
 case invert:
 break;       /* invert isn’t supported */
 }
 HUnlock(thePoly);
}

pascal psdArcProc
 (GrafVerb verb, Rect *theRect,
 short startAngle, short arcAngle)
{
 if (thePort->pnVis < 0)
 return;         /* nothing to draw */
 if (arcAngle < 0) { /* make angle always positive */
 startAngle += arcAngle;
 arcAngle = -arcAngle;
 }
 switch(verb) {
 case frame:
 if (thePort->pnSize.h == 0 &&
 thePort->pnSize.v == 0)
 return;
 psdSetPenPat();
 psdWriteRect(theRect);
 OutputNum(thePort->pnSize.h,thePSFile);
 OutputNum(startAngle,thePSFile);
 OutputNum(arcAngle,thePSFile);
 OutputString(“frarc\r”,thePSFile);
 break;
 case fill:
 case paint:
 case erase:
 if (verb == paint)
 psdSetPenPat();
 else if (verb == erase)
 OutputGray(0);
 else psdSetFillPat();
 psdWriteRect(theRect);
 OutputDouble(startAngle,thePSFile);
 OutputDouble(arcAngle,thePSFile);
 OutputString(“flarc\r”,thePSFile);
 break;
 case invert:
 break;        /* invert isn’t supported */
 }
}

/* psdWriteRect - output the coordinates 
   of a rectangle. */
void psdWriteRect(Rect *theRect)
{
 OutputNum(theRect->left,thePSFile);
 OutputNum(theRect->top,thePSFile);
 OutputNum(theRect->right,thePSFile);
 OutputNum(theRect->bottom,thePSFile);
}

void psdDrawLine(Point oldPt, Point newPt)
{
 int hdelta, vdelta;
 Point startcorner, endcorner;
 if (newPt.h > oldPt.h) {
 hdelta = thePort->pnSize.h;
 startcorner.h = oldPt.h;
 endcorner.h = newPt.h + hdelta;
 if (newPt.v > oldPt.v) {
 vdelta = thePort->pnSize.v;
 startcorner.v = oldPt.v;
 endcorner.v = newPt.v + vdelta;
 }
 else {     /* newPt.v <= oldPt.v */
 vdelta = -thePort->pnSize.v;
 startcorner.v = oldPt.v - vdelta;
 endcorner.v = newPt.v;
 }
 }
 else {     /* newPt.h <= oldPt.h */
 hdelta = -thePort->pnSize.h;
 startcorner.h = oldPt.h - hdelta;
 endcorner.h = newPt.h;
 if (newPt.v > oldPt.v) {
 vdelta = thePort->pnSize.v;
 startcorner.v = oldPt.v;
 endcorner.v = newPt.v + vdelta;
 }
 else {     /* newPt.v <= oldPt.v */
 hdelta = -thePort->pnSize.h;
 vdelta = -thePort->pnSize.v;
 startcorner.v = oldPt.v - vdelta;
 endcorner.v = newPt.v;
 }
 }
 OutputNum(startcorner.h,thePSFile);
 OutputNum(startcorner.v,thePSFile);
 OutputString(“moveto “,thePSFile);
 OutputNum(hdelta,thePSFile);
 OutputNum(0,thePSFile);
 OutputString(“rlineto “,thePSFile);
 OutputNum(endcorner.h,thePSFile);
 OutputNum(endcorner.v - vdelta,thePSFile);
 OutputString(“lineto “,thePSFile);
 OutputNum(0,thePSFile);
 OutputNum(vdelta,thePSFile);
 OutputString(“rlineto “,thePSFile);
 OutputNum(-hdelta,thePSFile);
 OutputNum(0,thePSFile);
 OutputString(“rlineto “,thePSFile);
 OutputNum(startcorner.h,thePSFile);
 OutputNum(startcorner.v+vdelta,thePSFile);
 OutputString
 (“lineto closepath fill\r”,thePSFile);
}
listing:  PSDtext.c
/* This module contains routines for bitmap operations */
/* Copyright 1992, Gary D. McGath */

#include “psd.h”

pascal psdTextProc
 (short byteCount, Ptr textBuf,
 Point numer, Point denom);
void SelectFont(void);

extern int thePSFile;
extern int gblFont;
extern int gblSize;

pascal psdTextProc
 (short byteCount, Ptr textBuf, 
 Point numer, Point denom) 
{
 register int i;
 SelectFont();   
 OutputString(“/SV save def “,thePSFile);
 OutputNum(thePort->pnLoc.h,thePSFile);
 OutputNum(thePort->pnLoc.v,thePSFile);
 OutputString(“translate 1 -1 scale “,thePSFile);
 OutputString(“0 0 moveto\r”,thePSFile);
 /* The following code will fail in various
 cases, most notably unbalanced
 parentheses within the string. */
 OutputChar(‘(‘, thePSFile);
 for (i = 0; i < byteCount; i++)
 OutputChar(textBuf[i],thePSFile);
 OutputString(“) show SV restore\r”, thePSFile);
}
void SelectFont()
{
 if (thePort->txFont != gblFont ||
 thePort->txSize != gblSize) {
 
 /* “Real” code needs to look up the 
 PostScript font name */
 OutputString
 (“/Times-Roman findfont “,thePSFile);
 OutputNum(thePort->txSize, thePSFile);
 OutputString(“scalefont setfont\r”,thePSFile);
 gblFont = thePort->txFont;
 gblSize = thePort->txSize;
 }
}
listing:  PSDbits.c
/* This module contains routines for bitmap operations */
/* Copyright 1992, Gary D. McGath */

pascal void psdBitsProc
 (BitMap *srcBits, Rect *srcRect, 
 Rect *dstRect,
 short mode, RgnHandle maskRgn);

extern int thePSFile;

/* In this version, the only mode supported 
   is copy. Most others
   are problematical, since PostScript doesn’t
   allow logical combinations
   with an existing raster.
   We support only 1-bit bitmaps here; Pixmaps 
   are ignored (left as an exercise for 
   the reader). */
pascal void psdBitsProc
 (BitMap *srcBits, Rect *srcRect, Rect *dstRect,
 short mode, RgnHandle maskRgn)
{
 int pixelWidth, pixelDepth;
 Ptr dataPtr;
 int byteWidth;
 register int i, j;
 
 if (srcBits->rowBytes & 0X8000)
 return;     /* can’t handle pixmap */
 if (srcRect->left != srcBits->bounds.left)
 return;     /* bit shift not implemented */
 pixelWidth = srcRect->right - srcRect->left;
 pixelDepth = srcRect->bottom - srcRect->top;
 byteWidth = (pixelWidth + 7) / 8;
/* avoid accumulating garbage */
 OutputString(“/SV save def /ims “,thePSFile);
 OutputNum(byteWidth,thePSFile);
 OutputString(“string def\r”,thePSFile);           OutputNum(dstRect->left, 
thePSFile);
 OutputNum(dstRect->top, thePSFile);
 OutputString(“translate\r”,thePSFile);
 
 OutputNum
 (dstRect->right - dstRect->left, thePSFile);
 OutputNum
 (dstRect->bottom - dstRect->top, thePSFile);
 OutputString(“scale\r”,thePSFile);
 OutputNum(pixelWidth, thePSFile);
 OutputNum(pixelDepth, thePSFile);
 OutputString(“1 [“,thePSFile);
 OutputNum(pixelWidth, thePSFile);   /* width */
 OutputString(“0 0 “, thePSFile);
 OutputNum(pixelDepth, thePSFile);
 OutputString
 (“0 0 ] {currentfile ims readhexstring pop}
 image\r”,thePSFile);

 /* Now output the image as hex data. */
 dataPtr = srcBits->baseAddr;
 for (i = 0; i < pixelDepth; i++) {
 for (j = 0; j < byteWidth; j++) {
 OutputHex(~*dataPtr++,thePSFile);
 if ((j & 0X7F) == 0X7F)
 OutputChar(‘\r’, thePSFile);
 }
 if (byteWidth & 1)
 dataPtr++;
 OutputChar(‘\r’, thePSFile);
 }
 OutputString(“SV restore\r”,thePSFile);
}
listing:  PSD Port.c
/* Copyright 1992, Gary D. McGath */
/* Calls for monitoring/handling changes in the GrafPort */
 
extern void OutputString(char *str, int theFile);

void psdSetPenPat(void);
void psdSetFillPat(void);
void OutputGray(double lev);
static void psdSetPat(unsigned char *p1);

extern int thePSFile;
extern Rect gPSClipRect;

double gblGray;   /* last gray level output */
int gblFont;
int gblSize;

/* Set up defaults for the port. */
void psdInitPort(GrafPtr itsPort)
{
 itsPort->pnSize.h = 1;
 itsPort->pnSize.v = 1;
 gblGray = -1;
 gblFont = -1;
 gblSize = -1; 
}

/* Output PostScript code to match the new pattern. “Real” 
   code should set up a pattern fill; this one sets the 
   color to black, white, or 50% gray depending on the 
   pattern. */
void psdSetPenPat()
{
 psdSetPat((unsigned char *)&thePort->pnPat);
}

void psdSetFillPat()
{
 psdSetPat((unsigned char *)&thePort->fillPat);
}

static void psdSetPat(unsigned char *p1)
{
 int allWhite = 1;
 int allBlack = 1;
 register int i;
 for (i = 1; i < 8; i++) {
 if (*p1 != 0XFF)
 allBlack = 0;
 if (*p1++ != 0)
 allWhite = 0;
 }
 if (allWhite)
 OutputGray(1.0);
 else if (allBlack)
 OutputGray(0.0);
 else OutputGray(0.5);
}

/* output a gray level, only if it’s different
   from the previous one */
void OutputGray(double lev)
{
 if (lev != gblGray) {
 OutputDouble(lev,thePSFile);
 OutputString(“setgray\r”,thePSFile);
 gblGray = lev;
 }
}
listing:  WriteEPSF.c
/* Low level routines for buffering output */
/* Copyright 1992, Gary D. McGath */

#define BUFFSIZE 512

void InitOutBuf(void);
void OutputString(char *str, int theFile);
void OutputChar(int ch,int theFile);
void OutputNum(int val, int theFile);
void FlushOutBuf(int theFile);
static void OutputHexNibble(int nib, int theFile);

static char buff[BUFFSIZE]; 
static int buffoff;

/* This must be called first */
void InitOutBuf()
{
 buffoff = 0;    /* make empty buffer */
}

/* Write a C string to the file */
void OutputString(char *str, int theFile)
{
 while (*str)
 OutputChar(*str++,theFile);
}

/* write # to the file, in decimal, with a trailing space */
  void OutputNum(int val, int theFile)
{
 int dvsr = 10000;
 int qtnt;
 int leadflag = 0;
 if (val < 0) {
 val = -val;
 OutputChar(‘-’,theFile); 
 }
 while (dvsr > 0) {
 if (dvsr == 1)
 leadflag = 1;   
 qtnt = val/dvsr;
 if (qtnt == 0) {
 if (leadflag) 
 OutputChar(‘0’,theFile);
 }
 else {
 OutputChar(qtnt + ‘0’, theFile);
 leadflag = 1;
 }
 val = val - qtnt * dvsr; 
 dvsr /= 10;
 }
 OutputChar(‘ ‘, theFile);
}

/* OutputDouble - similar in concept to OutputNum. Doesn’t 
   handle huge or tiny numbers reasonably. */
void OutputDouble(double val, int theFile)
{
 long ival;
 long dvsr = 1000000;
 int qtnt;
 int leadflag = 0; 
/* special case for zero or almost */
 if (val < 0.001 && val > -0.001) {
 OutputNum(0, theFile);
 return;
 }
 ival = val * 1000;
 if (ival < 0) {
 ival = -ival;
 OutputChar(‘-’,theFile); 
 }
 while (dvsr > 0) {
 if (dvsr == 100) {
 OutputChar (‘.’, theFile); 
 leadflag = 1;   
 }
 qtnt = ival/dvsr;
 if (qtnt == 0) {
 if (leadflag)   
 OutputChar(‘0’,theFile);
 }
 else {
 OutputChar(qtnt + ‘0’, theFile);  
 leadflag = 1;
 }
 ival = ival - qtnt * dvsr;
 dvsr /= 10;
 if (ival == 0 && dvsr <= 100)
 break;
 }
 OutputChar(‘ ‘, theFile);
}

void OutputHex(int ch, int theFile)
{
 OutputHexNibble(ch >> 4, theFile);
 OutputHexNibble(ch, theFile);
}

static void OutputHexNibble(int nib, int theFile)
{
 nib &= 0XF;
 if (nib > 9)    /* A-F */
 OutputChar(nib - 10 + ‘A’, theFile);
 else OutputChar(nib + ‘0’, theFile);
}

/* Write one character to the file */
void OutputChar(int ch, int theFile)
{
 long count = BUFFSIZE;
 buff[buffoff++] = ch;
 if (buffoff >= BUFFSIZE) {
 FSWrite(theFile,&count, buff);
 buffoff = 0;
 }
}

/* Write out whatever is left in the buffer. This must be 
   called before closing. */
void FlushOutBuf(int theFile)
{
 long count;
 count = buffoff;
 FSWrite(theFile,&count, buff);
}

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Motion 5.4.7 - Create and customize Fina...
Motion is designed for video editors, Motion 5 lets you customize Final Cut Pro titles, transitions, and effects. Or create your own dazzling animations in 2D or 3D space, with real-time feedback as... Read more
calibre 5.0.1 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Compressor 4.4.8 - Adds power and flexib...
Compressor adds power and flexibility to Final Cut Pro X export. Customize output settings, work faster with distributed encoding, and tap into a comprehensive set of delivery features. Features:... Read more
Adobe Acrobat Reader 20.012.20048 - View...
Adobe Acrobat Reader allows users to view PDF documents. You may not know what a PDF file is, but you've probably come across one at some point. PDF files are used by companies and even the IRS to... Read more
Adobe Acrobat DC 20.012.20048 - Powerful...
Acrobat DC is available only as a part of Adobe Creative Cloud, and can only be installed and/or updated through Adobe's Creative Cloud app. Adobe Acrobat DC with Adobe Document Cloud services is... Read more
Box Sync 4.0.8009 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Daylite 2020.36.1 - Dynamic business org...
Daylite helps businesses organize themselves with tools such as shared calendars, contacts, tasks, projects, notes, and more. Enable easy collaboration with features such as task and project... Read more
Catalina Cache Cleaner 15.0.6 - Clear ca...
Catalina Cache Cleaner is an award-winning general-purpose tool for macOS X. CCC makes system maintenance simple with an easy point-and-click interface to many macOS X functions. Novice and expert... Read more
Final Cut Pro X 10.4.10 - Professional v...
Final Cut Pro X is a professional video editing solution. Completely redesigned from the ground up, Final Cut Pro adds extraordinary speed, quality, and flexibility to every part of the post-... Read more
Civilization VI 1.3.4 - Next iteration o...
Civilization® VI is the award-winning experience. Expand your empire across the map, advance your culture, and compete against history’s greatest leaders to build a civilization that will stand the... Read more

Latest Forum Discussions

See All

Undercrawl is a procedurally generated r...
Undercrawl is a roguelike dungeon crawler from indie developer Monster Shop Games. It's a genre that's popular in gaming in general but features even more frequently on mobile devices since the shorter, 'run' style of playthrough suits playing in... | Read more »
Distract Yourself With These Great Mobil...
There’s a lot going on right now, and I don’t really feel like trying to write some kind of pithy intro for it. All I’ll say is lots of people have been coming together and helping each other in small ways, and I’m choosing to focus on that as I... | Read more »
BTS Universe Story, the social game that...
Netmarble's highly anticipated social game, BTS Universe Story, is available now for iOS and Android. It's the second collaboration between the hugely successful mobile developer and the K-pop superstars following BTS World. [Read more] | Read more »
The 5 Best Mobile Games Like Hades
Supergiant Games finally released Hades upon the world this week, and we’re loving it. The game plays to all of the studio’s strengths while still retaining a strong sense of identity. It also just so happens to play rather well using the Steam... | Read more »
A Year of Apple Arcade: The Good, The Ba...
Apple Arcade has persisted for just over a year at this point, and although that means I've been busy ranking and re-ranking every game on the service for just about as long, I haven't done much reflection on the service as a whole. [Read more] | Read more »
Animal Restaurant anniversary event team...
Animal idle simulator Animal Restaurant is celebrating its first-year anniversary with a crossover event with popular YouTube series Aaron’s Animals. [Read more] | Read more »
Raziel: Dungeon Arena is a hack 'n...
Raziel: Dungeon Arena is available now on mobile and will appeal to fans of both comic books and old school dungeon crawlers. Not only will you hack 'n' slash your way through mobs of enemies but there's also fully-narrated animated comic to enjoy... | Read more »
Steam Link Spotlight - Hades
Steam Link Spotlight is a feature where we look at PC games that play exceptionally well using the Steam Link app. Our last entry was on Disco Elysium. Read about how it plays using Steam Link over here. | Read more »
Microsoft has acquired ZeniMax Media and...
In the latest of a series of blockbuster moves, Microsoft has now acquired Zenimax Media and its subsidiary, Bethesda Softworks, for $7.5 billion. [Read more] | Read more »
Infinity Mechs is an upcoming idle game...
Indie developer SkullStar studio has announced an upcoming idle mech game called Infinity Mechs. It draws inspiration from the mobile game Iron Saga and has been officially licensed by Game Duchy. It's set to launch for both iOS and Android on... | Read more »

Price Scanner via MacPrices.net

Weekend’s Best MacBook Deal: These 2020 13″ M...
Apple has a full line of Certified Refurbished 2020 13″ 1.4GHz 4-Core Touch Bar MacBook Pros available starting at $1099 and up to $230 off original MSRP. Apple’s one-year warranty is included,... Read more
Clearance 8-core iMac Pro available for $3819...
Apple has Certified Refurbished, clearance, 27″ 3.2GHz 8-Core iMac Pros available $3819 including free shipping. Their price is $1180 off the original MSRP of new models. A standard Apple one-year... Read more
How The Upcoming Mac Transition To Apple Sili...
FEATURE: 09.25.20 – Apple’s plan to transition all of its desktop and notebook computers away from Intel processors to Apple silicon, chips designed by the company itself, has been eclipsed by the... Read more
New low price! Apple Watch SE for only $269
B&H Photo is reporting limited stock of Apple’s new Apple Watch SE GPS models for $10 off MSRP and including free shipping. Their $269 price for the 40mm model is the lowest price we’ve seen so... Read more
Lowest price anywhere: New 13″ 2.0GHz MacBook...
Amazon has new 2020 13″ 2.0GHz/512GB MacBook Pros with 10th generation Intel processors back in stock on sale today for $200 off Apple’s MSRP. Shipping is free. Be sure to purchase the MacBook Pro... Read more
Apple Pro Display XDR with Nano-Texture Glass...
Amazon Apple Premier Partner GatorTec has the Apple Pro Display XDR with Nano-Texture Glass on sale for $5599 shipped, on Amazon. Their price is $400 off Apple’s MSRP, and it’s the cheapest price... Read more
Get a 2019 13″ MacBook Air for only $779 toda...
Apple has clearance, Certified Refurbished, 2019 13″ 1.6GHz/128GB MacBook Airs available again for $779. Each MacBook features a new outer case, comes with a standard Apple one-year warranty, and is... Read more
2020 11″ iPad Pros on sale today for $50-$75...
Apple reseller Expercom has new 2020 11″ Apple iPad Pros on sale for $50-$75 off MSRP, with prices starting at $749. These are the same iPad Pros sold by Apple in their retail and online stores: – 11... Read more
Apple has restocked 2020 13″ MacBook Airs sta...
Apple has restocked Certified Refurbished 2020 13″ MacBook Airs starting at only $849 and up to $200 off the cost of new Airs. Each MacBook features a new outer case, comes with a standard Apple one-... Read more
Apple’s new 8th generation 10.2″ iPads are on...
Amazon is discounting new 2020 8th generation 10.2″ Apple iPads by up to $35 off MSRP with prices starting at only $299. Shipping is free. These are the same iPads sold by Apple in their retail and... Read more

Jobs Board

Bookseller- *Apple* Shop Support - Penn Sta...
…combination of education, certification and experience will be considered. + A+ and/or Apple certified or able to achieve certification within 60 days required. + Read more
Freelance *Apple* Technology Journalist - V...
…freelance basis. Valnet Inc. is looking for journalists with strong knowledge of Apple technology for our website MakeUseOf.com MakeUseOf is one of the largest Read more
*Apple* Certified Macintosh Technician - Exc...
Apple Certified Macintosh Technician Summary Title: Apple Certified Macintosh Technician ID:350 Department:All Location:Bethesda, MD Description Apple Read more
Security Officer ($23.00/Hourly) - *Apple*...
**Security Officer \($23\.00/Hourly\) \- Apple Store** **Description** About NMS Built on a culture of safety and integrity, NMSdelivers award\-winning, integrated Read more
Security Officer ($23.00/Hourly) - *Apple*...
**Security Officer \($23\.00/Hourly\) \- Apple Store** **Description** About NMS Built on a culture of safety and integrity, NMSdelivers award\-winning, integrated Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.