]> Shamusworld >> Repos - architektonas/blob - src/fileio.cpp
02ab2f577dc60af0a95f392a60e069b32b57b631
[architektonas] / src / fileio.cpp
1 //
2 // fileio.cpp: Architektonas file save/load support
3 //
4 // Part of the Architektonas Project
5 // (C) 2013 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // Who  When        What
11 // ---  ----------  ------------------------------------------------------------
12 // JLH  02/20/2013  Created this file
13 //
14
15 #include "fileio.h"
16
17 //#include <stdio.h>
18 #include <errno.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <vector>
22 #include "structs.h"
23
24 /*
25 How to handle connected objects
26 -------------------------------
27
28 Every Object has a vector<Object *> which enumerates all Objects connected to
29 the one we're looking at. So it looks like we'll have to take a two pass
30 approach to loading and saving.
31
32 Basically, in the saving case, first we write out all objects, keeping a
33 pointer-to-index-number record. Second, we loop through all the objects we
34 wrote out, writing connection lists. Format (indices are 1-based):
35
36 CONNECTIONS
37 1: 12 3
38 3: 1
39 12: 1
40 ENDCONNECTIONS
41
42 In the reading case, we do pretty much the same: we construct pointer-to-index-
43 number list, then read the connection list. The PTIN connects the index to the
44 Object pointers we've created. Then we simply call the Object's Connect()
45 function to connect the objects.
46
47 Small problem though: How does a Dimension know which points on a Line it's
48 connected to? This approach tells the Dimension it's connected to the Line and
49 the Line that it's connected to the Dimension, but not which points.
50
51 How to handle them then? Do we list the point with the Object pointed at? A
52 Line can contain an infinite number of points to connect with besides its
53 endpoints.
54
55 So with each connection Object in the vector, there would also have to be a
56 corresponding point to go with it, that would be gotten from the other Object's
57 Connect() function. Or, instead of a point, a parameter value?
58
59 Doing that, with the Line and a parameter "t", if t == 0 we have endpoint 1.
60 if t == 1, then we have endpoint 2. With a Circle, the parameter is a number
61 between 0 and 1 (scaled to 0 to 2π). With an Arc, the parameter goes from 0 to
62 1, 0 being enpoint 1 and 1 being endpoint 2.
63
64 How does this work for moving objects that are connected? Again, with the Line
65 and Dimension. The Line's connections looks like this:
66
67 Object *: dim1, t = 0
68 Object *: dim1, t = 1
69
70 Dimension looks like this:
71
72 Object *: line1, t = 0
73 Object *: line1, t = 1
74
75 For Dimensions, it can query the connected object (if any) using something like
76 GetPointForParameter(). That way it can figure out where its endpoints are. If
77 there is no connected point, then it uses its internal point.
78
79
80 Dimensions are special cases of lines: They have exactly *two* points and none
81 in between. Therefore, the Dimension object only needs to have two points. But
82 those points can be connected to multiple objects. The can also be connected to
83 no points/Objects too.
84
85 How to describe them and their connections (or lack thereof)?
86
87 Would have to be a 2nd pass, after all objects have been written out in order.
88 Then you could do something like:
89
90 DIMCONNECTIONS
91 8 (the Dimension #): 1 (the Object # for point 1) 1 (the Object # for point 2)
92 ENDDIMCONNECTIONS
93
94
95
96 Connection attributes: E.g., between a Line a Circle, it can be tangent,
97 perpendicular, or an arbitrary angle. How to encode that information? It's not
98 intrinsic to either the Line or the Circle, but is a function of the
99 relationship between them by virtue of their connection.
100
101
102 OTHER CONSIDERATIONS:
103 ---------------------
104
105   - Need to figure out how to store the Layer list (should layer list be optional?)
106   - Need to figure out how to store the Block list and blocks
107
108
109 */
110
111 //enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc,
112 //      OTFDimension, OTFPolygon, OTFText, OTFImage, OTFBlock, OTFEndOfFile };
113 enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile };
114
115
116 // Instantiate class variables
117 /*static*/ int FileIO::objectFileType = OTFObject;
118
119
120 /*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c)
121 {
122         /* Approach: loop through the container, doing a depth-first traversal. Any
123            extra containers found are looped through until there aren't any more
124            down, then ordinary objects are described. This can be handled by a
125            virtual Object function that reports the object by itself if it's a non-
126            Container, otherwise it enumerates all objects within itself. */
127
128         fprintf(file, "ARCHITEKTONAS DRAWING V1.1\n");
129         WriteObjectToFile(file, (Object *)c);
130         fprintf(file, "END\n");
131         return true;
132 }
133
134
135 /*static*/ bool FileIO::LoadAtnsFile(FILE * file, Container * drawing)
136 {
137         float version;
138
139         fscanf(file, "ARCHITEKTONAS DRAWING V%f", &version);
140
141         if (version == 1.0f)
142                 return LoadVersion1_0(file, drawing);
143         else if (version == 1.1f)
144                 return LoadVersion1_1(file, drawing);
145
146 //printf("LoadAtnsFile: Could not locate version! (version=%f)\n", version);
147         return false;
148 }
149
150
151 /*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing)
152 {
153         // Approach: read each object in the file, one by one. If the object is a
154         // Container, add objects to it until an "endContainer" marker is found.
155         // This will require a stack to maintain the current Container.
156         std::vector<Container *> containerStack;
157         Container * currentTopContainer = drawing;
158
159         while (!feof(file))
160         {
161                 // Reconstruct the object
162                 Object * obj = GetObjectFromFile(file);
163
164                 // objectFileType is set in GetObjectFromFile()...
165                 if (objectFileType == OTFObject)
166                 {
167                         if (obj == NULL)
168                                 return false;
169
170                         currentTopContainer->objects.push_back(obj);
171 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
172
173                         // If the object is a container, push current TLC on the stack and
174                         // set it as the new TLC
175                         if (obj->type == OTContainer)
176                         {
177                                 containerStack.push_back(currentTopContainer);
178                                 currentTopContainer = (Container *)obj;
179                         }
180                 }
181                 else if (objectFileType == OTFContainerEnd)
182                 {
183                         // Container is done, so pop the stack to get back the previous TLC
184                         currentTopContainer = containerStack.back();
185                         containerStack.pop_back();
186                 }
187                 else if (objectFileType == OTFEndOfFile)
188                 {
189 //printf("Load: container size = %li\n", drawing->objects.size());
190                         return true;
191                 }
192         }
193
194         return false;
195 }
196
197
198 /*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing)
199 {
200         // Approach: read each object in the file, one by one. If the object is a
201         // Container, add objects to it until an "endContainer" marker is found.
202         // This will require a stack to maintain the current Container.
203         std::vector<Container *> containerStack;
204         Container * currentTopContainer = drawing;
205
206         while (!feof(file))
207         {
208                 // Reconstruct the object (extended format!)
209                 Object * obj = GetObjectFromFile(file, true);
210
211                 // objectFileType is set in GetObjectFromFile()...
212                 if (objectFileType == OTFObject)
213                 {
214                         if (obj == NULL)
215                                 return false;
216
217                         currentTopContainer->objects.push_back(obj);
218 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
219
220                         // If the object is a container, push current TLC on the stack and
221                         // set it as the new TLC
222                         if (obj->type == OTContainer)
223                         {
224                                 containerStack.push_back(currentTopContainer);
225                                 currentTopContainer = (Container *)obj;
226                         }
227                 }
228                 else if (objectFileType == OTFContainerEnd)
229                 {
230                         // Container is done, so pop the stack to get back the previous TLC
231                         currentTopContainer = containerStack.back();
232                         containerStack.pop_back();
233                 }
234                 else if (objectFileType == OTFEndOfFile)
235                 {
236 //printf("Load: container size = %li\n", drawing->objects.size());
237                         return true;
238                 }
239         }
240
241         return false;
242 }
243
244
245 /*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/)
246 {
247         char buffer[256];
248         char textBuffer[65536];
249         int foundLayer = 0;
250         /*int num =*/ fscanf(file, "%s", buffer);
251         Object * obj = NULL;
252         objectFileType = OTFObject;
253 //printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer);
254
255 // The following fugliness is for troubleshooting. Can remove later.
256         if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0))
257 {
258 errno = 0;
259                 /*num =*/ fscanf(file, " %i ", &foundLayer);
260 //printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer);
261 if (errno)
262 {
263         if (errno == EAGAIN)
264                 printf("EAGAIN\n");
265         else if (errno == EBADF)
266                 printf("EBADF\n");
267         else if (errno == EILSEQ)
268                 printf("EILSEQ\n");
269         else if (errno == EINTR)
270                 printf("EINTR\n");
271         else if (errno == EINVAL)
272                 printf("EINVAL\n");
273         else if (errno == ENOMEM)
274                 printf("ENOMEM\n");
275         else if (errno == ERANGE)
276                 printf("ERANGE\n");
277         else
278                 printf("errno = %i\n", errno);
279 }
280 }
281
282         // Need to add pen attributes as well... do that for v1.1 :-P
283         if (strcmp(buffer, "LINE") == 0)
284         {
285                 Point p1, p2;
286                 fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y);
287                 obj = (Object *)new Line(p1, p2);
288         }
289         else if (strcmp(buffer, "CIRCLE") == 0)
290         {
291                 Point p;
292                 double r;
293                 fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r);
294                 obj = (Object *)new Circle(p, r);
295         }
296         else if (strcmp(buffer, "ARC") == 0)
297         {
298                 Point p;
299                 double r, a1, a2;
300                 fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2);
301                 obj = (Object *)new Arc(p, r, a1, a2);
302         }
303         else if (strcmp(buffer, "TEXT") == 0)
304         {
305                 Point p;
306                 fscanf(file, "(%lf,%lf) \"%[^\"]\"", &p.x, &p.y, textBuffer);
307                 obj = (Object *)new Text(p, textBuffer);
308         }
309         else if (strcmp(buffer, "DIMENSION") == 0)
310         {
311                 Point p1, p2;
312                 DimensionType type;
313                 fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type);
314                 obj = (Object *)new Dimension(p1, p2, type);
315         }
316         else if (strcmp(buffer, "CONTAINER") == 0)
317         {
318                 obj = (Object *)new Container();
319 //              objectFileType = OTFContainer;
320         }
321         else if (strcmp(buffer, "ENDCONTAINER") == 0)
322         {
323                 objectFileType = OTFContainerEnd;
324         }
325         else if (strcmp(buffer, "END") == 0)
326         {
327                 objectFileType = OTFEndOfFile;
328         }
329         else
330                 printf("Unknown object type '%s'...\n", buffer);
331
332         if (obj != NULL)
333         {
334                 obj->layer = foundLayer;
335
336                 if (extended && (obj->type != OTContainer))
337                 {
338                         fscanf(file, " (%i, %f, %i)", &obj->color, &obj->thickness, &obj->style);
339                 }
340         }
341
342         return obj;
343 }
344
345
346 /*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj)
347 {
348         // Sanity check
349         if (obj == NULL)
350                 return false;
351
352         switch (obj->type)
353         {
354         case OTLine:
355                 fprintf(file, "LINE %i (%lf,%lf) (%lf,%lf)", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y);
356                 break;
357         case OTCircle:
358                 fprintf(file, "CIRCLE %i (%lf,%lf) %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]);
359                 break;
360         case OTEllipse:
361                 break;
362         case OTArc:
363                 fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0], obj->angle[0], obj->angle[1]);
364                 break;
365         case OTPolygon:
366                 break;
367         case OTDimension:
368 //              fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
369                 fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y, ((Dimension *)obj)->subtype);
370                 break;
371         case OTSpline:
372                 break;
373         case OTText:
374                 fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str());
375                 break;
376         case OTContainer:
377         {
378                 Container * c = (Container *)obj;
379
380                 if (c->topLevel == false)
381                         fprintf(file, "CONTAINER %i\n", obj->layer);
382
383                 std::vector<void *>::iterator i;
384
385                 for(i=c->objects.begin(); i!=c->objects.end(); i++)
386                         WriteObjectToFile(file, (Object *)*i);
387
388                 if (c->topLevel == false)
389                         fprintf(file, "ENDCONTAINER\n");
390
391                 break;
392         }
393         default:
394                 break;
395         }
396
397         if (obj->type != OTContainer)
398                 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);
399
400         return true;
401 }
402