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

Fantastical 2.4.3 - Create calendar even...
Fantastical 2 is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event... Read more
Things 3.2.1 - Elegant personal task man...
Things is a task management solution that helps to organize your tasks in an elegant and intuitive way. Things combines powerful features with simplicity through the use of tags and its intelligent... Read more
A Better Finder Attributes 6.06 - Change...
A Better Finder Attributes is the ultimate file-tweaking tool for OS X. It combines photo-shooting date and file date changing along with a few unique tricks of its own. Change EXIF Timestamps at... Read more
MacCleanse 6.0.5 - $29.95
MacCleanse is the product of thousands of hours of intense research and development. It meticulously scans all of the nooks and crannies of a computer for unnecessary junk that can take up huge... Read more
Smultron 10.0.2 - Easy-to-use, powerful...
Smultron 10 is an elegant and powerful text editor that is easy to use. You can use Smultron 10 to create or edit any text document. Everything from a web page, a note or a script to any single piece... Read more
Capto 1.2.5 - $29.99
Capto (was Voila) is an easy-to-use app that takes capturing, recording, video and image editing to the next level. With an intelligent file manager and quick sharing options, Capto is perfect for... Read more
Cocktail 11.0.1 - General maintenance an...
Cocktail is a general purpose utility for macOS that lets you clean, repair and optimize your Mac. It is a powerful digital toolset that helps hundreds of thousands of Mac users around the world get... Read more
Sketch 47.1 - Design app for UX/UI for i...
Sketch is an innovative and fresh look at vector drawing. Its intentionally minimalist design is based upon a drawing space of unlimited size and layers, free of palettes, panels, menus, windows, and... Read more
Slack 2.8.2 - Collaborative communicatio...
Slack is a collaborative communication app that simplifies real-time messaging, archiving, and search for modern working teams. Version 2.8.2: A small release containing nothing but another Electron... Read more
Path Finder 7.6.1 - Powerful, award-winn...
Path Finder makes you a master of file management. Take full control over your file system. Save your time: compare and synchronize folders, view hidden files, use Dual Pane and full keyboard... Read more

