Back to index

tetex-bin  3.0
spaces.c
Go to the documentation of this file.
00001 /* $XConsortium: spaces.c,v 1.4 91/10/10 11:19:16 rws Exp $ */
00002 /* Copyright International Business Machines, Corp. 1991
00003  * All Rights Reserved
00004  * Copyright Lexmark International, Inc. 1991
00005  * All Rights Reserved
00006  *
00007  * License to use, copy, modify, and distribute this software and its
00008  * documentation for any purpose and without fee is hereby granted,
00009  * provided that the above copyright notice appear in all copies and that
00010  * both that copyright notice and this permission notice appear in
00011  * supporting documentation, and that the name of IBM or Lexmark not be
00012  * used in advertising or publicity pertaining to distribution of the
00013  * software without specific, written prior permission.
00014  *
00015  * IBM AND LEXMARK PROVIDE THIS SOFTWARE "AS IS", WITHOUT ANY WARRANTIES OF
00016  * ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY
00017  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
00018  * AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.  THE ENTIRE RISK AS TO THE
00019  * QUALITY AND PERFORMANCE OF THE SOFTWARE, INCLUDING ANY DUTY TO SUPPORT
00020  * OR MAINTAIN, BELONGS TO THE LICENSEE.  SHOULD ANY PORTION OF THE
00021  * SOFTWARE PROVE DEFECTIVE, THE LICENSEE (NOT IBM OR LEXMARK) ASSUMES THE
00022  * ENTIRE COST OF ALL SERVICING, REPAIR AND CORRECTION.  IN NO EVENT SHALL
00023  * IBM OR LEXMARK BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
00024  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
00025  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
00026  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
00027  * THIS SOFTWARE.
00028  */
00029  /* SPACES   CWEB         V0021 ********                             */
00030 /*
00031 :h1 id=spaces.SPACES Module - Handles Coordinate Spaces
00032  
00033 This module is responsible for handling the TYPE1IMAGER "XYspace" object.
00034  
00035 &author. Jeffrey B. Lotspiech (lotspiech@almaden.ibm.com)
00036  
00037  
00038 :h3.Include Files
00039 */
00040 #include "types.h"
00041 #include "objects.h"
00042 #include "spaces.h"
00043 #include "paths.h"
00044 #include "pictures.h"
00045 #include "fonts.h"
00046 #include "arith.h"
00047 #include "trig.h"
00048 
00049 static void FindFfcn();
00050 static void FindIfcn();
00051 /*
00052 :h3.Entry Points Provided to the TYPE1IMAGER User
00053 */
00054  
00055 /*SHARED LINE(S) ORIGINATED HERE*/
00056  
00057 /*
00058 :h3.Entry Points Provided to Other Modules
00059 */
00060  
00061 /*
00062 In addition, other modules call the SPACES module through function
00063 vectors in the "XYspace" structure.  The entry points accessed that
00064 way are "FConvert()", "IConvert()", and "ForceFloat()".
00065 */
00066  
00067 /*SHARED LINE(S) ORIGINATED HERE*/
00068 /*
00069 :h3.Macros and Typedefs Provided to Other Modules
00070  
00071 :h4.Duplicating and Killing Spaces
00072  
00073 Destroying XYspaces is so simple we can do it with a
00074 macro:
00075 */
00076  
00077 /*SHARED LINE(S) ORIGINATED HERE*/
00078 /*
00079 On the other hand, duplicating XYspaces is slightly more difficult
00080 because of the need to keep a unique ID in the space, see
00081 :hdref refid=dupspace..
00082  
00083 :h4.Fixed Point Pel Representation
00084  
00085 We represent pel positions with fixed point numbers.  This does NOT
00086 mean integer, but truly means fixed point, with a certain number
00087 of binary digits (FRACTBITS) representing the fractional part of the
00088 pel.
00089 */
00090  
00091 /*SHARED LINE(S) ORIGINATED HERE*/
00092 /*
00093 :h2.Data Structures for Coordinate Spaces and Points
00094 */
00095 /*
00096 :h3 id=matrix.Matrices
00097  
00098 TYPE1IMAGER uses 2x2 transformation matrices.  We'll use C notation for
00099 such a matrix (M[2][2]), the first index being rows, the second columns.
00100 */
00101  
00102 /*
00103 :h3.The "doublematrix" Structure
00104  
00105 We frequently find it desirable to store both a matrix and its
00106 inverse.  We store these in a "doublematrix" structure.
00107 */
00108  
00109 /*SHARED LINE(S) ORIGINATED HERE*/
00110  
00111 /*
00112 :h3.The "XYspace" Structure
00113  
00114 The XYspace structure represents the XYspace object.
00115 */
00116  
00117 /*SHARED LINE(S) ORIGINATED HERE*/
00118 #define    RESERVED  10      /* 'n' IDs are reserved for invalid & immortal spaces */
00119 /*
00120 */
00121 #define    NEXTID    ((SpaceID < RESERVED) ? (SpaceID = RESERVED) : ++SpaceID)
00122  
00123 static unsigned int SpaceID = 1;
00124  
00125 struct XYspace *CopySpace(S)
00126        register struct XYspace *S;
00127 {
00128        S = (struct XYspace *)Allocate(sizeof(struct XYspace), S, 0);
00129        S->ID = NEXTID;
00130        return(S);
00131 }
00132 /*
00133 :h3.The "fractpoint" Structure
00134  
00135 A fractional point is just a "fractpel" x and y:
00136 */
00137  
00138 /*SHARED LINE(S) ORIGINATED HERE*/
00139  
00140 /*
00141 :h3.Lazy Evaluation of Matrix Inverses
00142  
00143 Calculating the inverse of a matrix is somewhat involved, and we usually
00144 do not need them.  So, we flag whether or not the space has the inverse
00145 already calculated:
00146 */
00147  
00148 #define    HASINVERSE(flag)   ((flag)&0x80)
00149  
00150 /*
00151 The following macro forces a space to have an inverse:
00152 */
00153  
00154 #define    CoerceInverse(S)   if (!HASINVERSE((S)->flag)) { \
00155     MatrixInvert((S)->tofract.normal, (S)->tofract.inverse); (S)->flag |= HASINVERSE(ON); }
00156 /*
00157 :h3.IDENTITY Space
00158  
00159 IDENTITY space is (logically) the space corresponding to the identity
00160 transformation matrix.  However, since all our transformation matrices
00161 have a common FRACTFLOAT scale factor to convert to 'fractpel's, that
00162 is actually what we store in 'tofract' matrix of IDENTITY:
00163 */
00164  
00165 static struct XYspace identity = { SPACETYPE, ISPERMANENT(ON) + ISIMMORTAL(ON)
00166                         + HASINVERSE(ON), 2, /* added 3-26-91 PNM */
00167                         NULL, NULL,
00168                         NULL, NULL, NULL, NULL,
00169                         INVALIDID + 1, 0,
00170                         FRACTFLOAT, 0.0, 0.0, FRACTFLOAT,
00171                         1.0/FRACTFLOAT, 0.0, 0.0, 1.0/FRACTFLOAT,
00172                         0, 0, 0, 0 };
00173 struct XYspace *IDENTITY = &identity;
00174  
00175 /*
00176 */
00177 #define  MAXCONTEXTS   16
00178  
00179 static struct doublematrix contexts[MAXCONTEXTS];
00180  
00181 #ifdef notdef
00182 
00183 static int nextcontext = 1;
00184  
00185 /*SHARED LINE(S) ORIGINATED HERE*/
00186 
00187 #if __STDC__
00188 #define   pointer          void *
00189 #else
00190 #define   pointer          char *
00191 #endif
00192  
00193 /*
00194 :h3.FindDeviceContext() - Find the Context Given a Device
00195  
00196 This routine, given a device, returns the index of the device's
00197 transformation matrix in the context array.  If it cannot find it,
00198 it will allocate a new array entry and fill it out.
00199 */
00200  
00201 static int FindDeviceContext(device)
00202        pointer device;       /* device token                                 */
00203 {
00204        DOUBLE M[2][2];       /* temporary matrix                             */
00205        float Xres,Yres;      /* device  resolution                           */
00206        int orient = -1;      /* device orientation                           */
00207        int rc = -1;          /* return code for QueryDeviceState             */
00208  
00209        if (rc != 0)          /* we only bother with this check once          */
00210                t1_abort("Context:  QueryDeviceState didn't work");
00211  
00212        M[0][0] = M[1][0] = M[0][1] = M[1][1] = 0.0;
00213  
00214        switch (orient) {
00215            case 0:
00216                M[0][0] = Xres;  M[1][1] = -Yres;
00217                break;
00218            case 1:
00219                M[1][0] = Yres;  M[0][1] = Xres;
00220                break;
00221            case 2:
00222                M[0][0] = -Xres;  M[1][1] = Yres;
00223                break;
00224            case 3:
00225                M[1][0] = -Yres;  M[0][1] = -Xres;
00226                break;
00227            default:
00228                t1_abort("QueryDeviceState returned invalid orientation");
00229        }
00230        return(FindContext(M));
00231 }
00232  
00233 /*
00234 :h3.FindContext() - Find the Context Given a Matrix
00235  
00236 This routine, given a matrix, returns the index of that matrix matrix in
00237 the context array.  If it cannot find it, it will allocate a new array
00238 entry and fill it out.
00239 */
00240  
00241 int FindContext(M)
00242        DOUBLE M[2][2];       /* array to search for                          */
00243 {
00244        register int i;       /* loop variable for search                     */
00245        for (i=0; i < nextcontext; i++)
00246                if (M[0][0] == contexts[i].normal[0][0] && M[1][0] == contexts[i].normal[1][0]
00247                    && M[0][1] == contexts[i].normal[0][1] && M[1][1] == contexts[i].normal[1][1])
00248                        break;
00249  
00250        if (i >= nextcontext) {
00251                if (i >= MAXCONTEXTS)
00252                        t1_abort("Context:  out of them");
00253                LONGCOPY(contexts[i].normal, M, sizeof(contexts[i].normal));
00254                MatrixInvert(M, contexts[i].inverse);
00255                nextcontext++;
00256        }
00257  
00258        return(i);
00259 }
00260  
00261 /*
00262 :h3.Context() - Create a Coordinate Space for a Device
00263  
00264 This user operator is implemented by first finding the device context
00265 array index, then transforming IDENTITY space to create an appropriate
00266 cooridnate space.
00267 */
00268  
00269 struct XYspace *Context(device, units)
00270        pointer device;       /* device token                                 */
00271        DOUBLE units;         /* multiples of one inch                        */
00272 {
00273        DOUBLE M[2][2];       /* device transformation matrix                 */
00274        register int n;       /* will hold device context number              */
00275        register struct XYspace *S;  /* XYspace constructed                   */
00276  
00277        IfTrace2((MustTraceCalls),"Context(%x, %f)\n", device, &units);
00278  
00279        ARGCHECK((device == NULL), "Context of NULLDEVICE not allowed",
00280                     NULL, IDENTITY, (0), struct XYspace *);
00281        ARGCHECK((units == 0.0), "Context: bad units", NULL, IDENTITY, (0), struct XYspace *);
00282  
00283        n = FindDeviceContext(device);
00284  
00285        LONGCOPY(M, contexts[n].normal, sizeof(M));
00286  
00287        M[0][0] *= units;
00288        M[0][1] *= units;
00289        M[1][0] *= units;
00290        M[1][1] *= units;
00291  
00292        S = (struct XYspace *)Xform(IDENTITY, M);
00293  
00294        S->context = n;
00295        return(S);
00296 }
00297 #endif
00298  
00299 /*
00300 :h3.ConsiderContext() - Adjust a Matrix to Take Out Device Transform
00301  
00302 Remember, we have :f/x times U times D/ and :f/M/ and and we want :f/x
00303 times U times M times D/.  An easy way to do this is to calculate
00304 :f/D sup <-1> times M times D/, because:
00305 :formula.
00306 x times U times D times D sup <-1> times M times D = x times U times M times D
00307 :formula.
00308 So this subroutine, given an :f/M/and an object, finds the :f/D/ for that
00309 object and modifies :f/M/ so it is :f/D sup <-1> times M times D/.
00310 */
00311  
00312 static void ConsiderContext(obj, M)
00313        register struct xobject *obj;  /* object to be transformed            */
00314        register DOUBLE M[2][2];    /* matrix (may be changed)                */
00315 {
00316        register int context; /* index in contexts array                      */
00317  
00318        if (obj == NULL) return;
00319  
00320        if (ISPATHTYPE(obj->type)) {
00321                struct segment *path = (struct segment *) obj;
00322  
00323                context = path->context;
00324        }
00325        else if (obj->type == SPACETYPE) {
00326                struct XYspace *S = (struct XYspace *) obj;
00327  
00328                context = S->context;
00329        }
00330        else if (obj->type == PICTURETYPE) {
00331 
00332        }
00333        else
00334                context = NULLCONTEXT;
00335  
00336        if (context != NULLCONTEXT) {
00337                MatrixMultiply(contexts[context].inverse, M, M);
00338                MatrixMultiply(M, contexts[context].normal, M);
00339        }
00340 }
00341  
00342 /*
00343 :h2.Conversion from User's X,Y to "fractpel" X,Y
00344  
00345 When the user is building paths (lines, moves, curves, etc.) he passes
00346 the control points (x,y) for the paths together with an XYspace.  We
00347 must convert from the user's (x,y) to our internal representation
00348 which is in pels (fractpels, actually).  This involves transforming
00349 the user's (x,y) under the coordinate space transformation.  It is
00350 important that we do this quickly.  So, we store pointers to different
00351 conversion functions right in the XYspace structure.  This allows us
00352 to have simpler special case functions for the more commonly
00353 encountered types of transformations.
00354  
00355 :h3.Convert(), IConvert(), and ForceFloat() - Called Through "XYspace" Structure
00356  
00357 These are functions that fit in the "convert" and "iconvert" function
00358 pointers in the XYspace structure.  They call the "xconvert", "yconvert",
00359 "ixconvert", and "iyconvert" as appropriate to actually do the work.
00360 These secondary routines come in many flavors to handle different
00361 special cases as quickly as possible.
00362 */
00363  
00364 void FXYConvert(pt, S, x, y)
00365        register struct fractpoint *pt;  /* point to set                      */
00366        register struct XYspace *S;  /* relevant coordinate space             */
00367        register DOUBLE x,y;  /* user's coordinates of point                  */
00368 {
00369        pt->x = (*S->xconvert)(S->tofract.normal[0][0], S->tofract.normal[1][0], x, y);
00370        pt->y = (*S->yconvert)(S->tofract.normal[0][1], S->tofract.normal[1][1], x, y);
00371 }
00372  
00373 void IXYConvert(pt, S, x, y)
00374        register struct fractpoint *pt;  /* point to set                      */
00375        register struct XYspace *S;  /* relevant coordinate space             */
00376        register LONG x,y;    /* user's coordinates of point                  */
00377 {
00378        pt->x = (*S->ixconvert)(S->itofract[0][0], S->itofract[1][0], x, y);
00379        pt->y = (*S->iyconvert)(S->itofract[0][1], S->itofract[1][1], x, y);
00380 }
00381  
00382 /*
00383 ForceFloat is a substitute for IConvert(), when we just do not have
00384 enough significant digits in the coefficients to get high enough
00385 precision in the answer with fixed point arithmetic.  So, we force the
00386 integers to floats, and do the arithmetic all with floats:
00387 */
00388  
00389 void ForceFloat(pt, S, x, y)
00390        register struct fractpoint *pt;  /* point to set                      */
00391        register struct XYspace *S;  /* relevant coordinate space             */
00392        register LONG x,y;    /* user's coordinates of point                  */
00393 {
00394        (*S->convert)(pt, S, (DOUBLE) x, (DOUBLE) y);
00395 }
00396  
00397 /*
00398 :h3.FXYboth(), FXonly(), FYonly() - Floating Point Conversion
00399  
00400 These are the routines we use when the user has given us floating
00401 point numbers for x and y. FXYboth() is the general purpose routine;
00402 FXonly() and FYonly() are special cases when one of the coefficients
00403 is 0.0.
00404 */
00405  
00406 fractpel FXYboth(cx, cy, x, y)
00407        register DOUBLE cx,cy;  /* x and y coefficients                       */
00408        register DOUBLE x,y;  /* user x,y                                     */
00409 {
00410        register DOUBLE r;    /* temporary float                              */
00411  
00412        r = x * cx + y * cy;
00413        return((fractpel) r);
00414 }
00415  
00416 /*ARGSUSED*/
00417 fractpel FXonly(cx, cy, x, y)
00418        register DOUBLE cx,cy;  /* x and y coefficients                       */
00419        register DOUBLE x,y;  /* user x,y                                     */
00420 {
00421        register DOUBLE r;    /* temporary float                              */
00422  
00423        r = x * cx;
00424        return((fractpel) r);
00425 }
00426  
00427 /*ARGSUSED*/
00428 fractpel FYonly(cx, cy, x, y)
00429        register DOUBLE cx,cy;  /* x and y coefficients                       */
00430        register DOUBLE x,y;  /* user x,y                                     */
00431 {
00432        register DOUBLE r;    /* temporary float                              */
00433  
00434        r = y * cy;
00435        return((fractpel) r);
00436 }
00437  
00438 /*
00439 :h3.IXYboth(), IXonly(), IYonly() - Simple Integer Conversion
00440  
00441 These are the routines we use when the user has given us integers for
00442 x and y, and the coefficients have enough significant digits to
00443 provide precise answers with only "long" (32 bit?) multiplication.
00444 IXYboth() is the general purpose routine; IXonly() and IYonly() are
00445 special cases when one of the coefficients is 0.
00446 */
00447  
00448 fractpel IXYboth(cx, cy, x, y)
00449        register fractpel cx,cy;  /* x and y coefficients                     */
00450        register LONG x,y;    /* user x,y                                     */
00451 {
00452        return(x * cx + y * cy);
00453 }
00454  
00455 /*ARGSUSED*/
00456 fractpel IXonly(cx, cy, x, y)
00457        register fractpel cx,cy;  /* x and y coefficients                     */
00458        register LONG x,y;    /* user x,y                                     */
00459 {
00460        return(x * cx);
00461 }
00462  
00463 /*ARGSUSED*/
00464 fractpel IYonly(cx, cy, x, y)
00465        register fractpel cx,cy;  /* x and y coefficients                     */
00466        register LONG x,y;    /* user x,y                                     */
00467 {
00468        return(y * cy);
00469 }
00470  
00471  
00472 /*
00473 :h3.FPXYboth(), FPXonly(), FPYonly() - More Involved Integer Conversion
00474  
00475 These are the routines we use when the user has given us integers for
00476 x and y, but the coefficients do not have enough significant digits to
00477 provide precise answers with only "long" (32 bit?)  multiplication.
00478 We have increased the number of significant bits in the coefficients
00479 by FRACTBITS; therefore we must use "double long" (64 bit?)
00480 multiplication by calling FPmult().  FPXYboth() is the general purpose
00481 routine; FPXonly() and FPYonly() are special cases when one of the
00482 coefficients is 0.
00483  
00484 Note that it is perfectly possible for us to calculate X with the
00485 "FP" method and Y with the "I" method, or vice versa.  It all depends
00486 on how the functions in the XYspace structure are filled out.
00487 */
00488  
00489 fractpel FPXYboth(cx, cy, x, y)
00490        register fractpel cx,cy;  /* x and y coefficients                     */
00491        register LONG x,y;    /* user x,y                                     */
00492 {
00493        return( FPmult(x, cx) + FPmult(y, cy) );
00494 }
00495  
00496 /*ARGSUSED*/
00497 fractpel FPXonly(cx, cy, x, y)
00498        register fractpel cx,cy;  /* x and y coefficients                     */
00499        register LONG x,y;    /* user x,y                                     */
00500 {
00501        return( FPmult(x, cx) );
00502 }
00503  
00504 /*ARGSUSED*/
00505 fractpel FPYonly(cx, cy, x, y)
00506        register fractpel cx,cy;  /* x and y coefficients                     */
00507        register LONG x,y;    /* user x,y                                     */
00508 {
00509        return( FPmult(y, cy) );
00510 }
00511  
00512  
00513  
00514 /*
00515 :h3.FillOutFcns() - Determine the Appropriate Functions to Use for Conversion
00516  
00517 This function fills out the "convert" and "iconvert" function pointers
00518 in an XYspace structure, and also fills the "helper"
00519 functions that actually do the work.
00520 */
00521  
00522 static void FillOutFcns(S)
00523        register struct XYspace *S;  /* functions will be set in this structure */
00524 {
00525        S->convert = FXYConvert;
00526        S->iconvert = IXYConvert;
00527  
00528        FindFfcn(S->tofract.normal[0][0], S->tofract.normal[1][0], &S->xconvert);
00529        FindFfcn(S->tofract.normal[0][1], S->tofract.normal[1][1], &S->yconvert);
00530        FindIfcn(S->tofract.normal[0][0], S->tofract.normal[1][0],
00531                 &S->itofract[0][0], &S->itofract[1][0], &S->ixconvert);
00532        FindIfcn(S->tofract.normal[0][1], S->tofract.normal[1][1],
00533                 &S->itofract[0][1], &S->itofract[1][1], &S->iyconvert);
00534  
00535        if (S->ixconvert == NULL || S->iyconvert == NULL)
00536                 S->iconvert = ForceFloat;
00537 }
00538  
00539 /*
00540 :h4.FindFfcn() - Subroutine of FillOutFcns() to Fill Out Floating Functions
00541  
00542 This function tests for the special case of one of the coefficients
00543 being zero:
00544 */
00545  
00546 static void FindFfcn(cx, cy, fcnP)
00547        register DOUBLE cx,cy;  /* x and y coefficients                       */
00548        register fractpel (**fcnP)();  /* pointer to function to set          */
00549 {
00550        if (cx == 0.0)
00551                *fcnP = FYonly;
00552        else if (cy == 0.0)
00553                *fcnP = FXonly;
00554        else
00555                *fcnP = FXYboth;
00556 }
00557  
00558 /*
00559 :h4.FindIfcn() - Subroutine of FillOutFcns() to Fill Out Integer Functions
00560  
00561 There are two types of integer functions, the 'I' type and the 'FP' type.
00562 We use the I type functions when we are satisfied with simple integer
00563 arithmetic.  We used the FP functions when we feel we need higher
00564 precision (but still fixed point) arithmetic.  If all else fails,
00565 we store a NULL indicating that this we should do the conversion in
00566 floating point.
00567 */
00568  
00569 static void FindIfcn(cx, cy, icxP, icyP, fcnP)
00570        register DOUBLE cx,cy;  /* x and y coefficients                       */
00571        register fractpel *icxP,*icyP;  /* fixed point coefficients to set    */
00572        register fractpel (**fcnP)();  /* pointer to function to set          */
00573 {
00574        register fractpel imax;  /* maximum of cx and cy                      */
00575  
00576        *icxP = cx;
00577        *icyP = cy;
00578  
00579        if (cx != (float) (*icxP) || cy != (float) (*icyP)) {
00580 /*
00581 At this point we know our integer approximations of the coefficients
00582 are not exact.  However, we will still use them if the maximum
00583 coefficient will not fit in a 'fractpel'.   Of course, we have little
00584 choice at that point, but we haven't lost that much precision by
00585 staying with integer arithmetic.  We have enough significant digits
00586 so that
00587 any error we introduce is less than one part in 2:sup/16/.
00588 */
00589  
00590                imax = MAX(ABS(*icxP), ABS(*icyP));
00591                if (imax < (fractpel) (1<<(FRACTBITS-1)) ) {
00592 /*
00593 At this point we know our integer approximations just do not have
00594 enough significant digits for accuracy.  We will add FRACTBITS
00595 significant digits to the coefficients (by multiplying them by
00596 1<<FRACTBITS) and go to the "FP" form of the functions.  First, we
00597 check to see if we have ANY significant digits at all (that is, if
00598 imax == 0).  If we don't, we suspect that adding FRACTBITS digits
00599 won't help, so we punt the whole thing.
00600 */
00601                        if (imax == 0) {
00602                                *fcnP = NULL;
00603                                return;
00604                        }
00605                        cx *= FRACTFLOAT;
00606                        cy *= FRACTFLOAT;
00607                        *icxP = cx;
00608                        *icyP = cy;
00609                        *fcnP = FPXYboth;
00610                }
00611                else
00612                        *fcnP = IXYboth;
00613        }
00614        else
00615                *fcnP = IXYboth;
00616 /*
00617 Now we check for special cases where one coefficient is zero (after
00618 integer conversion):
00619 */
00620        if (*icxP == 0)
00621                *fcnP = (*fcnP == FPXYboth) ? FPYonly : IYonly;
00622        else if (*icyP == 0)
00623                *fcnP = (*fcnP == FPXYboth) ? FPXonly : IXonly;
00624 }
00625 /*
00626 :h3.UnConvert() - Find User Coordinates From FractPoints
00627  
00628 The interesting thing with this routine is that we avoid calculating
00629 the matrix inverse of the device transformation until we really need
00630 it, which is to say, until this routine is called for the first time
00631 with a given coordinate space.
00632  
00633 We also only calculate it only once.  If the inverted matrix is valid,
00634 we don't calculate it; if not, we do.  We never expect matrices with
00635 zero determinants, so by convention, we mark the matrix is invalid by
00636 marking both X terms zero.
00637 */
00638  
00639 void UnConvert(S, pt, xp, yp)
00640        register struct XYspace *S;  /* relevant coordinate space             */
00641        register struct fractpoint *pt;  /* device coordinates                */
00642        DOUBLE *xp,*yp;       /* where to store resulting x,y                 */
00643 {
00644        DOUBLE x,y;
00645  
00646        CoerceInverse(S);
00647        x = pt->x;
00648        y = pt->y;
00649        *xp = S->tofract.inverse[0][0] * x + S->tofract.inverse[1][0] * y;
00650        *yp = S->tofract.inverse[0][1] * x + S->tofract.inverse[1][1] * y;
00651 }
00652  
00653 /*
00654 :h2.Transformations
00655 */
00656 /*
00657 :h3 id=xform.Xform() - Transform Object in X and Y
00658  
00659 TYPE1IMAGER wants transformations of objects like paths to be identical
00660 to transformations of spaces.  For example, if you scale a line(1,1)
00661 by 10 it should yield the same result as generating the line(1,1) in
00662 a coordinate space that has been scaled by 10.
00663  
00664 We handle fonts by storing the accumulated transform, for example, SR
00665 (accumulating on the right).  Then when we map the font through space TD,
00666 for example, we multiply the accumulated font transform on the left by
00667 the space transform on the right, yielding SRTD in this case.  We will
00668 get the same result if we did S, then R, then T on the space and mapping
00669 an unmodified font through that space.
00670 */
00671  
00672 struct xobject *t1_Xform(obj, M)
00673        register struct xobject *obj;  /* object to transform                 */
00674        register DOUBLE M[2][2];    /* transformation matrix                  */
00675 {
00676        if (obj == NULL)
00677                return(NULL);
00678  
00679        if (obj->type == FONTTYPE) {
00680                register struct font *F = (struct font *) obj;
00681  
00682                F = UniqueFont(F);
00683                return((struct xobject*)F);
00684        }
00685        if (obj->type == PICTURETYPE) {
00686 /*
00687 In the case of a picture, we choose both to update the picture's
00688 transformation matrix and keep the handles up to date.
00689 */
00690                register struct picture *P = (struct picture *) obj;
00691                register struct segment *handles;  /* temporary path to transform handles */
00692  
00693                P = UniquePicture(P);
00694                handles = PathSegment(LINETYPE, P->origin.x, P->origin.y);
00695                handles = Join(handles,
00696                               PathSegment(LINETYPE, P->ending.x, P->ending.y) );
00697                handles = (struct segment *)Xform((struct xobject *) handles, M);
00698                P->origin = handles->dest;
00699                P->ending = handles->link->dest;
00700                KillPath(handles);
00701                return((struct xobject *)P);
00702        }
00703  
00704        if (ISPATHTYPE(obj->type)) {
00705                struct XYspace pseudo;  /* local temporary space              */
00706                PseudoSpace(&pseudo, M);
00707                return((struct xobject *) PathTransform(obj, &pseudo));
00708        }
00709  
00710  
00711        if (obj->type == SPACETYPE) {
00712                register struct XYspace *S = (struct XYspace *) obj;
00713  
00714 /* replaced ISPERMANENT(S->flag) with S->references > 1 3-26-91 PNM */
00715                if (S->references > 1)
00716                        S = CopySpace(S);
00717                else
00718                        S->ID = NEXTID;
00719  
00720                MatrixMultiply(S->tofract.normal, M, S->tofract.normal);
00721                /*
00722                * mark inverted matrix invalid:
00723                */
00724                S->flag &= ~HASINVERSE(ON);
00725  
00726                FillOutFcns(S);
00727                return((struct xobject *) S);
00728        }
00729  
00730        return(ArgErr("Untransformable object", obj, obj));
00731 }
00732  
00733 /*
00734 :h3.Transform() - Transform an Object
00735  
00736 This is the external user's entry point.
00737 */
00738 struct xobject *t1_Transform(obj, cxx, cyx, cxy, cyy)
00739        struct xobject *obj;
00740        DOUBLE cxx,cyx,cxy,cyy;  /* 2x2 transform matrix elements in row order */
00741 {
00742        DOUBLE M[2][2];
00743  
00744        IfTrace1((MustTraceCalls),"Transform(%z,", obj);
00745        IfTrace4((MustTraceCalls)," %f %f %f %f)\n", &cxx, &cyx, &cxy, &cyy);
00746  
00747        M[0][0] = cxx;
00748        M[0][1] = cyx;
00749        M[1][0] = cxy;
00750        M[1][1] = cyy;
00751        ConsiderContext(obj, M);
00752        return(Xform(obj, M));
00753 }
00754 /*
00755 :h3.Scale() - Special Case of Transform()
00756  
00757 This is a user operator.
00758 */
00759  
00760 struct xobject *t1_Scale(obj, sx, sy)
00761        struct xobject *obj;  /* object to scale                              */
00762        DOUBLE sx,sy;         /* scale factors in x and y                     */
00763 {
00764        DOUBLE M[2][2];
00765        IfTrace3((MustTraceCalls),"Scale(%z, %f, %f)\n", obj, &sx, &sy);
00766        M[0][0] = sx;
00767        M[1][1] = sy;
00768        M[1][0] = M[0][1] = 0.0;
00769        ConsiderContext(obj, M);
00770        return(Xform(obj, M));
00771 }
00772  
00773 /*
00774 :h3 id=rotate.Rotate() - Special Case of Transform()
00775  
00776 We special-case different settings of 'degrees' for performance
00777 and accuracy within the DegreeSin() and DegreeCos() routines themselves.
00778 */
00779  
00780 #ifdef notdef
00781 struct xobject *xiRotate(obj, degrees)
00782        struct xobject *obj;  /* object to be transformed                     */
00783        DOUBLE degrees;       /* degrees of COUNTER-clockwise rotation        */
00784 {
00785        DOUBLE M[2][2];
00786  
00787  
00788        IfTrace2((MustTraceCalls),"Rotate(%z, %f)\n", obj, &degrees);
00789  
00790        M[0][0] = M[1][1] = DegreeCos(degrees);
00791        M[1][0] = - (M[0][1] = DegreeSin(degrees));
00792        ConsiderContext(obj, M);
00793        return(Xform(obj, M));
00794 }
00795 #endif
00796  
00797 /*
00798 :h3.PseudoSpace() - Build a Coordinate Space from a Matrix
00799  
00800 Since we have built all this optimized code that, given an (x,y) and
00801 a coordinate space, yield transformed (x,y), it seems a shame not to
00802 use the same logic when we need to multiply an (x,y) by an arbitrary
00803 matrix that is not (initially) part of a coordinate space.  This
00804 subroutine takes the arbitrary matrix and builds a coordinate
00805 space, with all its nifty function pointers.
00806 */
00807  
00808 void PseudoSpace(S, M)
00809        struct XYspace *S;    /* coordinate space structure to fill out       */
00810        DOUBLE M[2][2];       /* matrix that will become 'tofract.normal'     */
00811 {
00812        S->type = SPACETYPE;
00813        S->flag = ISPERMANENT(ON) + ISIMMORTAL(ON);
00814        S->references = 2;   /* 3-26-91 added PNM  */
00815        S->tofract.normal[0][0] = M[0][0];
00816        S->tofract.normal[1][0] = M[1][0];
00817        S->tofract.normal[0][1] = M[0][1];
00818        S->tofract.normal[1][1] = M[1][1];
00819  
00820        FillOutFcns(S);
00821 }
00822  
00823 /*
00824 :h2 id=matrixa.Matrix Arithmetic
00825  
00826 Following the convention in Newman and Sproull, :hp1/Interactive
00827 Computer Graphics/,
00828 matrices are organized:
00829 :xmp.
00830        | cxx   cyx |
00831        | cxy   cyy |
00832 :exmp.
00833 A point is horizontal, for example:
00834 :xmp.
00835        [ x y ]
00836 :exmp.
00837 This means that:
00838 :formula/x prime = cxx times x + cxy times y/
00839 :formula/y prime = cyx times x + cyy times y/
00840 I've seen the other convention, where transform matrices are
00841 transposed, equally often in the literature.
00842 */
00843  
00844 /*
00845 :h3.MatrixMultiply() - Implements Multiplication of Two Matrices
00846  
00847 Implements matrix multiplication, A * B = C.
00848  
00849 To remind myself, matrix multiplication goes rows of A times columns
00850 of B.
00851 The output matrix may be the same as one of the input matrices.
00852 */
00853 void MatrixMultiply(A, B, C)
00854        register DOUBLE A[2][2],B[2][2];  /* input matrices                   */
00855        register DOUBLE C[2][2];    /* output matrix                          */
00856 {
00857        register DOUBLE txx,txy,tyx,tyy;
00858  
00859        txx = A[0][0] * B[0][0] + A[0][1] * B[1][0];
00860        txy = A[1][0] * B[0][0] + A[1][1] * B[1][0];
00861        tyx = A[0][0] * B[0][1] + A[0][1] * B[1][1];
00862        tyy = A[1][0] * B[0][1] + A[1][1] * B[1][1];
00863  
00864        C[0][0] = txx;
00865        C[1][0] = txy;
00866        C[0][1] = tyx;
00867        C[1][1] = tyy;
00868 }
00869 /*
00870 :h3.MatrixInvert() - Invert a Matrix
00871  
00872 My reference for matrix inversion was :hp1/Elementary Linear Algebra/
00873 by Paul C. Shields, Worth Publishers, Inc., 1968.
00874 */
00875 void MatrixInvert(M, Mprime)
00876        DOUBLE M[2][2];       /* input matrix                                 */
00877        DOUBLE Mprime[2][2];    /* output inverted matrix                     */
00878 {
00879        register DOUBLE D;    /* determinant of matrix M                      */
00880        register DOUBLE txx,txy,tyx,tyy;
00881  
00882        txx = M[0][0];
00883        txy = M[1][0];
00884        tyx = M[0][1];
00885        tyy = M[1][1];
00886  
00887        D = M[1][1] * M[0][0] - M[1][0] * M[0][1];
00888        if (D == 0.0)
00889                t1_abort("MatrixInvert:  can't");
00890  
00891        Mprime[0][0] = tyy / D;
00892        Mprime[1][0] = -txy / D;
00893        Mprime[0][1] = -tyx / D;
00894        Mprime[1][1] = txx / D;
00895 }
00896 /*
00897 :h2.Initialization, Queries, and Debug
00898 */
00899 /*
00900 :h3.InitSpaces() - Initialize Constant Spaces
00901  
00902 For compatibility, we initialize a coordinate space called USER which
00903 maps 72nds of an inch to pels on the default device.
00904 */
00905  
00906 struct XYspace *USER = &identity;
00907  
00908 void InitSpaces()
00909 {
00910        extern char *DEFAULTDEVICE;
00911  
00912        IDENTITY->type = SPACETYPE;
00913        FillOutFcns(IDENTITY);
00914  
00915        contexts[NULLCONTEXT].normal[1][0]
00916              = contexts[NULLCONTEXT].normal[0][1]
00917              = contexts[NULLCONTEXT].inverse[1][0]
00918              = contexts[NULLCONTEXT].inverse[0][1] = 0.0;
00919        contexts[NULLCONTEXT].normal[0][0]
00920              = contexts[NULLCONTEXT].normal[1][1]
00921              = contexts[NULLCONTEXT].inverse[0][0]
00922              = contexts[NULLCONTEXT].inverse[1][1] = 1.0;
00923  
00924        USER->flag |= ISIMMORTAL(ON);
00925        CoerceInverse(USER);
00926 }
00927 /*
00928 :h3.QuerySpace() - Returns the Transformation Matrix of a Space
00929  
00930 Since the tofract matrix of an XYspace includes the scale factor
00931 necessary to produce fractpel results (i.e., FRACTFLOAT), this
00932 must be taken out before we return the matrix to the user.  Fortunately,
00933 this is simple:  just multiply by the inverse of IDENTITY!
00934 */
00935  
00936 void QuerySpace(S, cxxP, cyxP, cxyP, cyyP)
00937        register struct XYspace *S;  /* space asked about                     */
00938        register DOUBLE *cxxP,*cyxP,*cxyP,*cyyP;  /* where to put answer      */
00939 {
00940        DOUBLE M[2][2];       /* temp matrix to build user's answer           */
00941  
00942        if (S->type != SPACETYPE) {
00943                ArgErr("QuerySpace: not a space", S, NULL);
00944                return;
00945        }
00946        MatrixMultiply(S->tofract.normal, IDENTITY->tofract.inverse, M);
00947        *cxxP = M[0][0];
00948        *cxyP = M[1][0];
00949        *cyxP = M[0][1];
00950        *cyyP = M[1][1];
00951 }
00952  
00953 /*
00954 :h3.FormatFP() - Format a Fixed Point Pel
00955  
00956 We format the pel as "dddd.XXXX", where XX's are hexidecimal digits,
00957 and the dd's are decimal digits.  This might be a little confusing
00958 mixing hexidecimal and decimal like that, but it is convenient
00959 to use for debug.
00960  
00961 We make sure we have N (FRACTBITS/4) digits past the decimal point.
00962 */
00963 #define  FRACTMASK   ((1<<FRACTBITS)-1)  /* mask for fractional part         */
00964  
00965 void FormatFP(str, fpel)
00966        register char *str;  /* output str                              */
00967        register fractpel fpel; /* fractional pel input                       */
00968 {
00969        char temp[8];
00970        register char *s;
00971        register char *sign;
00972  
00973        if (fpel < 0) {
00974                sign = "-";
00975                fpel = -fpel;
00976        }
00977        else
00978                sign = "";
00979  
00980        sprintf(temp, "000%x", fpel & FRACTMASK);
00981        s = temp + strlen(temp) - (FRACTBITS/4);
00982  
00983        sprintf(str, "%s%d.%sx", sign, fpel >> FRACTBITS, s);
00984 }
00985  
00986 /*
00987 :h3.DumpSpace() - Display a Coordinate Space
00988 */
00989 /*ARGSUSED*/
00990 void DumpSpace(S)
00991        register struct XYspace *S;
00992 {
00993        IfTrace4(TRUE,"--Coordinate space at %x,ID=%d,convert=%x,iconvert=%x\n",
00994                    S, S->ID, S->convert, S->iconvert);
00995        IfTrace2(TRUE,"             |  %12.3f  %12.3f  |",
00996                    &S->tofract.normal[0][0], &S->tofract.normal[0][1]);
00997        IfTrace2(TRUE,"   [  %p  %p ]\n", S->itofract[0][0], S->itofract[0][1]);
00998        IfTrace2(TRUE,"             |  %12.3f  %12.3f  |",
00999                    &S->tofract.normal[1][0], &S->tofract.normal[1][1]);
01000        IfTrace2(TRUE,"   [  %p  %p ]\n", S->itofract[1][0], S->itofract[1][1]);
01001 }