/* * Copyright 1993, 1994-1996 Johannes Sixt * * Permission to use, copy, modify and distribute this software and its * documentation for any purpose other than its commercial exploitation * is hereby granted without fee, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation. The authors * make no representations about the suitability of this software for * any purpose. It is provided "as is" without express or implied warranty. * * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * * Author: Johannes Sixt */ #include #include /* for definition of Position, Pixel */ #include #include #include /* abort */ #include #include "objects.h" #define LINE_WIDTH 1 #define HALF_LINE_WIDTH ((LINE_WIDTH + 1) >> 1) #ifndef min #define min(x, y) ((x) < (y) ? (x) : (y)) #endif #ifndef max #define max(x, y) ((x) > (y) ? (x) : (y)) #endif /* LaTeX supports only restricted slopes, lengths and diameters. This can be switched off. */ /* Line slopes: * The constants indicate the maximum possible value for the numerator or * denominator of the slope, i.e "6" means. */ #define END_SLOPE_TABLE 1000.0 #define LINE_SLOPES 6 #define ARROW_SLOPES 4 #define MIN_LENGTH 10 typedef struct { float slope, x, y; } slope_entry; Boolean unlimitedSlopes; Boolean unlimitedDiameters; Boolean noMinLength; Boolean snap, raster; char rasterHeightStr[RASTER_TEXT_LEN]; float zoomFactor; /* current zoom value */ float fileScale; /* rounded unit length in pt [\unitlength/pt]*/ static float preciseScale; /* exact unit length in pt [\unitlength/pt]*/ /* diameters must be sorted by size; -1 marks end of list */ int circle_diameter[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 28, 32, 36, 40, -1}; int disc_diameter[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -1}; static float rast_height; static slope_entry l_slope[30]; static slope_entry a_slope[20]; static GC drawGC, rubberGC, dashedGC; static XFontStruct *textFont; static void DrawBezier(Display *disp, Drawable d, GC gc, int ax, int ay, int ex, int ey, int sx, int sy); static void GetLineInfo(float x, float y, float ex, float ey, int *slope_x, int *slope_y, float *length); static void InitSlope(slope_entry *table, int slopes); static void SlopeInsert(slope_entry *table, float value, int x, int y); static int ValidLine(slope_entry *table, int x_origin, int y_origin, Position *x_ret, Position *y_ret); static void ValidLength(int x, int y, Position *x_ret, Position *y_ret); static int gcd(int a, int b); void ObjInit(Display *disp, Screen *scr, Drawable d, XFontStruct *font, Pixel foreground, Pixel background, float initRasterHeight) { XGCValues values; unsigned long mask; /* store away font info */ textFont = font; /* create GCs */ /* %%%%%% should use background of pboard here + check for 0 */ values.foreground = foreground ^ background; values.function = GXxor; mask = GCForeground | GCFunction; rubberGC = XCreateGC(disp, d, mask, &values); values.foreground = foreground; values.background = background; values.function = GXcopy; values.font = font->fid; values.line_width = LINE_WIDTH; mask |= GCLineWidth | GCBackground | GCFont; drawGC = XCreateGC(disp, d, mask, &values); values.line_style = LineOnOffDash; mask |= GCLineStyle; dashedGC = XCreateGC(disp, d, mask, &values); InitSlope(l_slope, LINE_SLOPES); InitSlope(a_slope, ARROW_SLOPES); sprintf(rasterHeightStr, "%.2f", initRasterHeight); } TeXPictObj *ObjCreate(TeXPictType type) { TeXPictObj *n; /* new */ n = XtNew(TeXPictObj); n->type = type; return n; } void ObjDestroy(TeXPictObj *obj) { XtFree((char *) obj); } void ObjCopy(TeXPictObj *fromObj, TeXPictObj *toObj) { toObj->type = fromObj->type; toObj->x = fromObj->x; toObj->y = fromObj->y; toObj->data = fromObj->data; } void ObjValidate(TeXPictType type, XPoint *points, int n_points, int point) { SnapPosition(&points[point].x, &points[point].y); switch (type) { case TeXLine: case TeXVector: if (n_points == 2) { ValidLine(type == TeXLine ? l_slope : a_slope, points[1-point].x, points[1-point].y, &points[point].x, &points[point].y); } break; case TeXCircle: case TeXDisc: if (n_points == 2 && point == 1) { /* compute new desired radius */ ValidRadius((int) points[0].x, (int) points[0].y, &points[point].x, &points[point].y, type == TeXDisc ? disc_diameter : circle_diameter, zoomFactor); } break; default: ; } } /*ARGSUSED*//* %%%%% num_points should be checked */ void ObjStore(TeXPictObj *obj, XPoint *points, int num_points) { int x, y, w, h; switch (obj->type) { case TeXVector: case TeXLine: obj->x = (float) points[0].x; obj->y = (float) points[0].y; obj->data.linear.ex = (float) points[1].x; obj->data.linear.ey = (float) points[1].y; break; case TeXBezier: obj->x = (float) points[0].x; obj->y = (float) points[0].y; obj->data.bezier.ex = (float) points[1].x; obj->data.bezier.ey = (float) points[1].y; obj->data.bezier.sx = (float) points[2].x; obj->data.bezier.sy = (float) points[2].y; break; case TeXDashedText: obj->data.rectangular.xtra = DASH_LEN; /* drop thru */ case TeXFramedText: case TeXFilled: case TeXOval: if (points[0].x <= points[1].x) { x = points[0].x; w = points[1].x - points[0].x; } else { x = points[1].x; w = points[0].x - points[1].x; } if (points[0].y <= points[1].y) { y = points[0].y; h = points[1].y - points[0].y; } else { y = points[1].y; h = points[0].y - points[1].y; } obj->x = (float) x; obj->y = (float) y; obj->data.rectangular.w = (float) w; obj->data.rectangular.h = (float) h; if (obj->type == TeXOval) { /* determine corner radius */ Position x1, y1; x1 = x + (w < h ? w : h) / 2; y1 = y; ValidRadius(x, y, &x1, &y1, circle_diameter, zoomFactor); obj->data.rectangular.xtra = (float) (x1 - x); } break; case TeXCircle: case TeXDisc: obj->x = (float) points[0].x; obj->y = (float) points[0].y; if (points[1].x >= points[0].x) { obj->data.r = (float) (points[1].x - points[0].x); } else { obj->data.r = (float) (points[0].x - points[1].x); } break; case TeXText: obj->x = (float) points[0].x; obj->y = (float) points[0].y; obj->data.rectangular.w = 0.0; obj->data.rectangular.h = 0.0; break; default: fprintf(stderr, "ObjStore: Unknown type %d\n", obj->type); exit(1); } } void ObjRetrieve(TeXPictObj *obj, XPoint *points, int *num_points) { points[0].x = (int) obj->x; points[0].y = (int) obj->y; switch (obj->type) { case TeXVector: case TeXLine: points[1].x = (int) obj->data.linear.ex; points[1].y = (int) obj->data.linear.ey; *num_points = 2; break; case TeXBezier: points[1].x = (int) obj->data.bezier.ex; points[1].y = (int) obj->data.bezier.ey; points[2].x = (int) obj->data.bezier.sx; points[2].y = (int) obj->data.bezier.sy; *num_points = 3; break; case TeXFramedText: case TeXDashedText: case TeXFilled: case TeXOval: points[1].x = (int) (obj->x + obj->data.rectangular.w); points[1].y = (int) (obj->y + obj->data.rectangular.h); *num_points = 2; break; case TeXCircle: case TeXDisc: points[1].x = (int) (obj->x + obj->data.r); points[1].y = points[0].y; *num_points = 2; break; case TeXText: *num_points = 1; break; default: fprintf(stderr, "ObjRetrieve: Unknown type %d\n", obj->type); exit(1); } } void ObjDraw(TeXPictObj *obj, Display *disp, Drawable d) { float length; float a, b; XPoint points[3]; int direction, ascent, descent; int x, y, w, h, r, text_len; XCharStruct overall; x = (int) obj->x; y = (int) obj->y; switch (obj->type) { case TeXVector: /* draws an arrow at the end of the line */ a = obj->data.linear.ex - obj->x; b = obj->data.linear.ey - obj->y; length = sqrt((double) (a * a + b * b)); if (length >= 1.0) { /* normalize vector (a,b) */ a /= length; b /= length; points[0].x = (short) obj->data.linear.ex; points[0].y = (short) obj->data.linear.ey; points[1].x = (short) (obj->data.linear.ex - zoomFactor*(6*a + 3*b)); points[1].y = (short) (obj->data.linear.ey - zoomFactor*(6*b - 3*a)); points[2].x = (short) (obj->data.linear.ex - zoomFactor*(6*a - 3*b)); points[2].y = (short) (obj->data.linear.ey - zoomFactor*(6*b + 3*a)); XFillPolygon(disp, d, drawGC, points, 3, Convex, CoordModeOrigin); } /* drop thru */ case TeXLine: XDrawLine(disp, d, drawGC, x, y, (int) obj->data.linear.ex, (int) obj->data.linear.ey); break; case TeXBezier: DrawBezier(disp, d, drawGC, x, y, (int) obj->data.bezier.ex, (int) obj->data.bezier.ey, (int) obj->data.bezier.sx, (int) obj->data.bezier.sy); break; case TeXFramedText: case TeXDashedText: case TeXText: w = (int) obj->data.rectangular.w; h = (int) obj->data.rectangular.h; if (w != 0 || h != 0) { XDrawRectangle(disp, d, obj->type == TeXDashedText ? dashedGC : drawGC, x, y, w, h); } text_len = strlen(obj->data.rectangular.text); if (text_len == 0) break; XTextExtents(textFont, obj->data.rectangular.text, text_len, &direction, &ascent, &descent, /* ignored */ &overall); if (w != 0) { while ((overall.width > w) && (text_len > 10)) { text_len--; XTextExtents(textFont, obj->data.rectangular.text, text_len, &direction, &ascent, &descent, /* ignored */ &overall); } } else if (text_len > 10){ text_len = 10; XTextExtents(textFont, obj->data.rectangular.text, text_len, &direction, &ascent, &descent, /* ignored */ &overall); } if (obj->data.rectangular.align & ALIGN_H_LEFT) { x += HALF_LINE_WIDTH; } else if (obj->data.rectangular.align & ALIGN_H_RIGHT) { x += (int) obj->data.rectangular.w - overall.width - HALF_LINE_WIDTH; } else { x += ((int) obj->data.rectangular.w - overall.width - LINE_WIDTH) / 2; } if (obj->data.rectangular.align & ALIGN_V_TOP) { y += overall.ascent + HALF_LINE_WIDTH; } else if (obj->data.rectangular.align & ALIGN_V_BOTTOM) { y += (int) obj->data.rectangular.h - overall.descent - HALF_LINE_WIDTH; } else { y += ((int) obj->data.rectangular.h + overall.ascent - overall.descent - LINE_WIDTH) / 2; } XDrawString(disp, d, drawGC, x, y, obj->data.rectangular.text, text_len); break; case TeXFilled: XFillRectangle(disp, d, drawGC, x, y, (unsigned int) obj->data.rectangular.w, (unsigned int) obj->data.rectangular.h); break; case TeXCircle: case TeXDisc: r = (unsigned int) obj->data.r; (*(obj->type == TeXCircle ? XDrawArc : XFillArc)) (disp, d, drawGC, x - r, y - r, r+r, r+r, 0, 360 * 64); break; case TeXOval: r = (int) obj->data.rectangular.xtra; XmuDrawRoundedRectangle(disp, d, drawGC, x, y, (int) obj->data.rectangular.w, (int) obj->data.rectangular.h, r, r); break; default: fprintf(stderr, "ObjDraw: Unknown type %d\n", obj->type); abort(); } } void ObjRubber(TeXPictType type, Display *disp, Drawable d, XPoint *points, int num_points) { int x, y, w, h, r; switch (num_points) { case 0: case 1: /* nothing to do */ break; case 2: switch (type) { case TeXLine: case TeXVector: case TeXBezier: XDrawLine(disp, d, rubberGC, points[0].x, points[0].y, points[1].x, points[1].y); break; case TeXFramedText: case TeXDashedText: case TeXFilled: case TeXOval: if (points[0].x <= points[1].x) { x = points[0].x; w = points[1].x - points[0].x; } else { x = points[1].x; w = points[0].x - points[1].x; } if (points[0].y <= points[1].y) { y = points[0].y; h = points[1].y - points[0].y; } else { y = points[1].y; h = points[0].y - points[1].y; } XDrawRectangle(disp, d, rubberGC, x, y, w, h); break; case TeXCircle: case TeXDisc: if (points[0].x >= points[1].x) { r = points[0].x - points[1].x; } else { r = points[1].x - points[0].x; } (*(type == TeXCircle ? XDrawArc : XFillArc)) (disp, d, rubberGC, points[0].x - r, points[0].y - r, r+r, r+r, 0, 360 * 64); break; default: fprintf(stderr, "ObjRubber: type %d illegal with 2 points\n", (int) type); abort(); } break; case 3: switch (type) { case TeXBezier: DrawBezier(disp, d, rubberGC, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); break; default: fprintf(stderr, "ObjRubber: type %d illegal with 3 points\n", (int) type); abort(); } break; default: fprintf(stderr, "ObjRubber: %d points illegal\n", num_points); } } void ObjOffset(TeXPictObj *obj, float x_off, float y_off) { obj->x += x_off; obj->y += y_off; switch (obj->type) { case TeXLine: case TeXVector: obj->data.linear.ex += x_off; obj->data.linear.ey += y_off; break; case TeXBezier: obj->data.bezier.ex += x_off; obj->data.bezier.ey += y_off; obj->data.bezier.sx += x_off; obj->data.bezier.sy += y_off; break; default: ; } } void ObjGetExtent(TeXPictObj *obj, TeXObjExtent *extent) { float t, new_val; switch (obj->type) { case TeXVector: case TeXLine: extent->x_min = min(obj->x, obj->data.linear.ex); extent->y_min = min(obj->y, obj->data.linear.ey); extent->x_max = max(obj->x, obj->data.linear.ex); extent->y_max = max(obj->y, obj->data.linear.ey); break; case TeXBezier: extent->x_min = min(obj->x, obj->data.bezier.ex); extent->y_min = min(obj->y, obj->data.bezier.ey); extent->x_max = max(obj->x, obj->data.bezier.ex); extent->y_max = max(obj->y, obj->data.bezier.ey); if (obj->data.bezier.sx < extent->x_min || obj->data.bezier.sx > extent->x_max) { /* denominator cannot be 0.0 here */ t = (obj->data.bezier.ex - obj->data.bezier.sx) / (obj->x + obj->data.bezier.ex - 2.0 * obj->data.bezier.sx); new_val = obj->x * t*t + 2*obj->data.bezier.sx * t*(1-t) + obj->data.bezier.ex * (1-t)*(1-t); if (extent->x_min > new_val) { extent->x_min = new_val; } else { extent->x_max = new_val; } } if (obj->data.bezier.sy < extent->y_min || obj->data.bezier.sy > extent->y_max) { /* denominator cannot be 0.0 here */ t = (obj->data.bezier.ey - obj->data.bezier.sy) / (obj->y + obj->data.bezier.ey - 2.0 * obj->data.bezier.sy); new_val = obj->y * t*t + 2*obj->data.bezier.sy * t*(1-t) + obj->data.bezier.ey * (1-t)*(1-t); if (extent->y_min > new_val) { extent->y_min = new_val; } else { extent->y_max = new_val; } } break; case TeXFramedText: case TeXDashedText: case TeXText: case TeXFilled: case TeXOval: extent->x_min = obj->x; extent->y_min = obj->y; extent->x_max = obj->x + obj->data.rectangular.w; extent->y_max = obj->y + obj->data.rectangular.h; break; case TeXCircle: case TeXDisc: extent->x_min = obj->x - obj->data.r; extent->y_min = obj->y - obj->data.r; extent->x_max = obj->x + obj->data.r; extent->y_max = obj->y + obj->data.r; break; default: fprintf(stderr, "ObjGetExtent: unknown type %d\n", (int) obj->type); } } #define FUZZ 3.0 Boolean ObjMatches(TeXPictObj *obj, float x, float y) { TeXObjExtent extent; ObjGetExtent(obj, &extent); return extent.x_min - FUZZ <= x && x <= extent.x_max + FUZZ && extent.y_min - FUZZ <= y && y <= extent.y_max + FUZZ; } void ObjSave(TeXPictObj *obj, FILE *file, float max_x, float max_y) { #define Y_TO_L(y) (max_y-(y)) #define X_TO_L(x) ((x)-max_x) int slope_x, slope_y; float length; float w, h; switch (obj->type) { case TeXVector: case TeXLine: fprintf(file, "\\put(%.2f,%.2f){\\", X_TO_L(obj->x),Y_TO_L(obj->y)); if (obj->type == TeXLine) { fprintf(file, "line"); } else { fprintf(file, "vector"); } GetLineInfo(X_TO_L(obj->x), Y_TO_L(obj->y), X_TO_L(obj->data.linear.ex), Y_TO_L(obj->data.linear.ey), &slope_x, &slope_y, &length); fprintf(file, "(%d,%d){%.2f}}\n", slope_x, slope_y, length); break; case TeXBezier: w = obj->x - obj->data.bezier.sx; h = obj->y - obj->data.bezier.sy; length = sqrt(w * w + h * h); w = obj->data.bezier.ex - obj->data.bezier.sx; h = obj->data.bezier.ey - obj->data.bezier.sy; fprintf(file, "\\qbezier(%.2f,%.2f)(%.2f,%.2f)(%.2f,%.2f)\n", X_TO_L(obj->x), Y_TO_L(obj->y), X_TO_L(obj->data.bezier.sx), Y_TO_L(obj->data.bezier.sy), X_TO_L(obj->data.bezier.ex), Y_TO_L(obj->data.bezier.ey)); break; case TeXFramedText: case TeXDashedText: case TeXText: fprintf(file, "\\put(%.2f,%.2f){\\", X_TO_L(obj->x), Y_TO_L(obj->y + obj->data.rectangular.h)); if (obj->type == TeXFramedText) { fprintf(file, "framebox"); } else if (obj->type == TeXDashedText) { fprintf(file, "dashbox{%.2f}", obj->data.rectangular.xtra); } else { fprintf(file, "makebox"); } fprintf(file, "(%.2f,%.2f)", obj->data.rectangular.w, obj->data.rectangular.h); if (obj->data.rectangular.align != (ALIGN_H_CENTER | ALIGN_V_CENTER)) { fputc('[', file); if (obj->data.rectangular.align & ALIGN_V_TOP) { fputc('t', file); } else if (obj->data.rectangular.align&ALIGN_V_BOTTOM) { fputc('b', file); } if (obj->data.rectangular.align & ALIGN_H_LEFT) { fputc('l', file); } else if (obj->data.rectangular.align&ALIGN_H_RIGHT) { fputc('r', file); } fputc(']', file); } fprintf(file, "{%s}}\n", obj->data.rectangular.text); break; case TeXFilled: fprintf(file, "\\put(%.2f,%.2f)" "{\\rule{%.2f\\unitlength}{%.2f\\unitlength}}\n", X_TO_L(obj->x), Y_TO_L(obj->y + obj->data.rectangular.h), obj->data.rectangular.w, obj->data.rectangular.h); break; case TeXOval: fprintf(file, "\\put(%.2f,%.2f){\\oval(%.2f,%.2f)}\n", X_TO_L(obj->x + obj->data.rectangular.w/2.0), Y_TO_L(obj->y + obj->data.rectangular.h/2.0), obj->data.rectangular.w, obj->data.rectangular.h); break; case TeXCircle: case TeXDisc: fprintf(file, "\\put(%.2f,%.2f){\\circle%s{%.2f}}\n", X_TO_L(obj->x),Y_TO_L(obj->y), obj->type == TeXDisc ? "*" : "", 2.0*obj->data.r); break; default: fprintf(stderr, "ObjSave: unknown type %d\n", (int) obj->type); } #undef Y_TO_L #undef X_TO_L } void ObjRescale(TeXPictObj *obj, float zoom) { obj->x *= zoom; obj->y *= zoom; switch (obj->type) { case TeXVector: case TeXLine: obj->data.linear.ex *= zoom; obj->data.linear.ey *= zoom; break; case TeXBezier: obj->data.bezier.ex *= zoom; obj->data.bezier.ey *= zoom; obj->data.bezier.sx *= zoom; obj->data.bezier.sy *= zoom; break; case TeXOval: case TeXDashedText: obj->data.rectangular.xtra *= zoom; /* drop thru */ case TeXFramedText: case TeXText: case TeXFilled: obj->data.rectangular.w *= zoom; obj->data.rectangular.h *= zoom; break; case TeXCircle: case TeXDisc: obj->data.r *= zoom; break; default: fprintf(stderr, "ObjRescale: Unknown type %d\n", obj->type); abort(); } } void SnapPositionFloat(Position *x, Position *y, float *xf, float *yf, float h) { /* compute the coordinates of the nearest raster-node */ int res; float a, b; a = (float) (*x); b = (float) (*y); res = (int) (a/h + 0.5); a = ((float) res) * h; res = (int) (b/h + 0.5); b = ((float) res) * h; (*x) = (int) (a + 0.5); (*y) = (int) (b + 0.5); (*xf) = a; (*yf) = b; } void SnapPosition(Position *x, Position *y) { static float xf, yf, h; if (snap && raster) { h = rast_height*zoomFactor*fileScale; SnapPositionFloat(x,y,&xf,&yf,h); (*x) = (int) (xf + 0.5); (*y) = (int) (yf + 0.5); } } Boolean SetRaster(float p_rast_height) { rast_height = p_rast_height; return ((rast_height*zoomFactor*fileScale) >= MIN_RASTER); } /* * Routine which converts an arbitrary fileScale number into agreeable * number and memorises the exact scale in preciseScale. Here is a * list of fileScale factors which I like and find numerically good */ static float okay_list[] = {1.0, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 7.5, 8.0, 10.0, -1.0};/* -1.0 means end of list */ void SetFileScale(float precise) { float power, scaled; float rel_error, min = 1.0, result = 1.0; int counter; if (precise == 0.0) { /* should not happen, fall back to sensible values */ fprintf(stderr, "Warning: zero \\unitlength corrected to 1pt\n"); preciseScale = 1.0; fileScale = 1.0; return; } power = pow(10.0, floor(log10(precise))); scaled = precise/power; for (counter = 0; okay_list[counter] != -1.0; counter++){ rel_error = okay_list[counter]/scaled - 1.0; rel_error *= ( rel_error < 0.0 ? -1.0 : 1.0); if (rel_error < min){ result = okay_list[counter]; min = rel_error; } } preciseScale = precise; /* memorise exact fileScale */ fileScale = result*power; } #define BEZIER_SEGMENTS 25 static void DrawBezier(Display *disp, Drawable d, GC gc, int pax, int pay, int pex, int pey, int psx, int psy) { double u = 0.0, v = 1.0; double ax = (double) pax, ay = (double) pay, ex = (double) pex, ey = (double) pey, sx = (double) psx, sy = (double) psy; XPoint point[BEZIER_SEGMENTS + 1]; int i; for (i = 0; i <= BEZIER_SEGMENTS; i++) { point[i].x = ax * u * u + 2 * sx * u * v + ex * v * v; point[i].y = ay * u * u + 2 * sy * u * v + ey * v * v; u += (1.0 / (double) BEZIER_SEGMENTS); v -= (1.0 / (double) BEZIER_SEGMENTS); } XDrawLines(disp, d, gc, point, BEZIER_SEGMENTS + 1, CoordModeOrigin); } #define EXACTNESS 0.005 #define MAX_LOOPS 50 static void GetLineInfo(float x, float y, float ex, float ey, int *slope_x, int *slope_y, float *length) { /* A Bresenham-like algorithm is used to find the slope of a line segment with given end points: (The width and height extents of the line segment are assumed to be positive.) A value "pitch" is maintained in a loop as follows: - If the current pitch is negative, the height extent of the line segment is added to the pitch and the x value of the slope is increased by 1. - If the current pitch is positive, the width extent of the line segment is subtracted from the pitch and the y value of the slope is increased by 1. - If the current pitch is zero, terminate. The current x and y values of the slope are the slope of the line segment. This algorithm will terminate soon for the restricted LaTeX slopes. However, for unrestricted slopes the following heuristics is added to the loop: - Whenever the value of the pitch is nearer to zero than ever since the start of the loop, we consider the current x and y values as approximations to the actual slope. To prevent "infinite" looping a maximum number of steps is predefined. - Since we finally return integers, we consider an absolute value < EXACTNESS to be zero. */ float w, h; float pitch, min_pitch, abs_pitch; int dx, dy, i; w = ex - x; h = ey - y; if (w < 0.0) w = -w; if (h < 0.0) h = -h; if (w < EXACTNESS) { /* vertical line */ *slope_x = 0; *slope_y = ey < y ? -1 : 1; *length = h; return; } if (h < EXACTNESS) { /* horizontal line */ *slope_x = ex < x ? -1 : 1; *slope_y = 0; *length = w; return; } /* ASSERT: w > 0.0, h > 0.0 */ pitch = -w; min_pitch = w; dx = 0; dy = 1; for (i = 0; i < MAX_LOOPS; i++) { abs_pitch = pitch < 0.0 ? -pitch : pitch; if (abs_pitch < EXACTNESS) { break; } if (abs_pitch < min_pitch) { /* heuristic "near miss" */ min_pitch = abs_pitch; *slope_x = dx; *slope_y = dy; } if (pitch < 0.0) { pitch += h; ++ dx; } else { pitch -= w; ++ dy; } } if (i < MAX_LOOPS) { /* Loop terminated because pitch == 0.0, thus dx, dy are EXACT. */ *slope_x = dx; *slope_y = dy; } /* determine sign */ if (ex < x) *slope_x = -*slope_x; if (ey < y) *slope_y = -*slope_y; *length = w; } void ValidRadius(int x, int y, Position *ex, Position *ey, int *diameter_list, float zoom) { /* calculate allowed diameter. It is guaranteed that *ey == y and *ex - x is the radius */ int desired, diameter; int a, b, i; /* %%%%%%%%%%%% calculate correct radii!!!! */ a = *ex - x; b = *ey - y; if (a < 0) a = -a; if (b < 0) b = -b; if (unlimitedDiameters) { diameter = (int) (2.0 * sqrt((double) (a * a + b * b))); } else { /* need to be exact here */ desired = (int) (2.0 * sqrt((double) (a * a + b * b))/zoom * preciseScale/fileScale); for (i = 0; diameter_list[i] != -1 && diameter_list[i] < desired; i++) ; if (i == 0) /* selected is smaller than smallest available */ diameter = diameter_list[0]; else if (diameter_list[i] == -1) /* selected is larger than largest available */ diameter = diameter_list[i - 1]; else { /* diameter_list[i-1] < desired <= diameter_list[i] ; select nearest */ if (desired - diameter_list[i - 1] < diameter_list[i] - desired) diameter = diameter_list[i - 1]; else diameter = diameter_list[i]; } /* diameter is the correct LaTeX diameter */ /* need to be exact here also */ diameter = (int) (diameter*zoom * fileScale/preciseScale); } *ex = x + (diameter + 1) / 2; *ey = y; } static int gcd(int a, int b) { /* compute the biggest common divisor of a and b */ /* (c) by Euklid */ int r; for (; b; r = (a % b), a = b, b = r); return a ? a : 1; } static void InitSlope(slope_entry *table, int slopes) { /* builds a line slope table */ int x, y; float r; table[0].slope = END_SLOPE_TABLE; /* end marker */ for (x = slopes; x != 0; x--) { for (y = slopes; y != 0; y--) { r = (float) x / (float) y; SlopeInsert(table, r, y, x); r = (float) y / (float) x; SlopeInsert(table, r, x, y); } } } static void SlopeInsert(slope_entry *table, float value, int x, int y) { /* insertion sort into the table */ int i, j; int divi; /* kuerzen */ divi = gcd(x, y); x /= divi; y /= divi; /* searching for insertion position */ j = 0; while ((table[j].slope != END_SLOPE_TABLE) && (table[j].slope < value)) j++; /* j is new insertion point; points to value bigger than 'value' */ /* value already in table? */ if (table[j].slope == value) return; /* search for end of the table */ i = j; for (; table[i].slope != END_SLOPE_TABLE; i++) ; /* i points to END_SLOPE_TABLE */ /* insert data */ table[i + 1].slope = END_SLOPE_TABLE; for (; i != j; i--) { table[i] = table[i - 1]; } /* insert new data at j */ table[j].slope = value; table[j].x = (float) x; table[j].y = (float) y; } static int ValidLine(slope_entry *table, int x_origin, int y_origin, Position *x_ret, Position *y_ret) { /* LaTeX supports only several slopes... this routine guarantees * correct LaTeX-slopes and, furthermore, dependent on different * TeX-implementations, other (& unlimited) slopes, too . */ /* slope = y/x */ /* * one of x_ret or y_ret is adjusted */ float x, y; /* real slope as fraction */ float h, v; /* best approximating slope as fraction */ int i, sign; /* sign indicates positive or * negative slope */ float vgl; /* real slope */ float lambda1, lambda2; float x_new, y_new; /* (x_new,y_ret) or (x_ret,y_new) are * the best new coordinates of the * endpoint of the line */ /* the minimum length is 10 points */ /* correct it, if necessary ...call to do this... */ if (unlimitedSlopes) { /* unlimited slopes require no coordinate-modification */ ValidLength(x_origin, y_origin, x_ret, y_ret); return -1; } x = (float) (*x_ret - x_origin); y = (float) (*y_ret - y_origin); /* exceptions: if slope is zero or infinite return directly */ if ((x == 0) || (y == 0)) { ValidLength(x_origin, y_origin, x_ret, y_ret); return -2; } vgl = y / x; /* the selected (positive|negative) slope */ sign = (vgl > 0) ? 1 : -1; if (vgl < 0) vgl = -vgl; /* searching the table */ for (i = 0; table[i].slope < vgl; i++); /* * i points to the first element, which is equal to/bigger than 'vgl' * ( or it points to the marker ) */ if (table[i].slope == END_SLOPE_TABLE) { if (vgl > (2 * table[i-1].slope)) { *x_ret = x_origin; /* slope is infinite */ ValidLength(x_origin, y_origin, x_ret, y_ret); return -3; } else i--; } else if (i == 0) if (vgl < (float) (0.5 * table[0].slope)) { *y_ret = y_origin; ValidLength(x_origin, y_origin, x_ret, y_ret); return -4; } /* compute best approximating slope */ if (i != 0) if ((vgl - table[i - 1].slope) < (table[i].slope - vgl)) i--; /* i points to the best slope */ /* compute new coordinates */ /* it should be the nearest one to the original coordinate */ /* see documentation... */ h = (float) (table[i].x * sign); v = table[i].y; /* compute new */ /* lambda first */ lambda1 = x / h; lambda2 = y / v; x_new = (float) x_origin + lambda2 * h; y_new = (float) y_origin + lambda1 * v; h = x_new - (float) (*x_ret); v = y_new - (float) (*y_ret); if (h < 0) h *= (-1); if (v < 0) v *= (-1); if (h < v) *x_ret = (int) x_new; else *y_ret = (int) y_new; /* coordinates are corrected */ ValidLength(x_origin, y_origin, x_ret, y_ret); return i; } static void ValidLength(int x, int y, Position *x_ret, Position *y_ret) { /* if the length of a line/vector is less than 10 points */ /* the coordinates have to be adjusted. this is an option in */ /* the settings-menu */ int ex = (*x_ret); int ey = (*y_ret); int min_length; /* disabled? */ if (noMinLength) return; /* need to be exact here */ /* %%%% generous rounding could avoid lines which are too short */ min_length = (int) (((float) MIN_LENGTH)*zoomFactor * fileScale/preciseScale + 0.5); /* line shorter than 10 points ? */ if ((abs(1 + x - ex) >= min_length)) return; if (x == ex || y == ey) /* no restriction on horiz. and vert. lines */ return; else { *x_ret = (ex < x) ? (x - min_length) : (x + min_length); *y_ret = y + ((*x_ret - x) * (ey - y)) / (ex - x); } }