/* * File: graphics.c * Last modified on Tue Jul 26 16:18:32 1994 by eroberts * ----------------------------------------------------- * This file implements the graphics.h and extgraph.h interfaces * for the Borland/Windows platform. */ #include #include #include #include #include #include #include #include #include "genlib.h" #include "gcalloc.h" #include "strlib.h" #include "extgraph.h" /* * Parameters * ---------- * PixelsPerInch -- Device resolution (not obtainable directly) * DefaultSize -- Default point size * MaxTitle -- Maximum window title length * MaxFontName -- Maximum font name length * MaxFonts -- Maximum number of fonts * TopMargin -- Margin from top of screen to graphics window * LeftMargin -- Margin from left of screen to both windows * RightMargin -- Margin from right of screen to both windows * BottomMargin -- Margin at bottom of screen from console window * WindowSep -- Separation between graphics and console windows * ConsoleHeight -- Height of the console window * PStartSize -- Starting size for polygon (must be greater than 1) * GWClassName -- Class name of the graphics window * DefaultFont -- Font that serves as the "Default" font */ #define PixelsPerInch 72 #define DefaultSize 12 #define MaxTitle 75 #define MaxFontName 50 #define MaxFonts 25 #define TopMargin 10 #define LeftMargin 15 #define RightMargin 15 #define BottomMargin 10 #define WindowSep 8 #define ConsoleHeight 140 #define PStartSize 50 #define GWClassName "Graphics Window" #define DefaultFont "Courier" /* * Other constants * --------------- * LargeInt -- Integer too large for a coordinate value * Epsilon -- Small arithmetic offset to reduce aliasing/banding * Pi -- Mathematical constant pi */ #define LargeInt 16000 #define Epsilon 0.00000000001 #define Pi 3.1415926535 /* * Type: graphicsStateT * -------------------- * This structure holds the variables that make up the graphics state. */ typedef struct graphicsStateT { double cx, cy; string font; int size; bool erase; struct graphicsStateT *link; } *graphicsStateT; /* * Type: fontEntryT * ---------------- * This structure holds the data for a font. */ typedef struct { string name; int size, ascent; HFONT font; } fontEntryT; /* * Type: regionStateT * ------------------ * The region assembly process has the character of a finite state * machine with the following four states: * * NoRegion Region has not yet been started * RegionStarting Region is started but no line segments yet * RegionActive First line segment appears * PenHasMoved Pen has moved during definition * * The current state determines whether other operations are legal * at that point. */ typedef enum { NoRegion, RegionStarting, RegionActive, PenHasMoved } regionStateT; /* * Static table: grayList * ---------------------- * This table contains the stock object identifiers for the gray * scale values. */ static int grayList[] = { WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH }; #define NGrays (sizeof grayList / sizeof grayList[0]) /* * Global variables * ---------------- * initialized -- TRUE if initialization has been done * consoleWindow -- Window handle for console window * graphicsWindow -- Window handoe for graphics window * osdc -- Offscreen DC (memory backup) * osbits -- Offscreen bitmap * drawPen -- Pen used for drawing * erasePen -- Pen used for erasing * nullPen -- Pen used for filling * drawColor -- Color used for drawing * eraseColor -- Color used for erasing * grayBrush -- Array of brush colors for filling * windowTitle -- Current window title (initialized statically) * xResolution -- Horizontal resolution of screen (dots per inch) * yResolution -- Vertical resolution of screen (dots per inch) * windowWidth -- Width of graphics window (inches) * windowHeight -- Height of graphics window (inches) * pixelWidth -- Width of graphics window (pixels) * pixelHeight -- Height of graphics window (pixels) * fontTable -- Table of stored fonts * nFonts -- Number of fonts in fontTable * currentFont -- Index of current font in fontTable * regionState -- Current state of the region * regionGray -- Gray scale to apply to region * polygonPoints -- Array of points used in current region * nPolygonPoints -- Number of active points * polygonSize -- Number of allocated points * polygonBounds -- Bounding box of polygon * stateStack -- Stack of graphicStateT blocks * cx, cy -- Current coordinates | These four variable * eraseMode -- Setting of erase flag | consititute the * textFont -- Current font | current graphics * pointSize -- Current point size | state */ static bool initialized = FALSE; static HWND consoleWindow, graphicsWindow; static HDC osdc; static HBITMAP osBits; static HPEN drawPen, erasePen, nullPen; static COLORREF drawColor, eraseColor; static HBRUSH grayBrush[NGrays]; static PAINTSTRUCT ps; static string windowTitle = "Graphics Window"; static double xResolution, yResolution; static double windowWidth, windowHeight; static int pixelWidth, pixelHeight; static fontEntryT fontTable[MaxFonts]; static int nFonts; static int currentFont; static regionStateT regionState; static double regionGray; static POINT *polygonPoints; static int nPolygonPoints; static int polygonSize; static RECT polygonBounds; static graphicsStateT stateStack; static double cx, cy; static bool eraseMode; static string textFont; static int pointSize; /* Private function prototypes */ static void InitCheck(void); static void InitGraphicsState(void); static void InitDisplay(void); static void InitDrawingTools(void); static void DisplayExit(void); static HWND FindConsoleWindow(void); static BOOL CALLBACK EnumerateProc(HWND window, LPARAM clientData); static void RegisterWindowClass(void); static LONG FAR PASCAL GraphicsEventProc(HWND w, UINT msg, WPARAM p1, LPARAM p2); static void DoUpdate(void); static void DisplayClear(void); static void DisplayLine(double x, double y, double dx, double dy); static void DisplayArc(double xc, double yc, double rx, double ry, double start, double sweep); static void RenderArc(double x, double y, double rx, double ry, double start, double sweep); static void DisplayText(double x, double y, string text); static void DisplayEraseMode(bool flag); static void DisplayFontAndSize(string font, int size); static int FindExistingFont(string name, int size); static void SetLineBB(RECT *rp, double x, double y, double dx, double dy); static void SetArcBB(RECT *rp, double xc, double yc, double rx, double ry, double start, double sweep); static void SetTextBB(RECT *rp, double x, double y, string text); static void StartPolygon(void); static void AddSegment(int x0, int y0, int x1, int y1); static void DisplayPolygon(void); static void AddPolygonPoint(int x, int y); static bool PrefixMatch(string prefix, string str); static int RectWidth(RECT r); static int RectHeight(RECT r); static double Radians(double degrees); static int Round(double x); static double InchesX(int x); static double InchesY(int y); static int PixelsX(double x); static int PixelsY(double y); static int ScaleX(double x); static int ScaleY(double y); static int Min(int x, int y); static int Max(int x, int y); /* Exported entries */ /* Section 1 -- Basic functions from graphics.h */ void InitGraphics(void) { if (!initialized) InitDisplay(); DisplayClear(); InitGraphicsState(); initialized = TRUE; } void MovePen(double x, double y) { InitCheck(); if (regionState == RegionActive) regionState = PenHasMoved; cx = x; cy = y; } void DrawLine(double dx, double dy) { InitCheck(); switch (regionState) { case NoRegion: DisplayLine(cx, cy, dx, dy); break; case RegionStarting: case RegionActive: DisplayLine(cx, cy, dx, dy); regionState = RegionActive; break; case PenHasMoved: Error("Region segments must be contiguous"); } cx += dx; cy += dy; } void DrawArc(double r, double start, double sweep) { DrawEllipticalArc(r, r, start, sweep); } double GetWindowWidth(void) { InitCheck(); return (windowWidth); } double GetWindowHeight(void) { InitCheck(); return (windowHeight); } double GetCurrentX(void) { InitCheck(); return (cx); } double GetCurrentY(void) { InitCheck(); return (cy); } /* Section 2 -- Elliptical arcs */ void DrawEllipticalArc(double rx, double ry, double start, double sweep) { double x, y; InitCheck(); x = cx + rx * cos(Radians(start + 180)); y = cy + ry * sin(Radians(start + 180)); switch (regionState) { case NoRegion: DisplayArc(x, y, rx, ry, start, sweep); break; case RegionStarting: case RegionActive: RenderArc(x, y, rx, ry, start, sweep); regionState = RegionActive; break; case PenHasMoved: Error("Region segments must be contiguous"); } cx = x + rx * cos(Radians(start + sweep)); cy = y + ry * sin(Radians(start + sweep)); } /* Section 3 -- Graphical structures */ void StartFilledRegion(double grayScale) { InitCheck(); if (regionState != NoRegion) { Error("Region is already in progress"); } if (grayScale < 0 || grayScale > 1) { Error("Gray scale for regions must be between 0 and 1"); } regionState = RegionStarting; regionGray = grayScale; StartPolygon(); } void EndFilledRegion(void) { InitCheck(); if (regionState == NoRegion) { Error("EndFilledRegion without StartFilledRegion"); } DisplayPolygon(); regionState = NoRegion; } /* Section 4 -- String functions */ void DrawTextString(string text) { InitCheck(); if (regionState != NoRegion) { Error("Text strings are illegal inside a region"); } DisplayText(cx, cy, text); cx += TextStringWidth(text); } double TextStringWidth(string text) { RECT r; InitCheck(); SetTextBB(&r, cx, cy, text); return (InchesX(RectWidth(r))); } void SetFont(string font) { InitCheck(); DisplayFontAndSize(font, pointSize); } string GetFont(void) { InitCheck(); return (CopyString(textFont)); } void SetPointSize(int size) { InitCheck(); DisplayFontAndSize(textFont, size); } int GetPointSize(void) { InitCheck(); return (pointSize); } /* Section 5 -- Miscellaneous functions */ void SetEraseMode(bool mode) { InitCheck(); eraseMode = mode; DisplayEraseMode(mode); } bool GetEraseMode(void) { InitCheck(); return (eraseMode); } void SetWindowTitle(string title) { windowTitle = CopyString(title); if (initialized) { SetWindowText(graphicsWindow, windowTitle); } } string GetWindowTitle(void) { return (CopyString(windowTitle)); } void UpdateDisplay(void) { InitCheck(); DoUpdate(); } void Pause(double seconds) { double finish; UpdateDisplay(); finish = (double) clock() / CLK_TCK + seconds; while (((double) clock() / CLK_TCK) < finish); } void SaveGraphicsState(void) { graphicsStateT sb; InitCheck(); sb = New(graphicsStateT); sb->cx = cx; sb->cy = cy; sb->font = textFont; sb->size = pointSize; sb->erase = eraseMode; sb->link = stateStack; stateStack = sb; } void RestoreGraphicsState(void) { graphicsStateT sb; InitCheck(); if (stateStack == NULL) { Error("RestoreGraphicsState called before SaveGraphicsState"); } sb = stateStack; cx = sb->cx; cy = sb->cy; textFont = sb->font; pointSize = sb->size; eraseMode = sb->erase; stateStack = sb->link; FreeBlock(sb); DisplayEraseMode(eraseMode); DisplayFontAndSize(textFont, pointSize); } /* Private functions */ /* * Function: InitCheck * Usage: InitCheck(); * ------------------- * This function merely ensures that the package has been * initialized before the client functions are called. */ static void InitCheck(void) { if (!initialized) Error("InitGraphics has not been called"); } /* * Function: InitGraphicsState * Usage: InitGraphicsState(); * --------------------------- * This function initializes the graphics state elements to * their default values. Because the erase mode and font * information are also maintained in the display state, * InitGraphicsState must call functions to ensure that these * values are initialized there as well. */ static void InitGraphicsState(void) { cx = cy = 0; eraseMode = FALSE; textFont = "Default"; pointSize = DefaultSize; stateStack = NULL; regionState = NoRegion; DisplayEraseMode(eraseMode); DisplayFontAndSize(textFont, pointSize); } /* * Function: InitDisplay * Usage: InitDisplay(); * --------------------- * This function does everything necessary to initialize the display. * In this implementation, the graphics window is created as an * overlapping child of the console window, which has been created * by EasyWin. As a child of the console, the window automatically * gets events whenever the console window is waiting for events, * simplifying the logic of the implementation considerably. Most * of the InitDisplay implementation is concerned with calculating * the new tiling geometry, after which the function creates the * new window and an offscreen bitmap to use as a target for any * rendering. When the window gets a Paint event in the event * procedure GraphicsEventProc, all it has to do is copy the * bits from the offscreen bitmap onto the screen. */ static void InitDisplay(void) { HWND desktop; RECT bounds, desktopRect, consoleRect, graphicsRect; DWORD style; clrscr(); atexit(DisplayExit); RegisterWindowClass(); InitDrawingTools(); consoleWindow = FindConsoleWindow(); desktop = GetDesktopWindow(); GetWindowRect(desktop, &desktopRect); GetWindowRect(consoleWindow, &consoleRect); xResolution = GetDeviceCaps(GetDC(consoleWindow), LOGPIXELSX); yResolution = GetDeviceCaps(GetDC(consoleWindow), LOGPIXELSY); graphicsRect.left = consoleRect.left = desktopRect.left + LeftMargin; graphicsRect.right = consoleRect.right = desktopRect.right - RightMargin; consoleRect.bottom = desktopRect.bottom - BottomMargin; consoleRect.top = consoleRect.bottom - ConsoleHeight; graphicsRect.top = desktopRect.top + TopMargin; graphicsRect.bottom = consoleRect.top - WindowSep; style = WS_OVERLAPPEDWINDOW & ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX); graphicsWindow = CreateWindow(GWClassName, windowTitle, style, graphicsRect.left, graphicsRect.top, RectWidth(graphicsRect), RectHeight(graphicsRect), consoleWindow, (HMENU) NULL, (HINSTANCE) NULL, (LPSTR) NULL); if (graphicsWindow == NULL) { printf("Internal error: CreateGraphicsWindow failed.\n"); } ShowWindow(graphicsWindow, SW_SHOW); UpdateWindow(graphicsWindow); SetWindowText(consoleWindow, "Console Window"); SetWindowPos(consoleWindow, HWND_TOP, consoleRect.left, consoleRect.top, RectWidth(consoleRect), RectHeight(consoleRect), 0); GetClientRect(graphicsWindow, &bounds); pixelWidth = RectWidth(bounds); pixelHeight = RectHeight(bounds); windowWidth = InchesX(pixelWidth); windowHeight = InchesY(pixelHeight); osdc = CreateCompatibleDC(GetDC(graphicsWindow)); if (osdc == NULL) { Error("Internal error: Can't create offscreen device"); } osBits = CreateCompatibleBitmap(osdc, pixelWidth, pixelHeight); if (osBits == NULL) { Error("Internal error: Can't create offscreen bitmap"); } (void) SelectObject(osdc, osBits); } /* * Function: InitDrawingTools * Usage: InitDrawingTools(); * -------------------------- * This function initializes all of the standard objects used for * drawing except for fonts, which are initialized dynamically by * the DisplayFontAndSize procedure. This function creates the * foreground/background colors, the drawing pens, and the brushes * for filled regions. */ static void InitDrawingTools(void) { int i; nFonts = 0; drawColor = RGB(0, 0, 0); eraseColor = RGB(255, 255, 255); drawPen = (HPEN) GetStockObject(BLACK_PEN); erasePen = (HPEN) GetStockObject(WHITE_PEN); nullPen = (HPEN) GetStockObject(NULL_PEN); if (drawPen == NULL || erasePen == NULL || nullPen == NULL) { Error("Internal error: Can't initialize pens"); } for (i = 0; i < NGrays; i++) { grayBrush[i] = (HBRUSH) GetStockObject(grayList[i]); if (grayBrush[i] == NULL) { Error("Internal error: Can't initialize gray scales"); } } } /* * Function: DisplayExit * Usage: DisplayExit(); * --------------------- * This function is called when the program exits and waits for the * user to type a carriage return. After reading and ignoring the * return key, this function frees the window system handles and * destroys the console window, thereby exiting the program. */ static void DisplayExit(void) { int i; (void) getchar(); DeleteObject(drawPen); DeleteObject(erasePen); DeleteObject(nullPen); for (i = 0; i < NGrays; i++) { DeleteObject(grayBrush[i]); } for (i = 0; i < nFonts; i++) { DeleteObject(fontTable[i].font); } DeleteDC(osdc); DestroyWindow(consoleWindow); } /* * Function: FindConsoleWindow * Usage: window = FindConsoleWindow(); * ------------------------------------ * The EasyWin package makes almost everything about the graphics * package easy in the Borland world. The only thing that is hard * is getting the handle of the window used for the console in the * first place. This function finds the console window handle by * enumerating the windows and looking for the first one whose * title ends with .EXE, which the EasyWin package puts there. */ static HWND FindConsoleWindow(void) { HWND result; EnumWindows(EnumerateProc, (LPARAM) &result); return (result); } /* * Function: EnumerateProc * Usage: Not called directly * -------------------------- * This callback procedure is used by the FindConsoleWindow * call to find the window whose title ends with .EXE. */ static BOOL CALLBACK EnumerateProc(HWND window, LPARAM clientData) { HWND *wptr; char title[MaxTitle]; bool ok; wptr = (HWND *) clientData; ok = GetWindowText(window, title, MaxTitle-1); if (ok && strcmp(title + strlen(title) - 4, ".EXE")==0) { *wptr = window; return (0); } return (1); } /* * Function: RegisterWindowClass * Usage: RegisterWindowClass(); * ----------------------------- * This function registers the window class used for the graphics * window. */ static void RegisterWindowClass(void) { WNDCLASS wcApp; wcApp.lpszClassName = GWClassName; wcApp.hInstance = NULL; wcApp.lpfnWndProc = GraphicsEventProc; wcApp.hCursor = NULL; wcApp.hIcon = NULL; wcApp.lpszMenuName = NULL; wcApp.hbrBackground = GetStockObject(WHITE_BRUSH); wcApp.style = CS_HREDRAW | CS_VREDRAW; wcApp.cbClsExtra = wcApp.cbWndExtra = 0; if (!RegisterClass(&wcApp)) { Error("Internal error: RegisterClass failed\n"); } } /* * Function: GraphicsEventProc * Usage: Not called directly * -------------------------- * This function is called when an event is received for the * graphics window. The only event this package needs to handle * is the paint event, which forces a screen update. */ static LONG FAR PASCAL GraphicsEventProc(HWND w, UINT msg, WPARAM p1, LPARAM p2) { if (msg == WM_PAINT) { DoUpdate(); return (0L); } else { return (DefWindowProc(w, msg, p1, p2)); } } /* * Function: DoUpdate * Usage: DoUpdate(); * ------------------ * This function redraws the graphics window by copying bits from * the offscreen bitmap behind the osdc device context into the * actual display context. */ static void DoUpdate(void) { HDC dc; dc = BeginPaint(graphicsWindow, &ps); BitBlt(dc, 0, 0, pixelWidth, pixelHeight, osdc, 0, 0, SRCCOPY); EndPaint(graphicsWindow, &ps); } /* * Function: DisplayClear * Usage: DisplayClear(); * ---------------------- * This function clears all the bits in the offscreen bitmap. */ static void DisplayClear(void) { RECT r; SetRect(&r, 0, 0, pixelWidth, pixelHeight); InvalidateRect(graphicsWindow, &r, TRUE); BitBlt(osdc, 0, 0, pixelWidth, pixelHeight, osdc, 0, 0, WHITENESS); } /* * Function: DisplayLine * Usage: DisplayLine(x, y, dx, dy); * --------------------------------- * This function renders a line into the offscreen bitmap. If the * region is started, it adds the line to the developing polygonal * region instead. */ static void DisplayLine(double x, double y, double dx, double dy) { int x0, y0, x1, y1; RECT r; x0 = ScaleX(x); y0 = ScaleY(y); x1 = ScaleX(x + dx); y1 = ScaleY(y + dy); if (regionState == NoRegion) { SetLineBB(&r, x, y, dx, dy); InvalidateRect(graphicsWindow, &r, TRUE); MoveTo(osdc, x0, y0); LineTo(osdc, x1, y1); } else { AddSegment(x0, y0, x1, y1); } } /* * Function: DisplayArc * Usage: DisplayArc(xc, yc, rx, ry, start, sweep); * ------------------------------------------------ * This function is used to draw an arc. The arguments are slightly * different from those in the client interface because xc and yc * designate the center. This function is only called if a region * is not being assembled; if it is, the package calls RenderArc * instead. */ static void DisplayArc(double xc, double yc, double rx, double ry, double start, double sweep) { RECT r; int xmax, xmin, ymax, ymin; int ix0, iy0, ix1, iy1; SetArcBB(&r, xc, yc, rx, ry, start, sweep); InvalidateRect(graphicsWindow, &r, TRUE); xmin = ScaleX(xc - rx); ymin = ScaleY(yc + ry); xmax = xmin + PixelsX(2 * rx); ymax = ymin + PixelsX(2 * ry); if (sweep < 0) { start += sweep; sweep = -sweep; } if (start < 0) { start = 360 - fmod(-start, 360); } else { start = fmod(start, 360); } ix0 = ScaleX(xc + rx * cos(Radians(start))); iy0 = ScaleY(yc + ry * sin(Radians(start))); ix1 = ScaleX(xc + rx * cos(Radians(start + sweep))); iy1 = ScaleY(yc + ry * sin(Radians(start + sweep))); Arc(osdc, xmin, ymin, xmax, ymax, ix0, iy0, ix1, iy1); } /* * Function: RenderArc * Usage: RenderArc(xc, yc, rx, ry, start, sweep); * ----------------------------------------------- * This function is identical to DisplayArc except that, instead * of calling the Arc function, RenderArc simulates the arc by * constructing a path of consecutive segments, which are added * to the current polygonal region. */ static void RenderArc(double x, double y, double rx, double ry, double start, double sweep) { double t, mint, maxt, dt, maxd; int ix0, iy0, ix1, iy1; if (sweep < 0) { start += sweep; sweep = -sweep; } if (fabs(rx) > fabs(ry)) { maxd = fabs(rx); } else { maxd = fabs(rx); } dt = atan2(InchesY(1), maxd); mint = Radians(start); maxt = Radians(start + sweep); ix0 = ScaleX(x + rx * cos(mint)); iy0 = ScaleY(y + ry * sin(mint)); for (t = mint + dt; t < maxt; t += dt) { if (t > maxt - dt / 2) t = maxt; ix1 = ScaleX(x + rx * cos(t)); iy1 = ScaleY(y + ry * sin(t)); AddSegment(ix0, iy0, ix1, iy1); ix0 = ix1; iy0 = iy1; } } /* * Function: DisplayText * Usage: DisplayText(x, y, text); * ------------------------------- * This function displays a text string at (x, y) in the current * font and size. The hard work is done in DisplayFontAndSize. */ static void DisplayText(double x, double y, string text) { RECT r; SetTextBB(&r, x, y, text); InvalidateRect(graphicsWindow, &r, TRUE); TextOut(osdc, ScaleX(x), ScaleY(y) - fontTable[currentFont].ascent, text, strlen(text)); } /* * Function: DisplayEraseMode * Usage: DisplayEraseMode(flag); * ------------------------------ * This function updates the pens and color state when the erase * mode changes. */ static void DisplayEraseMode(bool flag) { SelectObject(osdc, (flag) ? erasePen : drawPen); SetTextColor(osdc, (flag) ? eraseColor : drawColor); } /* * Function: DisplayFontAndSize * Usage: DisplayFontAndSize(font, size); * -------------------------------------- * This function updates the font and size information used for * drawing text. The program first uses FindExistingFont to see * if the desired font/size pair has been entered in the table, * in which case the program uses the stored handle of the font. * If not, the program uses CreateFont to try to create an * appropriate font, accepting only those whose typeface * matches the desired font string. If an acceptable font * is found, its data is entered into the font table. */ static void DisplayFontAndSize(string font, int size) { char fontBuffer[MaxFontName + 1]; char faceName[MaxFontName + 1]; string fontName; HFONT newFont, oldFont; TEXTMETRIC metrics; int i, fontIndex; for (i = 0; (fontBuffer[i] = tolower(font[i])) != '\0'; i++); if (StringEqual("default", fontBuffer)) { fontName = DefaultFont; } else { fontName = fontBuffer; } fontIndex = FindExistingFont(fontName, size); if (fontIndex == -1) { newFont = CreateFont(-size, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fontName); if (newFont != NULL) { oldFont = (HFONT) SelectObject(osdc, newFont); GetTextFace(osdc, MaxFontName, faceName); if (PrefixMatch(fontName, faceName) && GetTextMetrics(osdc, &metrics)) { if (nFonts == MaxFonts) Error("Too many fonts loaded"); fontIndex = nFonts++; fontTable[fontIndex].name = CopyString(fontName); fontTable[fontIndex].size = size; fontTable[fontIndex].font = newFont; fontTable[fontIndex].ascent = metrics.tmAscent; currentFont = fontIndex; textFont = CopyString(font); pointSize = size; } else { (void) SelectObject(osdc, oldFont); } } } else { (void) SelectObject(osdc, fontTable[fontIndex].font); currentFont = fontIndex; textFont = CopyString(font); pointSize = size; } } /* * Function: FindExistingFont * Usage: fontIndex = FindExistingFont(name, size); * ------------------------------------------------ * This function searches the font table for a matching name and * size. The function returns the matching table index or -1 if * no match is found, The caller has already converted the name * to lower case to preserve the case-insensitivity requirement. */ static int FindExistingFont(string name, int size) { int i; for (i = 0; i < nFonts; i++) { if (StringEqual(name, fontTable[i].name) && size == fontTable[i].size) return (i); } return (-1); } /* * Function: SetLineBB * Usage: SetLineBB(&rect, x, y, dx, dy); * -------------------------------------- * This function sets the rectangle dimensions to the bounding * box of the line. */ static void SetLineBB(RECT *rp, double x, double y, double dx, double dy) { int x0, y0, x1, y1; x0 = ScaleX(x); y0 = ScaleY(y); x1 = ScaleX(x + dx); y1 = ScaleY(y + dy); rp->top = Min(y0, y1); rp->bottom = Max(y0, y1) + 1; rp->left = Min(x0, x1); rp->right = Max(x0, x1) + 1; } /* * Function: SetArcBB * Usage: SetArcBB(&rect, xc, yc, rx, ry, start, sweep); * ----------------------------------------------------- * This function sets the rectangle dimensions to the bounding * box of the arc segment specified by the remaining arguments. */ static void SetArcBB(RECT *rp, double xc, double yc, double rx, double ry, double start, double sweep) { int xmax, xmin, ymax, ymin; int xl, xr, yt, yb; int ix0, iy0, ix1, iy1; xmin = ScaleX(xc - rx); ymin = ScaleY(yc + ry); xmax = xmin + PixelsX(2 * rx); ymax = ymin + PixelsX(2 * ry); if (sweep < 0) { start += sweep; sweep = -sweep; } if (sweep >= 360) { SetRect(rp, xmin, ymin, xmax, ymax); return; } if (start < 0) { start = 360 - fmod(-start, 360); } else { start = fmod(start, 360); } ix0 = ScaleX(xc + rx * cos(Radians(start))); iy0 = ScaleY(yc + ry * sin(Radians(start))); ix1 = ScaleX(xc + rx * cos(Radians(start + sweep))); iy1 = ScaleY(yc + ry * sin(Radians(start + sweep))); if (start + sweep > 360) { xr = xmax; } else { xr = Max(ix0, ix1); } start = fmod(start + 270, 360); if (start + sweep > 360) { yt = ymin; } else { yt = Min(iy0, iy1); } start = fmod(start + 270, 360); if (start + sweep > 360) { xl = xmin; } else { xl = Min(ix0, ix1); } start = fmod(start + 270, 360); if (start + sweep > 360) { yb = ymax; } else { yb = Max(iy0, iy1); } SetRect(rp, xl, yt, xr, yb); } /* * Function: SetTextBB * Usage: SetTextBB(&rect, x, y, text); * ------------------------------------- * This function sets the rectangle dimensions to the bounding * box of the text string using the current font and size. */ static void SetTextBB(RECT *rp, double x, double y, string text) { SIZE textSize; int ix, iy; if (!GetTextExtentPoint(osdc, text, strlen(text), &textSize)) { Error("Internal error: Text size calculation failed"); } ix = ScaleX(x); iy = ScaleY(y); SetRect(rp, ix, iy - textSize.cy, ix + textSize.cx, iy); } /* * Functions: StartPolygon, AddSegment, EndPolygon * Usage: StartPolygon(); * AddSegment(x0, y0, x1, y1); * AddSegment(x1, y1, x2, y2); * . . . * DisplayPolygon(); * ---------------------------------- * These functions implement the notion of a region in the PC * world, where the easiest shape to fill is a polygon. Calling * StartPolygon initializes the array polygonPoints so that * subsequent calls to AddSegment will add points to it. * The points in the polygon are assumed to be contiguous, * because the client interface checks for this property. * Because polygons involving arcs can be quite large, the * AddSegment code extends the polygonPoints list if needed * by doubling the size of the array. All storage is freed * after calling DisplayPolygon. */ static void StartPolygon(void) { polygonPoints = NewArray(PStartSize, POINT); polygonSize = PStartSize; nPolygonPoints = 0; SetRect(&polygonBounds, LargeInt, LargeInt, 0, 0); } static void AddSegment(int x0, int y0, int x1, int y1) { if (nPolygonPoints == 0) AddPolygonPoint(x0, y0); AddPolygonPoint(x1, y1); } static void DisplayPolygon(void) { int px; HBRUSH fillBrush; HPEN oldPen, fillPen; InvalidateRect(graphicsWindow, &polygonBounds, TRUE); if (eraseMode) { px = 0; fillPen = erasePen; } else { px = regionGray * (NGrays - 1) + 0.5 - Epsilon; fillPen = nullPen; } oldPen = (HPEN) SelectObject(osdc, fillPen); fillBrush = grayBrush[px]; if (fillBrush == NULL) { Error("Internal error: Can't load brush"); } (void) SelectObject(osdc, fillBrush); Polygon(osdc, polygonPoints, nPolygonPoints); (void) SelectObject(osdc, oldPen); FreeBlock(polygonPoints); } /* * Function: AddPolygonPoint * Usage: AddPolygonPoint(x, y); * ----------------------------- * AddPolygonPoint acts as a helper function for AddSegment. This * function does the work, but AddSegment has a more easily understood * interface. */ static void AddPolygonPoint(int x, int y) { POINT *newPolygon; int i; if (nPolygonPoints >= polygonSize) { polygonSize *= 2; newPolygon = NewArray(polygonSize, POINT); for (i = 0; i < nPolygonPoints; i++) { newPolygon[i] = polygonPoints[i]; } FreeBlock(polygonPoints); polygonPoints = newPolygon; } polygonBounds.left = Min(polygonBounds.left, x); polygonBounds.right = Max(polygonBounds.right, x); polygonBounds.top = Min(polygonBounds.top, y); polygonBounds.bottom = Max(polygonBounds.bottom, y); polygonPoints[nPolygonPoints].x = x; polygonPoints[nPolygonPoints].y = y; nPolygonPoints++; } /* * Utility functions * ----------------- * This section contains several extremely short utility functions * that improve the readability of the code. */ /* * Function: PrefixMatch * Usage: if (PrefixMatch(prefix, str)) . . . * ------------------------------------------------- * This function returns TRUE if prefix is the initial substring * of str, ignoring differences in case. */ static bool PrefixMatch(char *prefix, char *str) { while (*prefix != '\0') { if (tolower(*prefix++) != tolower(*str++)) return (FALSE); } return (TRUE); } /* * Functions: RectWidth, RectHeight * Usage: w = RectWidth(r); * h = RectHeight(r); * -------------------------------- * These functions return the width and height of a rectangle. */ static int RectWidth(RECT r) { return (r.right - r.left); } static int RectHeight(RECT r) { return (r.bottom - r.top); } /* * Function: Radians * Usage: radians = Radians(degrees); * ---------------------------------- * This functions convert an angle in degrees to radians. */ static double Radians(double degrees) { return (degrees * Pi / 180); } /* * Function: Round * Usage: n = Round(x); * -------------------- * This function rounds a double to the nearest integer. */ static int Round(double x) { return ((int) floor(x + 0.5)); } /* * Functions: InchesX, InchesY * Usage: inches = InchesX(pixels); * inches = InchesY(pixels); * -------------------------------- * These functions convert distances measured in pixels to inches. * Because the resolution may not be uniform in the horizontal and * vertical directions, the coordinates are treated separately. */ static double InchesX(int x) { return ((double) x / xResolution); } static double InchesY(int y) { return ((double) y / yResolution); } /* * Functions: PixelsX, PixelsY * Usage: pixels = PixelsX(inches); * pixels = PixelsY(inches); * -------------------------------- * These functions convert distances measured in inches to pixels. */ static int PixelsX(double x) { return (Round(x * xResolution + Epsilon)); } static int PixelsY(double y) { return (Round(y * yResolution + Epsilon)); } /* * Functions: ScaleX, ScaleY * Usage: pixels = ScaleX(inches); * pixels = ScaleY(inches); * -------------------------------- * These functions are like PixelsX and PixelsY but convert coordinates * rather than lengths. The difference is that y-coordinate values must * be inverted top to bottom to support the cartesian coordinates of * the graphics.h model. */ static int ScaleX(double x) { return (PixelsX(x)); } static int ScaleY(double y) { return (PixelsY(windowHeight - y)); } /* * Functions: Min, Max * Usage: min = Min(x, y); * max = Max(x, y); * ----------------------- * These functions find the minimum and maximum of two integers. */ static int Min(int x, int y) { return ((x < y) ? x : y); } static int Max(int x, int y) { return ((x > y) ? x : y); }