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

GIMP 2.8.14p2 - Powerful, free image edi...
GIMP is a multi-platform photo manipulation tool. GIMP is an acronym for GNU Image Manipulation Program. The GIMP is suitable for a variety of image manipulation tasks, including photo retouching,... Read more
Sandvox 2.10.2 - Easily build eye-catchi...
Sandvox is for Mac users who want to create a professional looking website quickly and easily. With Sandvox, you don't need to be a Web genius to build a stylish, feature-rich, standards-compliant... Read more
LibreOffice 5.0.1.2 - Free, open-source...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
f.lux 36.1 - Adjusts the color of your d...
f.lux makes the color of your computer's display adapt to the time of day, warm at night and like sunlight during the day. Ever notice how people texting at night have that eerie blue glow? Or wake... Read more
VirtualBox 5.0.2 - x86 virtualization so...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
File Juicer 4.43 - Extract images, video...
File Juicer is a drag-and-drop can opener and data archaeologist. Its specialty is to find and extract images, video, audio, or text from files which are hard to open in other ways. In computer... Read more
Apple MainStage 3.2 - Live performance t...
MainStage 3 makes it easy to bring to the stage all the same instruments and effects that you love in your recording. Everything from the Sound Library and Smart Controls you're familiar with from... Read more
formZ 8.5 - Solid and surface modeler. (...
formZ pro is a powerful 3D design application featuring a variety of modeling personalities and tools with an easy to use interface to express and communicate your imagination. It is based on... Read more
PopChar 7.2 - Floating window shows avai...
We're also selling a 5-license family pack for only $25.99! PopChar helps you get the most out of your font collection. With its crystal-clear interface, PopChar provides a frustration-free way to... Read more
FileMaker Pro 14.0.2 - Powerful, easy-to...
FileMaker Pro is powerful, easy-to-use software used to create custom solutions for your business that run on iPad, iPhone, Windows, Mac and the web. Use FileMaker Pro to manage and share information... Read more

ReBoard: Revolutionary Keyboard (Utilit...
ReBoard: Revolutionary Keyboard 1.0 Device: iOS Universal Category: Utilities Price: $1.99, Version: 1.0 (iTunes) Description: Do everything within the keyboard without switching apps! If you are in WhatsApp, how do you schedule a... | Read more »
Tiny Empire (Games)
Tiny Empire 1.1.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.1.3 (iTunes) Description: Launch cannonballs and blow tiny orcs into thousands of pieces in this intuitive fantasy-themed puzzle shooter! Embark on an... | Read more »
Astropad Mini (Productivity)
Astropad Mini 1.0 Device: iOS iPhone Category: Productivity Price: $4.99, Version: 1.0 (iTunes) Description: *** 50% off introductory price! ​*** Get the high-end experience of a Wacom tablet at a fraction of the price with Astropad... | Read more »
Emo Chorus (Music)
Emo Chorus 1.0.0 Device: iOS Universal Category: Music Price: $1.99, Version: 1.0.0 (iTunes) Description: Realistic Choir simulator ranging from simple Chorus emulation to full ensemble Choir with 128 members. ### introductory offer... | Read more »
Forest Spirit (Games)
Forest Spirit 1.0.5 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.5 (iTunes) Description: | Read more »
Ski Safari 2 (Games)
Ski Safari 2 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: The world's most fantastical, fun, family-friendly skiing game is back and better than ever! Play as Sven's sister Evana, share... | Read more »
Lara Croft GO (Games)
Lara Croft GO 1.0.47768 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.47768 (iTunes) Description: Lara Croft GO is a turn based puzzle-adventure set in a long-forgotten world. Explore the ruins of an ancient... | Read more »
Whispering Willows (Games)
Whispering Willows 1.23 Device: iOS Universal Category: Games Price: $4.99, Version: 1.23 (iTunes) Description: **LAUNCH SALE 50% OFF** - Whispering Willows is on sale for 50% off ($4.99) until September 9th. | Read more »
Calvino Noir (Games)
Calvino Noir 1.1 Device: iOS iPhone Category: Games Price: $3.99, Version: 1.1 (iTunes) Description: The film noir stealth game. Calvino Noir is the exploratory, sneaking adventure through the 1930s European criminal underworld.... | Read more »
Angel Sword (Games)
Angel Sword 1.0 Device: iOS Universal Category: Games Price: $6.99, Version: 1.0 (iTunes) Description: Prepare to adventure in the most epic full scale multiplayer 3D RPG for mobile! Experience amazing detailed graphics in full HD.... | Read more »

Price Scanner via MacPrices.net

Tablet Screen Sizes Expanding as iPad Pro App...
Larger screen sizes are gaining favor as the tablet transforms into a productivity device, with shipments growing 185 percent year-over-year in 2015. According to a new Strategy Analytics’ Tablet... Read more
Today Only: Save US$50 on Adobe Elements 13;...
Keep the memories. lose the distractions. Summer’s winding down and it’s time to turn almost perfect shots into picture perfect memories with Elements 13. And get the power to edit both photos and... Read more
1.4GHz Mac mini on sale for $449, save $50
Best Buy has the 1.4GHz Mac mini on sale for $50 off MSRP on their online store. Choose free shipping or free local store pickup (if available). Price for online orders only, in-store price may vary... Read more
12-inch 1.1GHz Gold MacBook on sale for $1149...
B&H Photo has the 12″ 1.1GHz Gold Retina MacBook on sale for $1149.99 including free shipping plus NY sales tax only. Their price is $150 off MSRP, and it’s the lowest price available for this... Read more
27-inch 3.3GHz 5K iMac on sale for $1849, sav...
Best Buy has the 27″ 3.3GHz 5K iMac on sale for $1849.99. Their price is $150 off MSRP, and it’s the lowest price available for this model. Choose free shipping or free local store pickup (if... Read more
Worldwide Tablet Shipments Expected to Declin...
Does Apple badly need a touchscreen convertible/hybrid laptop MacBook? Yes, judging from a new market forecast from the International Data Corporation (IDC) Worldwide Quarterly Tablet Tracker, which... Read more
Continued PC Shipment Shrinkage Expected Thro...
Worldwide PC shipments are expected to fall by -8.7 percent in 2015 and not stabilize until 2017, according to the latest International Data Corporation (IDC) Worldwide Quarterly PC Tracker data. The... Read more
Imminent iPhone 6s Announcement Leads To 103%...
NextWorth Solutions, with its online and in-store electronics trade-in programs including http://NextWorth.com, reports that it has experienced a 103 percent surge in quoted trade-in values over the... Read more
Weekend Deal: 13-inch Retina MacBook Pros for...
Save up to $100 on the purchase of a new 2015 13″ Retina MacBook Pro at the following resellers this weekend. Shipping is free with each model: 2.7GHz/128GB MSRP $1299 2.7GHz/... Read more
13-inch 2.5GHz MacBook Pro on sale for $999,...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $999.99 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more

Jobs Board

*Apple* Solutions Consultant - Retail Sales...
**Job Summary** As an Apple Solutions Consultant (ASC) you are the link between our customers and our products. Your role is to drive the Apple business in a retail Read more
Product Design Engineer - *Apple* Watch - A...
**Job Summary** Product Design Engineer Job Description As a member of Apple 's Watch product design team, you will help to create the next generation of the world's Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Software QA Engineer, *Apple* Pay Security...
Changing the world is all in a day039s work at Apple . If you love innovation, here039s your chance to make a career of it. You039ll work hard. But the job comes with Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.