(root)/
libredwg-0.13/
programs/
dwg2SVG.c
       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 (&center, 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 (&center, 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 (&center, 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  }