/* * DBcellbox.c -- * * Procedures for calculating and changing cell bounding boxes, * and for manipulating arrays of cells. * * ********************************************************************* * * Copyright (C) 1985, 1990 Regents of the University of California. * * * Permission to use, copy, modify, and distribute this * * * software and its documentation for any purpose and without * * * fee is hereby granted, provided that the above copyright * * * notice appear in all copies. The University of California * * * makes no representations about the suitability of this * * * software for any purpose. It is provided "as is" without * * * express or implied warranty. Export of this software outside * * * of the United States of America may require an export license. * * ********************************************************************* */ #ifndef lint static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/database/DBcellbox.c,v 1.3 2008/12/11 18:36:43 tim Exp $"; #endif /* not lint */ #include #include "utils/magic.h" #include "utils/geometry.h" #include "utils/geofast.h" #include "tiles/tile.h" #include "utils/hash.h" #include "database/database.h" #include "database/databaseInt.h" #include "windows/windows.h" #include "dbwind/dbwind.h" #include "utils/undo.h" /* *----------------------------------------------------------------------------- * * DBPrintUseId -- * * Generate the print name of the use identifier indicated by the supplied * SearchContext. * * Results: * Returns a pointer to the NULL byte at the end of the string * generated. * * Side effects: * The character string pointed to by name is set to contain the use * id of scx->scx_use followed by any array indices. If scx->scx_use * is a two dimensional array, the array indices are of the form [y,x], * otherwise there is a single array index either of the form [y] or [x]. * The array indices are taken from scx->scx_x and scx->scx_y. At most * size characters are copied into the string pointed to by name. * *----------------------------------------------------------------------------- */ char * DBPrintUseId( SearchContext *scx, /* Pointer to current search context, specifying a * cell use and X,Y array indices. */ char *name, /* Pointer to string into which we will copy the * print name of this instance. */ int size, /* Maximum number of characters to copy into string. */ bool display_only) /* TRUE if called for displaying only */ { CellUse *use = scx->scx_use; char *sp, *id, *ep; char indices[100]; if ((id = use->cu_id) == (char *) NULL) { name[0] = '\0'; return (name); } sp = name; if (display_only && (use->cu_flags & CU_LOCKED)) *sp++ = CULOCKCHAR; for (ep = &name[size]; (sp < ep) && *id; *sp++ = *id++) /* Nothing */; if (use->cu_xlo != use->cu_xhi || use->cu_ylo != use->cu_yhi) { if (use->cu_xlo == use->cu_xhi) (void) sprintf(indices, "[%d]", scx->scx_y); else if (use->cu_ylo == use->cu_yhi) (void) sprintf(indices, "[%d]", scx->scx_x); else (void) sprintf(indices, "[%d,%d]", scx->scx_y, scx->scx_x); for (id = indices; (sp < ep) && *id; *sp++ = *id++) /* Nothing */; } if (sp == ep) sp--; *sp = '\0'; return (sp); } /* * ---------------------------------------------------------------------------- * * DBCellSetAvail -- * DBCellClearAvail -- * * Mark a cell as available/unavailable. * These exist mainly to create a cell for the first time, and to * allow a cell to be 'flushed' via the "flush" command. * * Results: * None. * * Side effects: * Modifies flags in cellDef. * * ---------------------------------------------------------------------------- */ void DBCellSetAvail(cellDef) CellDef *cellDef; /* Pointer to definition of cell we wish to * mark as available. */ { cellDef->cd_flags &= ~CDNOTFOUND; cellDef->cd_flags |= CDAVAILABLE; } void DBCellClearAvail(cellDef) CellDef *cellDef; /* Pointer to definition of cell we wish to * mark as available. */ { cellDef->cd_flags &= ~(CDNOTFOUND|CDAVAILABLE); } /* * ---------------------------------------------------------------------------- * * DBCellGetModified -- * DBCellSetModified -- * * Get/set the CDMODIFIED status of a cell. * DBCellGetModified returns TRUE if the cell has been modified, FALSE * if not. DBCellSetModified marks the cell as having been modified * if its argument is TRUE, and marks it as having been unmodified if * its argument is FALSE. * * Results: * DBCellGetModified: TRUE or FALSE. * DBCellSetModified: None. * * Side effects: * DBCellGetModified: None. * DBCellSetModified: modifies cellDef->cd_flags. * * ---------------------------------------------------------------------------- */ bool DBCellGetModified(cellDef) CellDef *cellDef; /* Pointer to definition of cell */ { return ((cellDef->cd_flags & CDMODIFIED) != 0); } void DBCellSetModified(cellDef, ismod) CellDef *cellDef; bool ismod; /* If TRUE, mark the cell as modified; if FALSE, * mark it as unmodified. */ { cellDef->cd_flags &= ~CDMODIFIED|CDGETNEWSTAMP; if (ismod) cellDef->cd_flags |= CDMODIFIED|CDGETNEWSTAMP; } /* * ---------------------------------------------------------------------------- * * DBComputeUseBbox -- * * Compute the bounding box for a CellUse in coordinates of its parent. * * Results: * None. * * Side effects: * Sets cellUse->cu_bbox to be the bounding box for the indicated CellUse * in coordinates of that CellUse's parent, and cellUse->cu_extended * to the bounding box of the cell use including all rendered text labels. * * ---------------------------------------------------------------------------- */ void DBComputeUseBbox(use) CellUse *use; { Rect *box, *extended; Rect childRect, childExtend; int xdelta, ydelta; xdelta = use->cu_xsep * (use->cu_xhi - use->cu_xlo); ydelta = use->cu_ysep * (use->cu_yhi - use->cu_ylo); if (xdelta < 0) xdelta = (-xdelta); if (ydelta < 0) ydelta = (-ydelta); box = &(use->cu_def->cd_bbox); extended = &(use->cu_def->cd_extended); if (use->cu_xsep < 0) { childRect.r_xbot = box->r_xbot - xdelta; childRect.r_xtop = box->r_xtop; childExtend.r_xbot = extended->r_xbot - xdelta; childExtend.r_xtop = extended->r_xtop; } else { childRect.r_xbot = box->r_xbot; childRect.r_xtop = box->r_xtop + xdelta; childExtend.r_xbot = extended->r_xbot; childExtend.r_xtop = extended->r_xtop + xdelta; } if (use->cu_ysep < 0) { childRect.r_ybot = box->r_ybot - ydelta; childRect.r_ytop = box->r_ytop; childExtend.r_ybot = extended->r_ybot - ydelta; childExtend.r_ytop = extended->r_ytop; } else { childRect.r_ybot = box->r_ybot; childRect.r_ytop = box->r_ytop + ydelta; childExtend.r_ybot = extended->r_ybot; childExtend.r_ytop = extended->r_ytop + ydelta; } GeoTransRect(&use->cu_transform, &childRect, &use->cu_bbox); GeoTransRect(&use->cu_transform, &childExtend, &use->cu_extended); } /* * ---------------------------------------------------------------------------- * * DBIsChild -- * * Test to see if cu1 is a child of cu2. * * Results: * TRUE if cu1 is a child of cu2, FALSE otherwise. * * Side effects: * None. * * ---------------------------------------------------------------------------- */ bool DBIsChild(cu1, cu2) CellUse *cu1, *cu2; { return (cu1->cu_parent == cu2->cu_def); } /* * ---------------------------------------------------------------------------- * * DBSetArray -- * * Copy the array information from fromCellUse to toCellUse * * Results: * None. * * Side Effects: * The array information if toCellUse is modified. * * ---------------------------------------------------------------------------- */ void DBSetArray(fromCellUse, toCellUse) CellUse *fromCellUse; CellUse *toCellUse; { toCellUse->cu_xlo = fromCellUse->cu_xlo; toCellUse->cu_ylo = fromCellUse->cu_ylo; toCellUse->cu_xhi = fromCellUse->cu_xhi; toCellUse->cu_yhi = fromCellUse->cu_yhi; toCellUse->cu_xsep = fromCellUse->cu_xsep; toCellUse->cu_ysep = fromCellUse->cu_ysep; } /* * ---------------------------------------------------------------------------- * * DBSetTrans -- * * Change the transform for cellUse to that supplied. * * Results: * None. * * Side Effects: * Updates cellUse->cu_trans and cellUse->cu_bbox * * ---------------------------------------------------------------------------- */ void DBSetTrans(cellUse, trans) CellUse *cellUse; Transform *trans; { cellUse->cu_transform = *trans; DBComputeUseBbox(cellUse); } /* * ---------------------------------------------------------------------------- * * DBMakeArray -- * * Turn cellUse into an array whose X indices run from xlo through xhi * and whose Y indices run from ylo through yhi. The separation between * adjacent array elements is xsep in the X direction, and ysep in the * Y direction. * * The X and Y information is in coordinates of the root cell def. * It gets transformed down to the def of cellUse according to the * transform supplied. What we do guarantee is that the array * indices will appear, in root coordinates, to run from xlo to xhi * left-to-right, and from ylo to yhi bottom-to-top. * * Results: * None. * * Side Effects: * The array information if toCellUse is modified. * * ---------------------------------------------------------------------------- */ void DBMakeArray(cellUse, rootToCell, xlo, ylo, xhi, yhi, xsep, ysep) CellUse *cellUse; Transform *rootToCell; int xlo, ylo; int xhi, yhi; int xsep, ysep; { int t; cellUse->cu_xsep = rootToCell->t_a * xsep + rootToCell->t_b * ysep; cellUse->cu_ysep = rootToCell->t_d * xsep + rootToCell->t_e * ysep; /* * Now transform the indices. * We should preserve the property that indices in root coordinates * should appear to run from xlo through xhi, left-to-right, and * from ylo through yhi, bottom-to-top. */ if (rootToCell->t_a == 0) { t = xlo; xlo = ylo; ylo = t; t = xhi; xhi = yhi; yhi = t; } cellUse->cu_xlo = xlo; cellUse->cu_ylo = ylo; cellUse->cu_xhi = xhi; cellUse->cu_yhi = yhi; DBComputeUseBbox(cellUse); } /* * ---------------------------------------------------------------------------- * * DBArrayOverlap -- * * Determine which elements of an array overlap the supplied clipping * rectangle. Assumes that the clipping rectangle overlaps at least * some part of the array area. * * Results: * None. * * WARNING: * This code is very sensitive to being changed. Make sure you * understand it before you change it. * * Side Effects: * Sets *pxlo, *pxhi, *pylo, *pyhi to be the inclusive range of array * indices which overlay the given clipping rectangle. * * If there is any overlap in X, *pxlo <= *pxhi; similarly, if there * is any overlap in Y, *pylo <= *pyhi. * * ---------------------------------------------------------------------------- */ void DBArrayOverlap(cu, parentRect, pxlo, pxhi, pylo, pyhi) CellUse *cu; /* Pointer to cell use which may be an array */ Rect *parentRect; /* Clipping rectangle cu->cu_parent coords */ int *pxlo, *pxhi; int *pylo, *pyhi; { int outxlo, outxhi, outylo, outyhi, t; int xlo, ylo, xhi, yhi, xsep, ysep; Transform parentToCell; Rect box, childR; /* For a non-arrayed element, return the indices of the only element */ if (cu->cu_xlo == cu->cu_xhi && cu->cu_ylo == cu->cu_yhi) { *pxlo = *pxhi = cu->cu_xlo; *pylo = *pyhi = cu->cu_ylo; return; } box = cu->cu_def->cd_bbox; GEOINVERTTRANS(&cu->cu_transform, &parentToCell); GEOTRANSRECT(&parentToCell, parentRect, &childR); xsep = cu->cu_xsep; ysep = cu->cu_ysep; /* * Canonicalize the array indices so that the base element * of the array has the minimum x and y coordinate, ie, * so that xlo <= xhi and ylo <= yhi. */ if (cu->cu_xlo > cu->cu_xhi) xlo = cu->cu_xhi, xhi = cu->cu_xlo; else xlo = cu->cu_xlo, xhi = cu->cu_xhi; if (cu->cu_ylo > cu->cu_yhi) ylo = cu->cu_yhi, yhi = cu->cu_ylo; else ylo = cu->cu_ylo, yhi = cu->cu_yhi; /* * If the separation along one of the coordinate axes is negative, * flip everything about that axis. */ if (xsep < 0) { xsep = (-xsep); t = childR.r_xbot; childR.r_xbot = -childR.r_xtop; childR.r_xtop = -t; t = box.r_xbot; box.r_xbot = -box.r_xtop; box.r_xtop = -t; } if (ysep < 0) { ysep = (-ysep); t = childR.r_ybot; childR.r_ybot = -childR.r_ytop; childR.r_ytop = -t; t = box.r_ybot; box.r_ybot = -box.r_ytop; box.r_ytop = -t; } /* * The following inequalities are used to derive the equations * computed below. "Blo" is the lower coordinate of the incident * box, and "Bhi" is the upper coordinate. * * min outlo : (outlo - lo) * sep + top >= Blo * max outhi : (outhi - lo) * sep + bot <= Bhi * * The intent is that "outlo" will be the smaller of the two * coordinates, and "outhi" the larger. */ /* Even though it should never happen, handle zero spacings * gracefully. */ if (xsep != 0) { outxlo = xlo + (childR.r_xbot - box.r_xtop + xsep - 1) / xsep; outxhi = xlo + (childR.r_xtop - box.r_xbot) / xsep; } else { outxlo = xlo; outxhi = xhi; } if (ysep != 0) { outylo = ylo + (childR.r_ybot - box.r_ytop + ysep - 1) / ysep; outyhi = ylo + (childR.r_ytop - box.r_ybot) / ysep; } else { outylo = ylo; outyhi = yhi; } /* * Clip against the canonicalized array indices. * Note that this may result in rxlo > rxhi or rylo > ryhi, in which * case the rectangle doesn't intersect the array at all. */ if (outxlo < xlo) outxlo = xlo; if (outxhi > xhi) outxhi = xhi; if (outylo < ylo) outylo = ylo; if (outyhi > yhi) outyhi = yhi; /* * Convert canonicalized array indices back into actual * array indices for output. */ if (cu->cu_xlo > cu->cu_xhi) { *pxhi = cu->cu_xhi + cu->cu_xlo - outxlo; *pxlo = cu->cu_xhi + cu->cu_xlo - outxhi; } else { *pxlo = outxlo; *pxhi = outxhi; } if (cu->cu_ylo > cu->cu_yhi) { *pyhi = cu->cu_yhi + cu->cu_ylo - outylo; *pylo = cu->cu_yhi + cu->cu_ylo - outyhi; } else { *pylo = outylo; *pyhi = outyhi; } } /* * ---------------------------------------------------------------------------- * * DBReComputeBbox -- * DBReComputeBboxVert -- * * Propagate changes to bounding boxes. * The former is the default procedure; the latter should be used * when the tile planes of a cell are organized into maximal vertical * strips instead of maximal horizontal. * * Results: * None. * * Side Effects: * These procedures re-examine cellDef to see if its bounding box * has gotten larger or smaller. If so, the bounding box is * modified, and the change is reflected upwards in the * hierarchy to parents of cellDef. This occurs recursively * until the top of the hierarchy is reached. * * Also modifies the CellUse bounding boxes of parents as * appropriate (cu_bbox). * * WARNING * * In order to preserve consistency, if it is discovered that a * cell contains no material of any kind, its bounding box is * set to a default of (0,0) :: (1,1). If the cell contains * labels but still has a zero-size bounding box, then the bounding * box is enlarged by one unit to give it non-zero area. * * ---------------------------------------------------------------------------- */ void dbReComputeBboxFunc(); void DBReComputeBbox(cellDef) CellDef *cellDef; /* Cell def whose bounding box may have changed */ { extern bool DBBoundPlane(); dbReComputeBboxFunc(cellDef, DBBoundPlane, DBReComputeBbox); } void DBReComputeBboxVert(cellDef) CellDef *cellDef; /* Cell def whose bounding box may have changed */ { extern bool DBBoundPlaneVert(); dbReComputeBboxFunc(cellDef, DBBoundPlaneVert, DBReComputeBboxVert); } void dbReComputeBboxFunc(cellDef, boundProc, recurseProc) CellDef *cellDef; /* Cell def whose bounding box may have changed */ bool (*boundProc)(); void (*recurseProc)(); { bool degenerate; Rect rect, area, extended, *box; Rect redisplayArea; CellUse *use; CellDef *parent, *last; Label *label; bool foundAny; int pNum; /* * Include area of subcells separately */ if (!((foundAny = DBBoundCellPlane(cellDef, &extended, &rect)) > 0)) extended = GeoNullRect; area = rect; for (pNum = PL_PAINTBASE; pNum < DBNumPlanes; pNum++) if (pNum != PL_DRC_CHECK) if ((*boundProc)(cellDef->cd_planes[pNum], &rect)) { if (foundAny) (void) GeoInclude(&rect, &area); else area = rect; foundAny = TRUE; } /* * Include the area of label anchors, too. */ for (label = cellDef->cd_labels; label != NULL; label = label->lab_next) { if (foundAny) { if (label->lab_rect.r_xbot < area.r_xbot) area.r_xbot = label->lab_rect.r_xbot; if (label->lab_rect.r_ybot < area.r_ybot) area.r_ybot = label->lab_rect.r_ybot; if (label->lab_rect.r_xtop > area.r_xtop) area.r_xtop = label->lab_rect.r_xtop; if (label->lab_rect.r_ytop > area.r_ytop) area.r_ytop = label->lab_rect.r_ytop; } else { area = label->lab_rect; foundAny = TRUE; } } /* Make sure the extended bounding box includes the area of all * paint material just found, then include the area of all text * in the current cell. */ GeoInclude(&area, &extended); if (foundAny) { for (label = cellDef->cd_labels; label != NULL; label = label->lab_next) if (label->lab_font >= 0) GeoInclude(&label->lab_bbox, &extended); } /* * If there is nothing at all, produce a 1x1 box with its * lower left corner at the origin. */ if (!foundAny) { degenerate = TRUE; area.r_xbot = area.r_ybot = 0; area.r_xtop = area.r_ytop = 1; extended = area; } else degenerate = FALSE; /* * Canonicalize degeneracy in all other directions by * expanding the box by one unit in the degenerate * direction. */ if (area.r_xbot == area.r_xtop) area.r_xtop = area.r_xbot + 1; if (area.r_ybot == area.r_ytop) area.r_ytop = area.r_ybot + 1; if (extended.r_xbot == extended.r_xtop) extended.r_xtop = extended.r_xbot + 1; if (extended.r_ybot == extended.r_ytop) extended.r_ytop = extended.r_ybot + 1; /* Did the bounding box change? If not then there's no need to * recompute the parents. If the cell has no material, then * we must always propagate upwards. This is because DBClearCellDef * sets the bounding box to the degenerate size WITHOUT propagating * (it isn't safe for DBClearCellDef to propagate). At this point * the propagation must be done. */ box = &cellDef->cd_extended; if ((area.r_xbot == box->r_xbot && area.r_ybot == box->r_ybot && area.r_xtop == box->r_xtop && area.r_ytop == box->r_ytop) && !degenerate) return; /* * Alas, the cell's bounding box has actually changed. Thus * we must recompute the areas of all the parents of this * cell recursively. We must rip up the child cell and place * it down again to insure that the subcell tile planes in the * parents are all correct. * * The undo package is disabled around this since it will already * insure that DBReComputeBbox will be called at the appropriate * times. */ UndoDisable(); for (use = cellDef->cd_parents; use != NULL; use = use->cu_nextuse) if (use->cu_parent != (CellDef *) NULL) { /* DBDeleteCell trashes the parent pointer, but we restore * it so we'll know where to put the cell back later. */ parent = use->cu_parent; DBDeleteCellNoModify(use); use->cu_parent = parent; } cellDef->cd_bbox = area; cellDef->cd_extended = extended; /* * Relink each of the uses into the subcell tile planes * of their respective parents. Also, redisplay the use * in each parent, in each window where the cell isn't * expanded (i.e. the bounding box is no longer correct). * * Because more than one cell may be in an array, avoid * running recurseProc more than once on the same parent. * The simple way is just wait until the parent cell changes * to run the process, although this could be done more * thoroughly. */ last = NULL; for (use = cellDef->cd_parents; use != NULL; use = use->cu_nextuse) { redisplayArea = use->cu_extended; DBComputeUseBbox(use); if ((parent = use->cu_parent) != (CellDef *) NULL) { parent->cd_flags |= CDBOXESCHANGED; DBPlaceCellNoModify(use, parent); if (last != parent) { if (last != NULL) (*recurseProc)(last); last = parent; } (void) GeoInclude(&use->cu_extended, &redisplayArea); DBWAreaChanged(parent, &redisplayArea, (int) ~use->cu_expandMask, &DBAllButSpaceBits); } } if ((last != NULL) && (parent != NULL)) (*recurseProc)(parent); UndoEnable(); } /* * ---------------------------------------------------------------------------- * * DBComputeArrayArea -- * * Given an area in native coordinates of a celldef, computes the * corresponding area in a parent's coordinates, for a particular * celluse and a particular element of an array. * * Results: * None. * * Side Effects: * Sets *prect to the given area in the given array instance, * subject to the arraying and transformation inforamtion in * the given cellUse. * * ---------------------------------------------------------------------------- */ void DBComputeArrayArea(area, cellUse, x, y, prect) Rect *area; /* Area to be transformed. */ CellUse *cellUse; /* Cell use whose bounding box is to be computed */ int x, y; /* Indexes of array element whose box is being found */ Rect *prect; /* Pointer to rectangle to be set to bounding * box of the given array element, in coordinates * of the def of cellUse. */ { int xdelta, ydelta; if (cellUse->cu_xlo > cellUse->cu_xhi) x = cellUse->cu_xlo - x; else x = x - cellUse->cu_xlo; if (cellUse->cu_ylo > cellUse->cu_yhi) y = cellUse->cu_ylo - y; else y = y - cellUse->cu_ylo; xdelta = cellUse->cu_xsep * x; ydelta = cellUse->cu_ysep * y; prect->r_xbot = area->r_xbot + xdelta; prect->r_xtop = area->r_xtop + xdelta; prect->r_ybot = area->r_ybot + ydelta; prect->r_ytop = area->r_ytop + ydelta; } /* * ---------------------------------------------------------------------------- * * DBGetArrayTransform -- * * This procedure computes the transform from a particular element * of an array to the coordinates of the array as a whole. * * Results: * The return result is a pointer to a transform describing how * coordinates of use->cu_def must be transformed in order to * appear in the (x,y) element location. In other words, if the * transform for the whole array (use->cu_transform) were * GeoIdentityTransform, this is the transform from use->cu_def * to the parent use for the (x,y) element. By the way, the * return result is a locally-allocated transform that goes away * the next time this procedure is called, so use it carefully. * * Side effects: * None. * * ---------------------------------------------------------------------------- */ Transform * DBGetArrayTransform(use, x, y) CellUse *use; int x, y; /* Array indices of the desired element. * These must fall within the range of * use's array indices. */ { static Transform result; int xsep, ysep, xbase, ybase; if (use->cu_xlo > use->cu_xhi) xsep = -use->cu_xsep; else xsep = use->cu_xsep; if (use->cu_ylo > use->cu_yhi) ysep = -use->cu_ysep; else ysep = use->cu_ysep; xbase = xsep * (x - use->cu_xlo); ybase = ysep * (y - use->cu_ylo); GeoTransTranslate(xbase, ybase, &GeoIdentityTransform, &result); return &result; }