Returner 77 (Games)
Returner 77 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Returner 77 is a cinematic space mystery puzzle game. You are in a giant alien spaceship hovering above Earth, after everything... | Read more »
Dune! guide - how to toe the line and ge...
Publisher Voodoo is at it again with an all new high score chaser -- Dune! In this fast-paced arcade game, you have to propel yourself along sand dunes, gaining enough momentum to jump above the line to score points, while making sure you have... | Read more »
The best deals on the App Store this wee...
Happy Tuesday, dear readers. Your favorite part of the week as officially arrived. It's time to take a look at the best deals in games. Things are admittedly a bit sparse, but there are a few diamonds in the rough to see you through if you're... | Read more »
Be the last person standing in Legacy of...
Yoozoo Games’ popular action MMO Legacy of Discord is getting a huge new update to celebrate its first anniversary. Perhaps the biggest change is the addition of an exciting survival mode titled Last Guardian. This new survival mode will pit you... | Read more »
Home Street guide - how to make friends...
From the creators of Food Street comes Home Street, a new simulation game that tasks you with building a social network and designing a beautiful home. It's a bit like The Sims, but you won't have to worry about the daily chores involved (feeding,... | Read more »
Color Ballz guide - how to bounce to the...
Color Ballz is an addictive new arcade title from Ketchapp Studios. It takes old school mechanics from games like Brickles and puts a fun twist on it. Your job? To catch balls with a paddle and send them back into a chute to be carried back to... | Read more »
Q&A: A-33 Studio explains why Combat...
When it comes to mobile FPS, it’s often tricky to get the fundamentals right on a platform lacking a physical controller, large display and hefty RAM. With Combat Squad: Project Wednesday, A-33 Studio bravely took on the challenge of making a... | Read more »
Taichi Panda 3: Dragon Hunter guide - ti...
Taichi Panda 3: Dragon Hunter launched this week to players all over the world. It's a beautiful mobile MMORPG that blends elements of Eastern and Western fantasy. It reminds us of a mix between World of Warcraft and Jade Empire. MMO's can have a... | Read more »
The best new games we played this week -...
Phew. It has been a week, but now it's time to relax, put your feet up, and enjoy some brand new mobile games. It was a bit of slow week, but there's still plenty of new titles to add to your collection. Here are four of our favorites. [Read... | Read more »
Yoink - Improved Drag and Drop (Product...
Yoink - Improved Drag and Drop 1.0 Device: iOS Universal Category: Productivity Price: $2.99, Version: 1.0 (iTunes) Description: Yoink for iPad and iPhone lets you easily and quickly store items you drag, copy or share, for later use... | Read more »

Price Scanner via MacPrices.net

13″ MacBook Pros on sale for up to $120 off M...
B&H Photo has 2017 13″ MacBook Pros in stock today and on sale for up to $120 off MSRP, each including free shipping plus NY & NJ sales tax only: – 13-inch 2.3GHz/128GB Space Gray MacBook... Read more
15″ MacBook Pros on sale for up to $200 off M...
B&H Photo has 15″ MacBook Pros on sale for up to $200 off MSRP. Shipping is free, and B&H charges sales tax in NY & NJ only: – 15″ 2.8GHz MacBook Pro Space Gray (MPTR2LL/A): $2249, $150... Read more
Roundup of Apple Certified Refurbished iMacs,...
Apple has a full line of Certified Refurbished 2017 21″ and 27″ iMacs available starting at $1019 and ranging up to $350 off original MSRP. Apple’s one-year warranty is standard, and shipping is free... Read more
Sale! 27″ 3.8GHz 5K iMac for $2098, save $201...
Amazon has the 27″ 3.8GHz 5K iMac (MNED2LL/A) on sale today for $2098 including free shipping. Their price is $201 off MSRP, and it’s the lowest price available for this model (Apple’s $1949... Read more
Sale! 10″ Apple WiFi iPad Pros for up to $100...
B&H Photo has 10.5″ WiFi iPad Pros in stock today and on sale for $50-$100 off MSRP. Each iPad includes free shipping, and B&H charges sales tax in NY & NJ only: – 10.5″ 64GB iPad Pro: $... Read more
Apple iMacs on sale for up to $130 off MSRP w...
B&H Photo has 21-inch and 27-inch iMacs in stock and on sale for up to $130 off MSRP including free shipping. B&H charges sales tax in NY & NJ only: – 27″ 3.8GHz iMac (MNED2LL/A): $2179 $... Read more
2017 3.5GHz 6-Core Mac Pro on sale for $2799,...
B&H Photo has the 2017 3.5GHz 6-Core Mac Pro (MD878LL/A) on sale today for $2799 including free shipping plus NY & NJ sales tax only . Their price is $200 off MSRP. Read more
12″ 1.2GHz Space Gray MacBook on sale for $11...
Amazon has the 2017 12″ 1.2GHz Space Gray Retina MacBook on sale for $100 off MSRP. Shipping is free: 12″ 1.2GHz Space Gray MacBook: $1199.99 $100 off MSRP Read more
Bare Bones Software Releases macOS High Sierr...
Bare Bones Software has announced the release and immediate availability of BBEdit 12.0, a significant upgrade to its professional strength text and code editor. BBEdit 12 introduces a new foundation... Read more
Yale Announces Availability of Apple HomeKit-...
Yale Locks & Hardware has announced that Apple HomeKit support for its Assure Lock family is available this month. The new Yale iM1 Network Module, which provides support for the Apple Home app... Read more

Jobs Board

*Apple* News Product Marketing Mgr., Publish...
Job Summary The Apple News Product Marketing Manager will work closely with a cross-functional group to assist in defining and marketing new features and services. Read more
Fraud Analyst, *Apple* Advertising Platform...
Job Summary Apple Ad Platforms has an opportunity to redefine advertising on mobile devices. Apple reaches hundreds of millions of iPhone, iPod touch, and iPad Read more
*Apple* Information Security - Security Data...
Job Summary This role is responsible for helping to strengthen Apple 's information security posture through the identification and curation of security event data. Read more
Lead *Apple* Solution Consultant - Apple In...
…develop a team of diverse partner employees focusing on excellence to deliver the Apple story. Even when you're not present, you will maintain a consistent influence Read more
watchOS Frameworks Engineering Manager, *App...
Job Summary Join the team that is shaping the future of software development for Apple Watch! Apple is looking for an exceptional software engineering leader to Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.