/* * Copyright 1991 Klaus Zitzmann, 1993-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. * * Authors: Klaus Zitzmann * Johannes Sixt */ #include #include #include /* tolower() */ #include /* need MAXPATHLEN */ #include #include #include "objects.h" #include "yyscan.h" #include "io_trans.h" #include "patchlevel.h" /* included when saving files */ #define CANVAS_WIDTH 500 #define CANVAS_HEIGHT 600 TeXPictObj *objList; /* list of objects */ Boolean changeMade; /* database changed since last save? */ Widget pboard; char moveBaseStr[MOVE_BASE_STR_LEN + 1]; /* see Donald Knuth: The TeXbook, Addison Wesley, p. 57 */ static const struct { char *name; float scale; } pt_scale[] = { "pt", 1.0, /* UnitPT */ "cm", 72.27 / 2.54, /* UnitCM */ "mm", 72.27 / 25.4, /* UnitMM */ "pc", 12.0, /* UnitPC */ "in", 72.27, /* UnitIN */ "bp", 72.27 / 72.0, /* UnitBP */ "dd", 1238.0 / 1157.0, /* UnitDD */ "cc", 12.0 * 1238.0 / 1157.0, /* UnitCC */ "sp", 1.0 / 65536.0, /* UnitSP */ "em", 10.0, /* UnitEM */ /* roughly 1em=10pt */ }; static String defaultUnitLen; /* \unitlength for new files */ static float unitlength; /* value registered with \unitlength */ static Unit unit; /* unit of this value */ static TeXPictObj obj; /* temporary buffer during scan */ static OffsetFunc offset_cb; /* callbacks for MoveBase; may become array */ static float overallxoff, overallyoff; static void ReverseObjectList(void); static void NewBox(TeXPictType type); static void NewLine(TeXPictType type, float length); static void NewCircle(TeXPictType type); static void NewBezier(TeXPictType type); static void Warning(char *text); static void Refresh(Widget w, XtPointer client_data, XEvent *event, Boolean *ctd); static Boolean ParseUnitlength(String string, float *ul, Unit *u); void InitDB(Widget parent, float initMoveBase, String initUnitLength) { /* create the drawing area */ pboard = XtVaCreateManagedWidget("canvas", compositeWidgetClass, parent, XtNwidth, (XtArgVal) CANVAS_WIDTH, XtNheight, (XtArgVal) CANVAS_HEIGHT, NULL); XtAddEventHandler(pboard, ExposureMask, False, Refresh, NULL); objList = NULL; /* initialize unitlength */ defaultUnitLen = initUnitLength; if (!ParseUnitlength(defaultUnitLen, &unitlength, &unit)) { /* unit not found */ fprintf(stderr, "xtexcad: don't understand default \\unitlength=\"%s\";" " using 1.0pt\n", initUnitLength); } SetFileScale(unitlength * pt_scale[(int) unit].scale); /* initialize moveBase */ sprintf(moveBaseStr, "%.2f", initMoveBase); } /* The coordinates stored in the objects are in pt. */ int LoadFromFile(char *name, ErrorCB errorCB) { FILE *file; int t, opt; /* token */ float length; /* open file */ if ((file = fopen(name, "r")) == NULL) { errorCB("Can't open file"); return 0; } yyinitscan(file); /* initialize scanner */ Erase(); /* also sets unitlength, unit and fileScale */ t = yylex(); while (t != TOK_END && t != 0) { /* yylex returns 0 on EOF */ obj.data.rectangular.align = ALIGN_V_CENTER | ALIGN_H_CENTER; switch (t) { case TOK_UNITLENGTH: unitlength = yyfloatval; if (unitlength == 0.0) { unitlength = 1.0; Warning("\\unitlength was 0; is 1 now"); } if (yylex() != TOK_UNIT) { Warning("unit not found, assuming pt"); unit = UnitPT; } else unit = yyunit; SetFileScale(unitlength * pt_scale[(int) unit].scale); break; case TOK_BEGIN: /* read \begin{picture}(x,y)(xo,yo); * (the last two are optional) * none of these values is used */ if (yylex() != TOK_FLOAT) Warning("error in \\begin{picture} line"); if (yylex() != TOK_FLOAT) Warning("error in \\begin{picture} line"); t = yylex(); if (t == TOK_FLOAT) { if (yylex() != TOK_FLOAT) Warning("error in \\begin{picture} line"); } else { /* re-examine the token in t: * go back to switch(t) */ continue; } break; case TOK_PUT: if (yylex() != TOK_FLOAT) { Warning("missing coord of \\put, assuming 0.0"); obj.x = 0.0; } else obj.x = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing coord of \\put, assuming 0.0"); obj.y = 0.0; } else obj.y = yyfloatval; break; case TOK_TEXT: strncpy(obj.data.rectangular.text, yystrval, MAX_TEXT_LEN); obj.data.rectangular.text[MAX_TEXT_LEN] = '\0'; obj.data.rectangular.align = ALIGN_V_BOTTOM | ALIGN_H_LEFT; obj.data.rectangular.w = obj.data.rectangular.h = 0.0; NewBox(TeXText); break; case TOK_DASHBOX: if (yylex() != TOK_FLOAT) { Warning("missing dashlength, assuming 5.0"); obj.data.rectangular.xtra = DASH_LEN / fileScale; } else obj.data.rectangular.xtra = yyfloatval; /* drop thru */ case TOK_FRAMEBOX: case TOK_MAKEBOX: if (yylex() != TOK_FLOAT) { Warning("missing extent of box, assuming 0.0"); obj.data.rectangular.w = 0.0; } else obj.data.rectangular.w = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing extent of box, assuming 0.0"); obj.data.rectangular.h = 0.0; } else obj.data.rectangular.h = yyfloatval; opt = yylex(); if (opt == TOK_LETTERS_OPT) { char *p; for (p = yystrval; *p != '\0'; p++) { #define objalign obj.data.rectangular.align switch (*p) { case 't': objalign &= ~ALIGN_V_ALL; objalign |= ALIGN_V_TOP; break; case 'b': objalign &= ~ALIGN_V_ALL; objalign |= ALIGN_V_BOTTOM; break; case 'l': objalign &= ~ALIGN_H_ALL; objalign |= ALIGN_H_LEFT; break; case 'r': objalign &= ~ALIGN_H_ALL; objalign |= ALIGN_H_RIGHT; break; } #undef objalign } opt = yylex(); } if (opt != TOK_TEXT) { Warning("text expected; assuming empty"); obj.data.rectangular.text[0] = '\0'; } else { strncpy(obj.data.rectangular.text, yystrval, MAX_TEXT_LEN); obj.data.rectangular.text[MAX_TEXT_LEN] = '\0'; } if (t == TOK_FRAMEBOX) { NewBox(TeXFramedText); } else if (t == TOK_DASHBOX) { NewBox(TeXDashedText); } else { obj.x += obj.data.rectangular.w / 2.0; obj.y += obj.data.rectangular.h / 2.0; obj.data.rectangular.w = obj.data.rectangular.h = 0.0; NewBox(TeXText); } break; case TOK_LINE: case TOK_VECTOR: if (yylex() != TOK_FLOAT) { Warning("missing coord: \\line, assuming 0.0"); obj.data.linear.ex = 0.0; } else obj.data.linear.ex = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing coord: \\line, assuming 0.0"); obj.data.linear.ey = 0.0; } else obj.data.linear.ey = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing length, assuming 100.0"); length = 100.0/fileScale; } else length = yyfloatval; NewLine(t == TOK_LINE ? TeXLine : TeXVector, length); break; case TOK_CIRCLE: case TOK_CIRCLE_AST: if (yylex() != TOK_FLOAT) { Warning("missing diameter, assuming 1.0"); obj.data.r = 0.5/fileScale; } else obj.data.r = yyfloatval/2.0; NewCircle(t == TOK_CIRCLE ? TeXCircle : TeXDisc); break; case TOK_OVAL: if (yylex() != TOK_FLOAT) { Warning("missing extent of oval, assuming 0.0"); obj.data.rectangular.w = 0.0; } else obj.data.rectangular.w = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing extent of oval, assuming 0.0"); obj.data.rectangular.h = 0.0; } else obj.data.rectangular.h = yyfloatval; obj.x -= obj.data.rectangular.w / 2.0; obj.y -= obj.data.rectangular.h / 2.0; { /* determine corner radius */ /* don't worry about rounding errors */ Position x1, y1; float w, h; w = obj.data.rectangular.w; h = obj.data.rectangular.h; x1 = (obj.x + (w < h ? w : h) / 2.0)*fileScale; y1 = obj.y*fileScale; ValidRadius((int) (obj.x*fileScale), (int) (obj.y*fileScale), &x1, &y1, circle_diameter, 1.0); obj.data.rectangular.xtra = (((float) x1)/fileScale - obj.x); } NewBox(TeXOval); break; case TOK_RULE: if (yylex() != TOK_FLOAT) { Warning("missing extent of rule, assuming 0.0"); obj.data.rectangular.w = 0.0; } else obj.data.rectangular.w = yyfloatval; /* "\unitlength" or a unit follows */ t = yylex(); if (t == TOK_UNIT) { obj.data.rectangular.w *= pt_scale[(int) yyunit].scale / fileScale; } else if (t != TOK_DIM_UNITLENGTH) Warning("missing unit, assuming \\unitlength"); if (yylex() != TOK_FLOAT) { Warning("missing extent of rule, assuming 0.0"); obj.data.rectangular.h = 0.0; } else obj.data.rectangular.h = yyfloatval; /* "\unitlength" or a unit follows */ t = yylex(); if (t == TOK_UNIT) { obj.data.rectangular.h *= pt_scale[(int) yyunit].scale / fileScale; } else if (t != TOK_DIM_UNITLENGTH) Warning("missing unit, assuming \\unitlength"); NewBox(TeXFilled); break; case TOK_BEZIER: if (yylex() != TOK_FLOAT) { Warning("missing number in bezier"); } /* fall thru */ case TOK_QBEZIER: if (yylex() != TOK_FLOAT) { Warning("missing coord in bezier, assuming 0.0"); obj.x = 0.0; } else obj.x = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing coord in bezier, assuming 0.0"); obj.y = 0.0; } else obj.y = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing coord in bezier, assuming 0.0"); obj.data.bezier.sx = 0.0; } else obj.data.bezier.sx = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing coord in bezier, assuming 0.0"); obj.data.bezier.sy = 0.0; } else obj.data.bezier.sy = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing coord in bezier, assuming 0.0"); obj.data.bezier.ex = 0.0; } else obj.data.bezier.ex = yyfloatval; if (yylex() != TOK_FLOAT) { Warning("missing coord in bezier, assuming 0.0"); obj.data.bezier.ey = 0.0; } else obj.data.bezier.ey = yyfloatval; NewBezier(TeXBezier); break; case TOK_THICKLINE: Warning("Ignoring \\thicklines command"); break; case TOK_THINLINE: Warning("Ignoring \\thinlines command"); break; default: break; } /* switch */ t = yylex(); } /* while */ if (t != TOK_END) { Warning("unexpected end of file"); } fclose(file); if (objList == NULL) { errorCB("Nothing was loaded"); Erase(); /* reset everything to sensible values */ return 1; } else { ReverseObjectList(); } /* apply zoom factor and filescale */ RescaleAll(zoomFactor*fileScale); overallyoff = 0.0; MoveBase(NULL, (XtPointer) MoveBaseCenter, NULL); changeMade = False; return 1; } static void ReverseObjectList(void) { TeXPictObj *obj, *next, *newList = NULL; for (obj = objList; obj != NULL; obj = next) { next = obj->next; obj->next = newList; newList = obj; } objList = newList; } static void Warning(char *text) { fprintf(stderr, "Warning(%d): %s\n", lineno, text); } /* These routines insert a new object into the database */ static void NewBox(TeXPictType type) { TeXPictObj *newObj; newObj = ObjCreate(type); newObj->next = objList; objList = newObj; newObj->x = obj.x; newObj->y = - obj.data.rectangular.h - obj.y; newObj->data.rectangular = obj.data.rectangular; } static void NewLine(TeXPictType type, float length) { TeXPictObj *newObj; newObj = ObjCreate(type); newObj->next = objList; objList = newObj; newObj->x = obj.x; newObj->y = -obj.y; /* ex and ey contain the slope information */ if (obj.data.linear.ex == 0.0) { /* vertical line */ newObj->data.linear.ex = obj.x; if (obj.data.linear.ey > 0.0) newObj->data.linear.ey = -length - obj.y; else newObj->data.linear.ey = length - obj.y; } else { /* all other lines */ if (obj.data.linear.ex > 0.0) newObj->data.linear.ex = obj.x + length; else { newObj->data.linear.ex = obj.x - length; obj.data.linear.ex = -obj.data.linear.ex; } newObj->data.linear.ey = -obj.y - length * obj.data.linear.ey / obj.data.linear.ex; } } static void NewCircle(TeXPictType type) { TeXPictObj *newObj; newObj = ObjCreate(type); newObj->next = objList; objList = newObj; newObj->x = obj.x; newObj->y = -obj.y; newObj->data.r = obj.data.r; } static void NewBezier(TeXPictType type) { TeXPictObj *newObj; newObj = ObjCreate(type); newObj->next = objList; objList = newObj; newObj->x = obj.x; newObj->y = -obj.y; newObj->data.bezier.sx = obj.data.bezier.sx; newObj->data.bezier.sy = -obj.data.bezier.sy; newObj->data.bezier.ex = obj.data.bezier.ex; newObj->data.bezier.ey = -obj.data.bezier.ey; } /*ARGSUSED*/ int SaveToFile(char *name, ErrorCB errorCB, char *savesuffix) { char bak_name[MAXPATHLEN+4]; FILE *file; TeXObjExtent overall; TeXPictObj *obj; if (objList == NULL) { errorCB("Nothing to save"); return 0; } /* scale objects to original size and include unit factor*/ RescaleAll(1.0/(fileScale*zoomFactor)); /* make a backup by renaming the original file */ strcpy(bak_name, name); strcat(bak_name, savesuffix); /* resource's default is ".old" */ rename(name, bak_name); /* don't care if no success */ if ((file = fopen(name, "w")) == NULL) { errorCB("Can't open file"); return 0; } GetOverallExtent(&overall); /* write header; put everything into a group, so \unitlength is local */ fprintf(file, "{%% Picture saved by xtexcad %s.%s\n", VERSION, PATCHLEVEL); fprintf(file, "\\unitlength=%f%s\n", unitlength, pt_scale[(int) unit].name); /* x- and y-coordinate transformation is done in ObjSave() */ fprintf(file, "\\begin{picture}(%.2f,%.2f)(%.2f,%.2f)\n", overall.x_max - overall.x_min, overall.y_max - overall.y_min, 0.0,0.0); for (obj = objList; obj != NULL; obj = obj->next) { ObjSave(obj, file, overall.x_min, overall.y_max); } fprintf(file, "\\end{picture}}\n"); fclose(file); changeMade = False; /* scale back objects */ RescaleAll(zoomFactor*fileScale); return 1; } void RegisterOffset(OffsetFunc func) { offset_cb = func; } /*ARGSUSED*/ void MoveBase(Widget wid, XtPointer client_data, XtPointer call_data) { float x_off, y_off; float offset, xf, yf, rastheight; TeXObjExtent overall; TeXPictObj *obj; Dimension w, h; static Position xp, yp; x_off = y_off = 0.0; if (sscanf(moveBaseStr,"%f", &offset)!=1) offset = 1.0; /* take zooming and unitlength into account */ offset = offset*zoomFactor*fileScale; switch ((MoveBaseID) client_data) { case MoveBaseUp: y_off = -offset; break; case MoveBaseDown: y_off = offset; break; case MoveBaseLeft: x_off = -offset; break; case MoveBaseRight: x_off = offset; break; case MoveBaseCenter: if (objList != NULL) { /* determine overall extent */ GetOverallExtent(&overall); XtVaGetValues(pboard, XtNwidth, (XtArgVal) &w, XtNheight, (XtArgVal) &h, NULL); x_off =((float) w - overall.x_max - overall.x_min)/2.0; y_off =((float) h - overall.y_max - overall.y_min)/2.0; xp = (Position) (x_off + overall.x_min); yp = (Position) (y_off + overall.y_max); /* gross hack to center picture into snapped * position, but take care if rastheight and * unitlength are not sensible */ if (sscanf(rasterHeightStr, "%f", &rastheight) != 1) rastheight = 1.0; /* bad rastheight */ rastheight = rastheight*zoomFactor*fileScale; while ((4.0*rastheight > (float) w)|| (4.0*rastheight > (float) h)){ rastheight /= 10.0; /* bad unitlength*rastheight */ } SnapPositionFloat(&xp, &yp, &xf, &yf, rastheight); x_off = xf - overall.x_min; y_off = yf - overall.y_max; } } /* move objects */ for (obj = objList; obj != NULL; obj = obj->next) { ObjOffset(obj, x_off, y_off); } overallxoff += x_off/zoomFactor; overallyoff += y_off/zoomFactor; /* %%%% disable redrawing here */ offset_cb(x_off, y_off); /* %%%% enable redrawing here */ /* redraw */ if (XtIsRealized(pboard)) XClearArea(XtDisplay(pboard), XtWindow(pboard), 0, 0, 0, 0, True); } void SetCoordOrigin() { Dimension h, y; float rast_height; overallxoff = 0.0; XtVaGetValues(pboard, XtNheight, (XtArgVal) &h, NULL); if (sscanf(rasterHeightStr, "%f", &rast_height) != 1) rast_height = 1.0; /* round offset in y direction so that SnapPosition gives nice values although starting from top not bottom */ y = ((float) h)/(zoomFactor*fileScale*rast_height); overallyoff = ((float) y)*fileScale*rast_height; } void GetCoords(int x, int y, float *xf, float *yf) { *xf = (((float) x)/zoomFactor - overallxoff)/fileScale; *yf = -(((float) y)/zoomFactor - overallyoff)/fileScale; } void GetOverallExtent(TeXObjExtent *overall) { #define LARGE_COORD 1000000.0 #define SMALL_COORD (-1000000.0) TeXObjExtent extent; TeXPictObj *obj; overall->x_min = overall->y_min = LARGE_COORD; overall->x_max = overall->y_max = SMALL_COORD; for (obj = objList; obj != NULL; obj = obj->next) { ObjGetExtent(obj, &extent); if (extent.x_min < overall->x_min) overall->x_min = extent.x_min; if (extent.y_min < overall->y_min) overall->y_min = extent.y_min; if (extent.x_max > overall->x_max) overall->x_max = extent.x_max; if (extent.y_max > overall->y_max) overall->y_max = extent.y_max; } } void RescaleAll(float zoom) { TeXPictObj* obj; for (obj = objList; obj != NULL; obj = obj->next) { ObjRescale(obj, zoom); } } /*ARGSUSED*/ void Erase(void) { /* erases all objects */ TeXPictObj *obj, *next; for (next = objList; (obj = next);) { next = obj->next; ObjDestroy(obj); } objList = NULL; changeMade = False; XClearWindow(XtDisplay(pboard), XtWindow(pboard)); /* set default unit length (ignoring errors) */ ParseUnitlength(defaultUnitLen, &unitlength, &unit); SetFileScale(unitlength * pt_scale[(int) unit].scale); SetCoordOrigin(); } static void Refresh(Widget w, XtPointer client_data, XEvent *event, Boolean *ctd) { Display *disp; Drawable win; TeXPictObj *obj; if (event->xexpose.count != 0) return; disp = XtDisplay(w); win = XtWindow(w); /* redraw objects */ for (obj = objList; obj != NULL; obj = obj->next) { ObjDraw(obj, disp, win); } } static Boolean ParseUnitlength(String string, float *ul, Unit *u) { int n; char ulstr[4]; n = sscanf(string, "%f%3s", ul, ulstr); if (n == 2 && /* ulstr must have exactly 2 characters */ ulstr[0] != '\0' && ulstr[1] != 0 && ulstr[2] == '\0') { ulstr[0] = tolower(ulstr[0]); ulstr[1] = tolower(ulstr[1]); for (n = 0; n < XtNumber(pt_scale); n++) { if (strcmp(pt_scale[n].name, ulstr) == 0) { *u = (Unit) n; return True; } } } /* syntax error */ *ul = 1.0; *u = UnitPT; return False; }