1 /*****************************************************************************/
2 /* LibreDWG - free implementation of the DWG file format */
3 /* */
4 /* Copyright (C) 2009-2024 Free Software Foundation, Inc. */
5 /* Copyright (C) 2010 Thien-Thi Nguyen */
6 /* */
7 /* This library is free software, licensed under the terms of the GNU */
8 /* General Public License as published by the Free Software Foundation, */
9 /* either version 3 of the License, or (at your option) any later version. */
10 /* You should have received a copy of the GNU General Public License */
11 /* along with this program. If not, see <http://www.gnu.org/licenses/>. */
12 /*****************************************************************************/
13
14 /*
15 * dwg2SVG.c: convert a DWG to SVG
16 * written by Felipe CorrĂȘa da Silva Sances
17 * modified by Rodrigo Rodrigues da Silva
18 * modified by Thien-Thi Nguyen
19 * modified by Reini Urban
20 *
21 * TODO: all entities: 3DSOLID, SHAPE, ARC_DIMENSION, ATTRIB, DIMENSION*,
22 * *SURFACE, GEOPOSITIONMARKER/CAMERA/LIGHT, HATCH, HELIX,
23 * IMAGE/WIPEOUT/UNDERLAY, LEADER, MESH, MINSERT, MLINE, MTEXT,
24 * MULTILEADER, OLE2FRAME, OLEFRAME, POLYLINE_3D, POLYLINE_MESH,
25 * POLYLINE_PFACE, RAY, XLINE, SPLINE, TABLE, TOLERANCE, VIEWPORT?
26 * common_entity_data: ltype, ltype_scale.
27 * PLINE: widths, bulges.
28 */
29
30 #define _GNU_SOURCE /* make musl expose strcasestr */
31 #include "../src/config.h"
32 #include <stdio.h>
33 #include <stdlib.h>
34 #ifdef HAVE_STRCASESTR
35 # undef __DARWIN_C_LEVEL
36 # define __DARWIN_C_LEVEL __DARWIN_C_FULL
37 # ifndef __USE_GNU
38 # define __USE_GNU
39 # endif
40 # ifndef __BSD_VISIBLE
41 # define __BSD_VISIBLE 1
42 # endif
43 #endif
44 #include <string.h>
45 #ifdef HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif
48 #include "my_getopt.h"
49 #include <math.h>
50 #ifdef HAVE_VALGRIND_VALGRIND_H
51 # include <valgrind/valgrind.h>
52 #endif
53
54 #include <dwg.h>
55 #include <dwg_api.h>
56 #include "bits.h"
57 #include "common.h"
58 #include "escape.h"
59 #include "geom.h"
60 #include "suffix.inc"
61 #include "my_getopt.h"
62
63 static int opts = 0;
64 static int mspace = 0; // only mspace, even when pspace is defined
65 Dwg_Data g_dwg;
66 double model_xmin, model_ymin, model_xmax, model_ymax;
67 double page_width, page_height, scale;
68
69 static void output_SVG (Dwg_Data *dwg);
70
71 static int
72 usage (void)
73 {
74 printf ("\nUsage: dwg2SVG [-v[0-9]] DWGFILE >SVGFILE\n");
75 return 1;
76 }
77 static int
78 opt_version (void)
79 {
80 printf ("dwg2SVG %s\n", PACKAGE_VERSION);
81 return 0;
82 }
83 static int
84 help (void)
85 {
86 printf ("\nUsage: dwg2SVG [OPTION]... DWGFILE >SVGFILE\n");
87 printf ("Converts some 2D elements of the DWG to a SVG.\n"
88 "\n");
89 #ifdef HAVE_GETOPT_LONG
90 printf (" -v[0-9], --verbose [0-9] verbosity\n");
91 printf (" --mspace only model-space, no paper-space\n");
92 printf (" --force-free force free\n");
93 printf (" --help display this help and exit\n");
94 printf (" --version output version information and exit\n"
95 "\n");
96 #else
97 printf (" -v[0-9] verbosity\n");
98 printf (" -m only model-space, no paper-space\n");
99 printf (" -h display this help and exit\n");
100 printf (" -i output version information and exit\n"
101 "\n");
102 #endif
103 printf ("GNU LibreDWG online manual: "
104 "<https://www.gnu.org/software/libredwg/>\n");
105 return 0;
106 }
107
108 static double
109 transform_X (double x)
110 {
111 return x - model_xmin;
112 }
113
114 static double
115 transform_Y (double y)
116 {
117 return page_height - (y - model_ymin);
118 }
119
120 static bool
121 isnan_2BD (BITCODE_2BD pt)
122 {
123 return isnan (pt.x) || isnan (pt.y);
124 }
125
126 static bool
127 isnan_2pt (dwg_point_2d pt)
128 {
129 return isnan (pt.x) || isnan (pt.y);
130 }
131
132 static bool
133 isnan_3BD (BITCODE_3BD pt)
134 {
135 return isnan (pt.x) || isnan (pt.y) || isnan (pt.z);
136 }
137
138 static bool
139 entity_invisible (Dwg_Object *obj)
140 {
141 BITCODE_BS invisible = obj->tio.entity->invisible;
142 Dwg_Object *layer;
143 Dwg_Object_LAYER *_obj;
144 if (invisible)
145 return true;
146
147 if (!obj->tio.entity->layer || !obj->tio.entity->layer->obj)
148 return false;
149 layer = obj->tio.entity->layer->obj;
150 if (layer->fixedtype != DWG_TYPE_LAYER)
151 return false;
152 _obj = layer->tio.object->tio.LAYER;
153 // pre-r13 it is set if the layer color is negative
154 return _obj->on == 0 ? true : false;
155 }
156
157 static double
158 entity_lweight (Dwg_Object_Entity *ent)
159 {
160 // TODO: resolve BYLAYER 256, see above.
161 // stroke-width:%0.1fpx. 100th of a mm
162 int lw = dxf_cvt_lweight (ent->linewt);
163 return lw < 0 ? 0.1 : (double)(lw * 0.001);
164 }
165
166 static char *
167 entity_color (Dwg_Object_Entity *ent)
168 {
169 // TODO: alpha?
170 if (ent->color.index >= 8 && ent->color.index < 256)
171 {
172 const Dwg_RGB_Palette *palette = dwg_rgb_palette ();
173 const Dwg_RGB_Palette *rgb = &palette[ent->color.index];
174 char *s = (char *)malloc (8);
175 sprintf (s, "#%02x%02x%02x", rgb->r, rgb->g, rgb->b);
176 return s;
177 }
178 else if (ent->color.flag & 0x80 && !(ent->color.flag & 0x40))
179 {
180 char *s = (char *)malloc (8);
181 sprintf (s, "#%06x", ent->color.rgb & 0x00ffffff);
182 return s;
183 }
184 else
185 switch (ent->color.index)
186 {
187 case 1:
188 return (char *)"red";
189 case 2:
190 return (char *)"yellow";
191 case 3:
192 return (char *)"green";
193 case 4:
194 return (char *)"cyan";
195 case 5:
196 return (char *)"blue";
197 case 6:
198 return (char *)"magenta";
199 case 7:
200 return (char *)"white";
201 case 0: // ByBlock
202 case 256: // ByLayer
203 default:
204 return (char *)"black";
205 }
206 }
207
208 static void
209 common_entity (Dwg_Object_Entity *ent)
210 {
211 double lweight;
212 char *color;
213 lweight = entity_lweight (ent);
214 color = entity_color (ent);
215 printf (" style=\"fill:none;stroke:%s;stroke-width:%.1fpx\" />\n",
216 color, lweight);
217 if (*color == '#')
218 free (color);
219 }
220
221 // TODO: MTEXT
222 static void
223 output_TEXT (Dwg_Object *obj)
224 {
225 Dwg_Data *dwg = obj->parent;
226 Dwg_Entity_TEXT *text = obj->tio.entity->tio.TEXT;
227 char *escaped;
228 const char *fontfamily;
229 BITCODE_H style_ref = text->style;
230 Dwg_Object *o = style_ref ? dwg_ref_object_silent (dwg, style_ref) : NULL;
231 Dwg_Object_STYLE *style = o ? o->tio.object->tio.STYLE : NULL;
232 BITCODE_2DPOINT pt;
233
234 if (!text->text_value || entity_invisible (obj))
235 return;
236 if (isnan_2BD (text->ins_pt) || isnan_3BD (text->extrusion))
237 return;
238 if (dwg->header.version >= R_2007)
239 escaped = htmlwescape ((BITCODE_TU)text->text_value);
240 else
241 escaped = htmlescape (text->text_value, dwg->header.codepage);
242
243 if (style && o->fixedtype == DWG_TYPE_STYLE && style->font_file
244 && *style->font_file
245 #ifdef HAVE_STRCASESTR
246 && strcasestr (style->font_file, ".ttf")
247 #else
248 && (strstr (style->font_file, ".ttf")
249 || strstr (style->font_file, ".TTF"))
250 #endif
251 )
252 {
253 #ifdef HAVE_STRCASESTR
254 if (strcasestr (style->font_file, "Arial"))
255 #else
256 if ((strstr (style->font_file, "arial"))
257 || strstr (style->font_file, "Arial"))
258 #endif
259 {
260 fontfamily = "Arial";
261 }
262 else
263 fontfamily = "Verdana";
264 }
265 else
266 fontfamily = "Courier";
267
268 transform_OCS_2d (&pt, text->ins_pt, text->extrusion);
269 printf ("\t<text id=\"dwg-object-%d\" x=\"%f\" y=\"%f\" "
270 "font-family=\"%s\" font-size=\"%f\" fill=\"%s\">%s</text>\n",
271 obj->index, transform_X (pt.x), transform_Y (pt.y), fontfamily,
272 text->height /* fontsize */, entity_color (obj->tio.entity),
273 escaped ? escaped : "");
274 free (escaped);
275 }
276
277 static void
278 output_LINE (Dwg_Object *obj)
279 {
280 Dwg_Entity_LINE *line = obj->tio.entity->tio.LINE;
281 BITCODE_3DPOINT start, end;
282
283 if (isnan_3BD (line->start) || isnan_3BD (line->end)
284 || isnan_3BD (line->extrusion) || entity_invisible (obj))
285 return;
286 transform_OCS (&start, line->start, line->extrusion);
287 transform_OCS (&end, line->end, line->extrusion);
288 printf ("\t<!-- line-%d -->\n", obj->index);
289 printf ("\t<path id=\"dwg-object-%d\" d=\"M %f,%f L %f,%f\"\n\t", obj->index,
290 transform_X (start.x), transform_Y (start.y), transform_X (end.x),
291 transform_Y (end.y));
292 common_entity (obj->tio.entity);
293 }
294
295 static void
296 output_XLINE (Dwg_Object *obj)
297 {
298 Dwg_Entity_XLINE *xline = obj->tio.entity->tio.XLINE;
299 BITCODE_3DPOINT invvec;
300 static BITCODE_3DPOINT box[2];
301 int sign[3];
302 double txmin, txmax, tymin, tymax, tzmin, tzmax;
303
304 if (isnan_3BD (xline->point) || isnan_3BD (xline->vector)
305 || entity_invisible (obj))
306 return;
307
308 invvec.x = 1.0 / xline->vector.x;
309 invvec.y = 1.0 / xline->vector.y;
310 invvec.z = 1.0 / xline->vector.z;
311 sign[0] = (invvec.x < 0.0);
312 sign[1] = (invvec.y < 0.0);
313 sign[2] = (invvec.z < 0.0);
314 box[0].x = model_xmin;
315 box[0].y = model_ymin;
316 box[1].x = model_xmax;
317 box[1].y = model_ymin;
318 printf ("\t<!-- xline-%d -->\n", obj->index);
319
320 // untested!
321 /* intersect xline with model_xmin, model_ymin, model_xmax, model_ymax */
322 txmin = (box[sign[0]].x - xline->point.x) * invvec.x;
323 txmax = (box[1 - sign[0]].x - xline->point.x) * invvec.x;
324 tymin = (box[sign[1]].x - xline->point.y) * invvec.y;
325 tymax = (box[1 - sign[1]].x - xline->point.y) * invvec.y;
326 if ((txmin > tymax) || (tymin > txmax))
327 return;
328 if (tymin > txmin)
329 txmin = tymin;
330 if (tymax > txmax)
331 txmax = tymax;
332 tzmin = (box[sign[0]].z - xline->point.z) * invvec.z;
333 tzmax = (box[1 - sign[0]].z - xline->point.z) * invvec.z;
334 if ((txmin > tzmax) || (tzmin > txmax))
335 return;
336
337 printf ("\t<path id=\"dwg-object-%d\" d=\"M %f,%f L %f,%f\"\n\t", obj->index,
338 txmin, tymin, txmax, tymax);
339 common_entity (obj->tio.entity);
340 }
341
342 static void
343 output_RAY (Dwg_Object *obj)
344 {
345 Dwg_Entity_XLINE *xline = obj->tio.entity->tio.RAY;
346 BITCODE_3DPOINT point, invvec;
347 static BITCODE_3DPOINT box[2];
348 int sign[3];
349 double txmin, txmax, tymin, tymax, tzmin, tzmax;
350
351 if (isnan_3BD (xline->point) || isnan_3BD (xline->vector)
352 || entity_invisible (obj))
353 return;
354
355 invvec.x = 1.0 / xline->vector.x;
356 invvec.y = 1.0 / xline->vector.y;
357 invvec.z = 1.0 / xline->vector.z;
358 sign[0] = (invvec.x < 0.0);
359 sign[1] = (invvec.y < 0.0);
360 sign[2] = (invvec.z < 0.0);
361 box[0].x = model_xmin;
362 box[0].y = model_ymin;
363 box[1].x = model_xmax;
364 box[1].y = model_ymin;
365 printf ("\t<!-- ray-%d -->\n", obj->index);
366
367 // untested!
368 /* intersect ray from point with box (model_xmin, model_ymin, model_xmax,
369 * model_ymax) */
370 txmin = (box[sign[0]].x - xline->point.x) * invvec.x;
371 txmax = (box[1 - sign[0]].x - xline->point.x) * invvec.x;
372 tymin = (box[sign[1]].x - xline->point.y) * invvec.y;
373 tymax = (box[1 - sign[1]].x - xline->point.y) * invvec.y;
374 if ((txmin > tymax) || (tymin > txmax))
375 return;
376 if (tymin > txmin)
377 txmin = tymin;
378 if (tymax > txmax)
379 txmax = tymax;
380 point.x = (xline->point.x > txmax) ? txmax : xline->point.x;
381 if (point.x < txmin)
382 point.x = txmin;
383 point.y = (xline->point.y > tymax) ? tymax : xline->point.y;
384 if (point.y < tymin)
385 point.y = tymin;
386
387 tzmin = (box[sign[0]].z - xline->point.z) * invvec.z;
388 tzmax = (box[1 - sign[0]].z - xline->point.z) * invvec.z;
389 if ((txmin > tzmax) || (tzmin > txmax))
390 return;
391
392 printf ("\t<path id=\"dwg-object-%d\" d=\"M %f,%f L %f,%f\"\n\t", obj->index,
393 point.x, point.y, txmax, tymax);
394 common_entity (obj->tio.entity);
395 }
396
397 static void
398 output_CIRCLE (Dwg_Object *obj)
399 {
400 Dwg_Entity_CIRCLE *circle = obj->tio.entity->tio.CIRCLE;
401 BITCODE_3DPOINT center;
402
403 if (isnan_3BD (circle->center) || isnan_3BD (circle->extrusion)
404 || isnan (circle->radius) || entity_invisible (obj))
405 return;
406 transform_OCS (¢er, circle->center, circle->extrusion);
407 printf ("\t<!-- circle-%d -->\n", obj->index);
408 printf ("\t<circle id=\"dwg-object-%d\" cx=\"%f\" cy=\"%f\" r=\"%f\"\n\t",
409 obj->index, transform_X (center.x), transform_Y (center.y),
410 circle->radius);
411 common_entity (obj->tio.entity);
412 }
413
414 // CIRCLE with radius 0.1
415 static void
416 output_POINT (Dwg_Object *obj)
417 {
418 Dwg_Entity_POINT *point = obj->tio.entity->tio.POINT;
419 BITCODE_3DPOINT pt, pt1;
420
421 pt.x = point->x;
422 pt.y = point->y;
423 pt.z = point->z;
424 if (isnan_3BD (pt) || isnan_3BD (point->extrusion) || entity_invisible (obj))
425 return;
426 transform_OCS (&pt1, pt, point->extrusion);
427 printf ("\t<!-- point-%d -->\n", obj->index);
428 printf ("\t<circle id=\"dwg-object-%d\" cx=\"%f\" cy=\"%f\" r=\"0.1\"\n\t",
429 obj->index, transform_X (pt1.x), transform_Y (pt1.y));
430 common_entity (obj->tio.entity);
431 }
432
433 static void
434 output_ARC (Dwg_Object *obj)
435 {
436 Dwg_Entity_ARC *arc = obj->tio.entity->tio.ARC;
437 BITCODE_3DPOINT center;
438 double x_start, y_start, x_end, y_end;
439 int large_arc;
440
441 if (isnan_3BD (arc->center) || isnan_3BD (arc->extrusion)
442 || isnan (arc->radius) || isnan (arc->start_angle)
443 || isnan (arc->end_angle) || entity_invisible (obj))
444 return;
445 transform_OCS (¢er, arc->center, arc->extrusion);
446
447 x_start = center.x + arc->radius * cos (arc->start_angle);
448 y_start = center.y + arc->radius * sin (arc->start_angle);
449 x_end = center.x + arc->radius * cos (arc->end_angle);
450 y_end = center.y + arc->radius * sin (arc->end_angle);
451 // Assuming clockwise arcs.
452 large_arc = (arc->end_angle - arc->start_angle < M_PI) ? 0 : 1;
453
454 printf ("\t<!-- arc-%d -->\n", obj->index);
455 printf (
456 "\t<path id=\"dwg-object-%d\" d=\"M %f,%f A %f,%f 0 %d,0 %f,%f\"\n\t",
457 obj->index, transform_X (x_start), transform_Y (y_start), arc->radius,
458 arc->radius, large_arc, transform_X (x_end), transform_Y (y_end));
459 common_entity (obj->tio.entity);
460 }
461
462 // FIXME
463 static void
464 output_ELLIPSE (Dwg_Object *obj)
465 {
466 Dwg_Entity_ELLIPSE *ell = obj->tio.entity->tio.ELLIPSE;
467 BITCODE_2DPOINT radius;
468 // BITCODE_3DPOINT center, sm_axis;
469 // double x_start, y_start, x_end, y_end;
470
471 if (isnan_3BD (ell->center) || isnan_3BD (ell->extrusion)
472 || isnan_3BD (ell->sm_axis) || isnan (ell->axis_ratio)
473 || isnan (ell->start_angle) || isnan (ell->end_angle)
474 || entity_invisible (obj))
475 return;
476 /* The 2 points are already WCS */
477 // transform_OCS (¢er, ell->center, ell->extrusion);
478 // transform_OCS (&sm_axis, ell->sm_axis, ell->extrusion);
479 radius.x = fabs (ell->sm_axis.x);
480 radius.y = fabs (ell->sm_axis.y * ell->axis_ratio);
481
482 /*
483 x_start = ell->center.x + radius.x * cos (ell->start_angle);
484 y_start = ell->center.y + radius.y * sin (ell->start_angle);
485 x_end = ell->center.x + radius.x * cos (ell->end_angle);
486 y_end = ell->center.y + radius.y * sin (ell->end_angle);
487 */
488
489 // TODO: rotate. start,end_angle => pathLength
490 printf ("\t<!-- ellipse-%d -->\n", obj->index);
491 printf ("\t<!-- sm_axis=(%f,%f,%f) axis_ratio=%f start_angle=%f "
492 "end_angle=%f-->\n",
493 ell->sm_axis.x, ell->sm_axis.y, ell->sm_axis.z, ell->axis_ratio,
494 ell->start_angle, ell->end_angle);
495 printf ("\t<ellipse id=\"dwg-object-%d\" cx=\"%f\" cy=\"%f\" rx=\"%f\" "
496 "ry=\"%f\" transform=\"rotate=(%f %f %f)\"\n\t",
497 obj->index, ell->center.x, ell->center.y, radius.x, radius.y,
498 ell->sm_axis.x, ell->sm_axis.y, ell->sm_axis.z);
499 common_entity (obj->tio.entity);
500 }
501
502 // untested
503 static void
504 output_SOLID (Dwg_Object *obj)
505 {
506 Dwg_Entity_SOLID *sol = obj->tio.entity->tio.SOLID;
507 BITCODE_2DPOINT c1, c2, c3, c4;
508 BITCODE_2DPOINT s1, s2, s3, s4;
509
510 memcpy (&s1, &sol->corner1, sizeof s1);
511 memcpy (&s2, &sol->corner2, sizeof s1);
512 memcpy (&s3, &sol->corner3, sizeof s1);
513 memcpy (&s4, &sol->corner4, sizeof s1);
514 if (isnan_2BD (s1) || isnan_2BD (s2) || isnan_2BD (s3) || isnan_2BD (s4)
515 || entity_invisible (obj))
516 return;
517 transform_OCS_2d (&c1, s1, sol->extrusion);
518 transform_OCS_2d (&c2, s2, sol->extrusion);
519 transform_OCS_2d (&c3, s3, sol->extrusion);
520 transform_OCS_2d (&c4, s4, sol->extrusion);
521
522 printf ("\t<!-- solid-%d -->\n", obj->index);
523 printf ("\t<polygon id=\"dwg-object-%d\" "
524 "points=\"%f,%f %f,%f %f,%f %f,%f\"\n\t",
525 obj->index, transform_X (c1.x), transform_Y (c1.y),
526 transform_X (c2.x), transform_Y (c2.y), transform_X (c3.x),
527 transform_Y (c3.y), transform_X (c4.x), transform_Y (c4.y));
528 common_entity (obj->tio.entity);
529 }
530
531 // untested
532 static void
533 output_3DFACE (Dwg_Object *obj)
534 {
535 Dwg_Entity__3DFACE *ent = obj->tio.entity->tio._3DFACE;
536
537 if (isnan_3BD (ent->corner1) || isnan_3BD (ent->corner2)
538 || isnan_3BD (ent->corner3) || isnan_3BD (ent->corner4)
539 || entity_invisible (obj))
540 return;
541 printf ("\t<!-- 3dface-%d -->\n", obj->index);
542 if (ent->invis_flags)
543 {
544 // move to 1
545 printf ("\t<path id=\"dwg-object-%d\" d=\"M %f,%f", obj->index,
546 ent->corner1.x, ent->corner1.y);
547 printf (" %s %f,%f", ent->invis_flags & 1 ? "M" : "L", ent->corner2.x,
548 ent->corner2.y);
549 printf (" %s %f,%f", ent->invis_flags & 2 ? "M" : "L", ent->corner3.x,
550 ent->corner3.y);
551 printf (" %s %f,%f", ent->invis_flags & 4 ? "M" : "L", ent->corner4.x,
552 ent->corner4.y);
553 printf (" %s %f,%f\"\n\t", ent->invis_flags & 8 ? "M" : "L",
554 ent->corner1.x, ent->corner1.y);
555 }
556 else
557 printf ("\t<polygon id=\"dwg-object-%d\" "
558 "points=\"%f,%f %f,%f %f,%f %f,%f\"\n\t",
559 obj->index, ent->corner1.x, ent->corner1.y, ent->corner2.x,
560 ent->corner2.y, ent->corner3.x, ent->corner3.y, ent->corner4.x,
561 ent->corner4.y);
562 common_entity (obj->tio.entity);
563 }
564
565 static void
566 output_POLYLINE_2D (Dwg_Object *obj)
567 {
568 int error;
569 Dwg_Entity_POLYLINE_2D *pline = obj->tio.entity->tio.POLYLINE_2D;
570 BITCODE_RL numpts;
571
572 if (entity_invisible (obj))
573 return;
574 numpts = dwg_object_polyline_2d_get_numpoints (obj, &error);
575 if (numpts && !error)
576 {
577 BITCODE_2DPOINT pt, ptin;
578 dwg_point_2d *pts = dwg_object_polyline_2d_get_points (obj, &error);
579 BITCODE_RL j;
580
581 if (error || isnan_2pt (pts[0]) || isnan_3BD (pline->extrusion))
582 return;
583 ptin.x = pts[0].x;
584 ptin.y = pts[0].y;
585 transform_OCS_2d (&pt, ptin, pline->extrusion);
586 printf ("\t<!-- polyline_2d-%d -->\n", obj->index);
587 printf ("\t<path id=\"dwg-object-%d\" d=\"M %f,%f", obj->index,
588 transform_X (pt.x), transform_Y (pt.y));
589 // TODO curve_types, C for Bezier https://svgwg.org/specs/paths/#PathData
590 for (j = 1; j < numpts; j++)
591 {
592 ptin.x = pts[j].x;
593 ptin.y = pts[j].y;
594 if (isnan_2BD (ptin))
595 continue;
596 transform_OCS_2d (&pt, ptin, pline->extrusion);
597 // TODO bulge -> arc, widths
598 printf (" L %f,%f", transform_X (pt.x), transform_Y (pt.y));
599 }
600 if (pline->flag & 1) // closed
601 printf (" Z");
602 printf ("\"\n\t");
603 common_entity (obj->tio.entity);
604 free (pts);
605 }
606 }
607
608 static void
609 output_LWPOLYLINE (Dwg_Object *obj)
610 {
611 int error;
612 Dwg_Entity_LWPOLYLINE *pline = obj->tio.entity->tio.LWPOLYLINE;
613 BITCODE_RL numpts;
614
615 if (entity_invisible (obj))
616 return;
617 numpts = dwg_ent_lwpline_get_numpoints (pline, &error);
618 if (numpts && !error)
619 {
620 BITCODE_2DPOINT pt, ptin;
621 dwg_point_2d *pts = dwg_ent_lwpline_get_points (pline, &error);
622 BITCODE_RL j;
623
624 if (error || isnan_2pt (pts[0]) || isnan_3BD (pline->extrusion))
625 return;
626 ptin.x = pts[0].x;
627 ptin.y = pts[0].y;
628 transform_OCS_2d (&pt, ptin, pline->extrusion);
629 printf ("\t<!-- lwpolyline-%d -->\n", obj->index);
630 printf ("\t<path id=\"dwg-object-%d\" d=\"M %f,%f", obj->index,
631 transform_X (pt.x), transform_Y (pt.y));
632 // TODO curve_types, C for Bezier https://svgwg.org/specs/paths/#PathData
633 for (j = 1; j < numpts; j++)
634 {
635 ptin.x = pts[j].x;
636 ptin.y = pts[j].y;
637 if (isnan_2BD (ptin))
638 continue;
639 transform_OCS_2d (&pt, ptin, pline->extrusion);
640 // TODO bulge -> arc, widths
641 printf (" L %f,%f", transform_X (pt.x), transform_Y (pt.y));
642 }
643 if (pline->flag & 512) // closed
644 printf (" Z");
645 printf ("\"\n\t");
646 common_entity (obj->tio.entity);
647 free (pts);
648 }
649 }
650
651 // TODO: MINSERT
652 static void
653 output_INSERT (Dwg_Object *obj)
654 {
655 Dwg_Entity_INSERT *insert = obj->tio.entity->tio.INSERT;
656 if (entity_invisible (obj))
657 return;
658 if (insert->block_header && insert->block_header->handleref.value)
659 {
660 BITCODE_3DPOINT ins_pt;
661 if (isnan_3BD (insert->ins_pt) || isnan_3BD (insert->extrusion)
662 || isnan (insert->rotation) || isnan_3BD (insert->scale))
663 return;
664 transform_OCS (&ins_pt, insert->ins_pt, insert->extrusion);
665 printf ("\t<!-- insert-%d -->\n", obj->index);
666 printf ("\t<use id=\"dwg-object-%d\" transform=\"translate(%f %f) "
667 "rotate(%f) scale(%f %f)\" xlink:href=\"#symbol-" FORMAT_RLLx
668 "\" />"
669 "<!-- block_header->handleref: " FORMAT_H " -->\n",
670 obj->index, transform_X (ins_pt.x), transform_Y (ins_pt.y),
671 (180.0 / M_PI) * insert->rotation, insert->scale.x,
672 insert->scale.y, insert->block_header->absolute_ref,
673 ARGS_H (insert->block_header->handleref));
674 }
675 else
676 {
677 printf ("\n\n<!-- WRONG INSERT(" FORMAT_H ") -->\n",
678 ARGS_H (obj->handle));
679 }
680 }
681
682 static int
683 output_object (Dwg_Object *obj)
684 {
685 int num = 1;
686 if (!obj)
687 {
688 fprintf (stderr, "object is NULL\n");
689 return 0;
690 }
691
692 switch (obj->fixedtype)
693 {
694 case DWG_TYPE_INSERT:
695 output_INSERT (obj);
696 break;
697 case DWG_TYPE_LINE:
698 output_LINE (obj);
699 break;
700 case DWG_TYPE_CIRCLE:
701 output_CIRCLE (obj);
702 break;
703 case DWG_TYPE_TEXT:
704 output_TEXT (obj);
705 break;
706 case DWG_TYPE_ARC:
707 output_ARC (obj);
708 break;
709 case DWG_TYPE_POINT:
710 output_POINT (obj);
711 break;
712 case DWG_TYPE_ELLIPSE:
713 output_ELLIPSE (obj);
714 break;
715 case DWG_TYPE_SOLID:
716 output_SOLID (obj);
717 break;
718 case DWG_TYPE__3DFACE:
719 output_3DFACE (obj);
720 break;
721 case DWG_TYPE_POLYLINE_2D:
722 output_POLYLINE_2D (obj);
723 break;
724 case DWG_TYPE_LWPOLYLINE:
725 output_LWPOLYLINE (obj);
726 break;
727 case DWG_TYPE_RAY:
728 output_RAY (obj);
729 break;
730 case DWG_TYPE_XLINE:
731 output_XLINE (obj);
732 break;
733 case DWG_TYPE_SEQEND:
734 case DWG_TYPE_VIEWPORT:
735 break;
736 default:
737 num = 0;
738 if (obj->supertype == DWG_SUPERTYPE_ENTITY)
739 fprintf (stderr, "%s ignored\n", obj->name);
740 // all other non-graphical objects are silently ignored
741 break;
742 }
743 return num;
744 }
745
746 static int
747 output_BLOCK_HEADER (Dwg_Object_Ref *ref)
748 {
749 Dwg_Object *obj;
750 Dwg_Object_BLOCK_HEADER *hdr;
751 int is_g = 0;
752 int num = 0;
753
754 if (!ref) // silently ignore empty pspaces
755 return 0;
756 if (!ref->obj)
757 return 0;
758 obj = ref->obj;
759 if (obj->type != DWG_TYPE_BLOCK_HEADER)
760 {
761 fprintf (stderr, "Argument not a BLOCK_HEADER reference\n");
762 return 0;
763 }
764 if (!obj->tio.object)
765 { // TODO could be an assert also
766 fprintf (stderr, "Found null obj->tio.object\n");
767 return 0;
768 }
769 if (!obj->tio.object->tio.BLOCK_HEADER)
770 { // TODO could be an assert also
771 fprintf (stderr, "Found null obj->tio.object->tio.BLOCK_HEADER\n");
772 return 0;
773 }
774
775 hdr = obj->tio.object->tio.BLOCK_HEADER;
776 if (hdr->name)
777 {
778 char *escaped;
779 Dwg_Data *dwg = obj->parent;
780 if (dwg->header.version >= R_2007)
781 escaped = htmlwescape ((BITCODE_TU)hdr->name);
782 else
783 escaped = htmlescape (hdr->name, dwg->header.codepage);
784 // fatal: The string "--" is not permitted within comments.
785 if (escaped && strstr (escaped, "--"))
786 {
787 char *s;
788 while ((s = strstr (escaped, "--")))
789 {
790 *s = '_';
791 *(s + 1) = '_';
792 }
793 }
794 // don't group *Model_Space
795 if (!escaped || strcmp (escaped, "*Model_Space") != 0)
796 {
797 is_g = 1;
798 printf ("\t<g id=\"symbol-" FORMAT_RLLx "\" >\n\t\t<!-- %s -->\n",
799 ref->absolute_ref, escaped ? escaped : "");
800 }
801 else
802 printf ("\t<!-- %s -->\n", escaped);
803 if (escaped)
804 free (escaped);
805 }
806
807 obj = get_first_owned_entity (ref->obj);
808 while (obj)
809 {
810 num += output_object (obj);
811 obj = get_next_owned_entity (ref->obj, obj);
812 }
813
814 if (is_g)
815 printf ("\t</g>\n");
816 return num;
817 }
818
819 static void
820 output_SVG (Dwg_Data *dwg)
821 {
822 BITCODE_BS i;
823 int num = 0;
824 Dwg_Object *obj;
825 Dwg_Object_Ref *ref;
826 Dwg_Object_BLOCK_CONTROL *block_control;
827 double dx, dy;
828
829 model_xmin = dwg_model_x_min (dwg);
830 model_ymin = dwg_model_y_min (dwg);
831 model_xmax = dwg_model_x_max (dwg);
832 model_ymax = dwg_model_y_max (dwg);
833
834 dx = model_xmax - model_xmin;
835 dy = model_ymax - model_ymin;
836 // double scale_x = dx / (dwg_page_x_max(dwg) - dwg_page_x_min(dwg));
837 // double scale_y = dy / (dwg_page_y_max(dwg) - dwg_page_y_min(dwg));
838 scale = 25.4 / 72; // pt:mm
839 if (isnan (dx))
840 dx = 100.0;
841 if (isnan (dy))
842 dy = 100.0;
843 page_width = dx;
844 page_height = dy;
845 // scale *= (scale_x > scale_y ? scale_x : scale_y);
846
847 // optional, for xmllint
848 // <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
849 // "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
850 // But we use jing with relaxng, which is better. Just LaTeXML shipped a
851 // broken rng
852 printf ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
853 "<svg\n"
854 " xmlns:svg=\"http://www.w3.org/2000/svg\"\n"
855 " xmlns=\"http://www.w3.org/2000/svg\"\n"
856 " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
857 " version=\"1.1\" baseProfile=\"basic\"\n"
858 " width=\"100%%\" height=\"100%%\"\n"
859 " viewBox=\"%f %f %f %f\">\n",
860 model_xmin, model_ymin, page_width, page_height);
861
862 if (!mspace && (ref = dwg_paper_space_ref (dwg)))
863 num = output_BLOCK_HEADER (
864 ref); // how many paper-space entities we did print
865 if (!num && (ref = dwg_model_space_ref (dwg)))
866 output_BLOCK_HEADER (ref);
867 printf ("\t<defs>\n");
868 for (i = 0; i < dwg->block_control.num_entries; i++)
869 {
870 if (dwg->block_control.entries && (ref = dwg->block_control.entries[i]))
871 output_BLOCK_HEADER (ref);
872 }
873 printf ("\t</defs>\n");
874 printf ("</svg>\n");
875 fflush (stdout);
876 }
877
878 int
879 main (int argc, char *argv[])
880 {
881 int error;
882 int force_free = 0;
883 int i = 1;
884 int c;
885 #ifdef HAVE_GETOPT_LONG
886 int option_index = 0;
887 static struct option long_options[]
888 = { { "verbose", 1, &opts, 1 }, // optional
889 { "mspace", 0, 0, 0 }, { "force-free", 0, 0, 0 },
890 { "help", 0, 0, 0 }, { "version", 0, 0, 0 },
891 { NULL, 0, NULL, 0 } };
892 #endif
893
894 if (argc < 2)
895 return usage ();
896
897 while
898 #ifdef HAVE_GETOPT_LONG
899 ((c = getopt_long (argc, argv, ":v:m::h", long_options, &option_index))
900 != -1)
901 #else
902 ((c = getopt (argc, argv, ":v:m::hi")) != -1)
903 #endif
904 {
905 if (c == -1)
906 break;
907 switch (c)
908 {
909 case ':': // missing arg
910 if (optarg && !strcmp (optarg, "v"))
911 {
912 opts = 1;
913 break;
914 }
915 fprintf (stderr, "%s: option '-%c' requires an argument\n", argv[0],
916 optopt);
917 break;
918 #ifdef HAVE_GETOPT_LONG
919 case 0:
920 /* This option sets a flag */
921 if (!strcmp (long_options[option_index].name, "verbose"))
922 {
923 if (opts < 0 || opts > 9)
924 return usage ();
925 # if defined(USE_TRACING) && defined(HAVE_SETENV)
926 {
927 char v[2];
928 *v = opts + '0';
929 *(v + 1) = 0;
930 setenv ("LIBREDWG_TRACE", v, 1);
931 }
932 # endif
933 break;
934 }
935 if (!strcmp (long_options[option_index].name, "version"))
936 return opt_version ();
937 if (!strcmp (long_options[option_index].name, "help"))
938 return help ();
939 if (!strcmp (long_options[option_index].name, "force-free"))
940 force_free = 1;
941 if (!strcmp (long_options[option_index].name, "mspace"))
942 mspace = 1;
943 break;
944 #else
945 case 'i':
946 return opt_version ();
947 #endif
948 case 'v': // support -v3 and -v
949 i = (optind > 0 && optind < argc) ? optind - 1 : 1;
950 if (!memcmp (argv[i], "-v", 2))
951 {
952 opts = argv[i][2] ? argv[i][2] - '0' : 1;
953 }
954 if (opts < 0 || opts > 9)
955 return usage ();
956 #if defined(USE_TRACING) && defined(HAVE_SETENV)
957 {
958 char v[2];
959 *v = opts + '0';
960 *(v + 1) = 0;
961 setenv ("LIBREDWG_TRACE", v, 1);
962 }
963 #endif
964 break;
965 case 'h':
966 return help ();
967 case '?':
968 fprintf (stderr, "%s: invalid option '-%c' ignored\n", argv[0],
969 optopt);
970 break;
971 default:
972 return usage ();
973 }
974 }
975 i = optind;
976 if (i >= argc)
977 return usage ();
978
979 memset (&g_dwg, 0, sizeof (Dwg_Data));
980 g_dwg.opts = opts;
981 error = dwg_read_file (argv[i], &g_dwg);
982
983 if (opts)
984 fprintf (stderr, "\nSVG\n===\n");
985 if (error < DWG_ERR_CRITICAL)
986 output_SVG (&g_dwg);
987
988 #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
989 {
990 char *asanenv = getenv ("ASAN_OPTIONS");
991 if (!asanenv)
992 force_free = 1;
993 // detect_leaks is enabled by default. see if it's turned off
994 else if (strstr (asanenv, "detect_leaks=0") == NULL) /* not found */
995 force_free = 1;
996 }
997 #endif
998
999 // forget about leaks. really huge DWG's need endlessly here.
1000 if ((g_dwg.header.version && g_dwg.num_objects < 1000) || force_free
1001 #ifdef HAVE_VALGRIND_VALGRIND_H
1002 || (RUNNING_ON_VALGRIND)
1003 #endif
1004 )
1005 {
1006 dwg_free (&g_dwg);
1007 }
1008 return error >= DWG_ERR_CRITICAL ? 1 : 0;
1009 }