(root)/
libredwg-0.13/
src/
acds.spec
/* -*- c -*- */
/*****************************************************************************/
/*  LibreDWG - free implementation of the DWG file format                    */
/*                                                                           */
/*  Copyright (C) 2020-2021 Free Software Foundation, Inc.                   */
/*                                                                           */
/*  This library is free software, licensed under the terms of the GNU       */
/*  General Public License as published by the Free Software Foundation,     */
/*  either version 3 of the License, or (at your option) any later version.  */
/*  You should have received a copy of the GNU General Public License        */
/*  along with this program.  If not, see <http://www.gnu.org/licenses/>.    */
/*****************************************************************************/

/*
 * acds.spec: AcDb:AcDsProtoype_1b datastorage section specification,
 *            containing new SAB ACIS content.
 * written by Reini Urban
 */

  #include "spec.h"

  static char _AcDs_Schema_Prop_types[] = {0, 0, 2, 1, 2, 4, 8, 1, 2, 4, 8, 4, 8, 0, 0, 0};

  DECODER {
    LOG_TRACE ("unknown_bits [%" PRIuSIZE " (%u,%d,%d) %" PRIuSIZE " TF]: ", dat->size * 8,
               0, 0, 0, dat->size);
    LOG_TRACE_TF (dat->chain, (int)dat->size);
    LOG_TRACE ("\n");
  }
  // header
  FIELD_RLx (file_signature, 0);
  FIELD_RL (file_header_size, 0);
  FIELD_RL (unknown_1, 0);  // always 2
  FIELD_RL (version, 0);    // always 2
  FIELD_RL (unknown_2, 0);  // always 0
  FIELD_RL (ds_version, 0); // datastorage revision
  FIELD_RL (segidx_offset, 0);
  FIELD_RL (segidx_unknown, 0);
  FIELD_RL (num_segidx, 0);
  FIELD_RL (schidx_segidx, 0);
  FIELD_RL (datidx_segidx, 0);
  FIELD_RL (search_segidx, 0);
  FIELD_RL (prvsav_segidx, 0);
  FIELD_RL (file_size, 0);
  dat->byte = _obj->segidx_offset;
#ifdef IS_DECODER
  if (dat->byte > dat->size)
    {
      LOG_ERROR ("Invalid segidx_offset");
      return DWG_ERR_VALUEOUTOFBOUNDS;
    }
  if ((size_t)(_obj->num_segidx * sizeof (Dwg_AcDs_Segment)) >
      (size_t)(dat->size - dat->byte))
    {
      LOG_ERROR ("Invalid num_segidx");
      return DWG_ERR_VALUEOUTOFBOUNDS;
    }
  _obj->segments = (Dwg_AcDs_Segment *)calloc (_obj->num_segidx,
                                               sizeof (Dwg_AcDs_Segment));
#endif
#ifndef IS_JSON
  if (FIELD_VALUE(num_segidx))
    {
      SUB_FIELD_RSx (segments[0],signature, 0); /* always 0xD5AC (ACD5 in the TF) */
      FIELD_TFF (segments[0].name, 6, 0);       /* always segidx */
      DECODER { _obj->segments[0].type = 0; }
      JSON { SUB_FIELD (segments[0],type, RC, 0); }
      SUB_FIELD (segments[0],segment_idx, RL, 0);
      SUB_FIELD (segments[0],is_blob01, RL, 0);
      SUB_FIELD (segments[0],segsize, RL, 0);
      SUB_FIELD (segments[0],unknown_2, RL, 0);
      SUB_FIELD (segments[0],ds_version, RL, 0); // datastorage revision
      SUB_FIELD (segments[0],unknown_3, RL, 0);
      SUB_FIELD (segments[0],data_algn_offset, RL, 0);
      SUB_FIELD (segments[0],objdata_algn_offset, RL, 0);
      FIELD_TFF (segments[0].padding, 8, 0); // always 8x 0x55
    }
#endif

  REPEAT (num_segidx, segidx, Dwg_AcDs_SegmentIndex)
  REPEAT_BLOCK
#ifdef IS_JSON
      KEY (index); VALUE_RL (rcount1, 0);
#endif
      SUB_FIELD_RLL (segidx[rcount1],offset, 0);
      SUB_FIELD_RL (segidx[rcount1],size, 0);
      DECODER { if (_obj->segidx[rcount1].offset) _obj->total_segments++; }
  END_REPEAT_BLOCK
#ifdef IS_JSON
  END_REPEAT (segidx) // still needed, free later
#endif

  _REPEAT_NF (_obj->num_segidx, segments, Dwg_AcDs_Segment, 1)
  REPEAT_BLOCK
      if (_obj->segidx[rcount1].offset)
        {
#ifdef IS_JSON
          KEY (index); VALUE_RL (rcount1, 0);
#endif
#ifdef IS_DECODER
          if (_obj->segidx[rcount1].offset >= dat->size)
            {
              LOG_ERROR ("Invalid AcDs.segments[%d] offset: " FORMAT_RLL "\n", rcount1,
                         _obj->segidx[rcount1].offset);
              _obj->segidx[rcount1].offset = 0;
              continue;
            }
          else
            {
              LOG_TRACE ("\nsegments[%d] offset: " FORMAT_RLL "\n", rcount1,
                         _obj->segidx[rcount1].offset);
              dat->byte = _obj->segidx[rcount1].offset;
            }
#endif
        }
      else
        {
#ifdef IS_JSON
          ENDHASH;
#endif
          continue;
        }

      SUB_FIELD_RSx (segments[rcount1],signature, 0); /* always 0xD5AC (ACD5 in the TF) */
      DECODER {
        if (_obj->segments[rcount1].signature != 0xD5AC)
          {
            LOG_ERROR ("Invalid AcDs.segments[%d].signature %x != 0xd5ac", rcount1,
                       _obj->segments[rcount1].signature);
            error |= DWG_ERR_SECTIONNOTFOUND;
            //continue;
          }
      }
      /* segidx, datidx, _data_, schidx, schdat, search, blob01, prvsav, freesp */
      FIELD_TFF (segments[rcount1].name, 6, 0);
      DECODER {
        _obj->segments[rcount1].name[6] = '\0';
        _obj->segments[rcount1].type = -1;
        if (strEQc ((char*)_obj->segments[rcount1].name, "segidx"))
          _obj->segments[rcount1].type = 0;
        else if (strEQc ((char*)_obj->segments[rcount1].name, "datidx"))
          _obj->segments[rcount1].type = 1;
        else if (strEQc ((char*)_obj->segments[rcount1].name, "_data_"))
          _obj->segments[rcount1].type = 2;
        else if (strEQc ((char*)_obj->segments[rcount1].name, "schidx"))
          _obj->segments[rcount1].type = 3;
        else if (strEQc ((char*)_obj->segments[rcount1].name, "schdat"))
          _obj->segments[rcount1].type = 4;
        else if (strEQc ((char*)_obj->segments[rcount1].name, "search"))
          _obj->segments[rcount1].type = 5;
        else if (strEQc ((char*)_obj->segments[rcount1].name, "blob01"))
          _obj->segments[rcount1].type = 6;
        else if (strEQc ((char*)_obj->segments[rcount1].name, "prvsav")) // ODA undocumented
          _obj->segments[rcount1].type = 7;
        else if (strEQc ((char*)_obj->segments[rcount1].name, "freesp")) // ODA undocumented
          _obj->segments[rcount1].type = 8;
        else
          {
            LOG_ERROR ("Invalid AcDs.segments[%d].name %s", rcount1,
                       (char *)_obj->segments[rcount1].name);
            error |= DWG_ERR_SECTIONNOTFOUND;
            continue;
          }
      }
      JSON {
        SUB_FIELD (segments[rcount1],type, RC, 0);
      }
      SUB_FIELD (segments[rcount1],segment_idx, RL, 0);
      SUB_FIELD (segments[rcount1],is_blob01, RL, 0);
      SUB_FIELD (segments[rcount1],segsize, RL, 0);
      SUB_FIELD (segments[rcount1],unknown_2, RL, 0);
      SUB_FIELD (segments[rcount1],ds_version, RL, 0); // datastorage revision
      SUB_FIELD (segments[rcount1],unknown_3, RL, 0);
      SUB_FIELD (segments[rcount1],data_algn_offset, RL, 0);
      SUB_FIELD (segments[rcount1],objdata_algn_offset, RL, 0);
      FIELD_TFF (segments[rcount1].padding, 8, 0); // always 8x 0x55

      switch (_obj->segments[rcount1].type) {
      case 0: // hmm, more than 1 segidx?
#if 0
        REPEAT2 (num_segidx, segidx, Dwg_AcDs_SegmentIndex)
        REPEAT_BLOCK
# ifdef IS_JSON
            KEY (index); VALUE_RL (rcount2, 0);
# endif
            SUB_FIELD_RLL (segidx[rcount2],offset, 0);
            SUB_FIELD_RL (segidx[rcount2],size, 0);
        END_REPEAT_BLOCK
        END_REPEAT (segidx)
#endif
        break;
      case 1:
        if (_obj->datidx_segidx != rcount1)
          LOG_WARN ("Possibly wrong datidx_segidx %d for %d", _obj->datidx_segidx, rcount1);
        SUB_FIELD_RL (datidx,num_entries, 0);
        SUB_FIELD_RL (datidx,di_unknown, 0);
        REPEAT2 (datidx.num_entries, datidx.entries, Dwg_AcDs_DataIndex_Entry)
        REPEAT_BLOCK
#ifdef IS_JSON
            KEY (index); VALUE_RL (rcount2, 0);
#endif
            SUB_FIELD_RL (datidx.entries[rcount2],segidx, 0);
            SUB_FIELD_RL (datidx.entries[rcount2],offset, 0);
            SUB_FIELD_RL (datidx.entries[rcount2],schidx, 0);
        END_REPEAT_BLOCK
        END_REPEAT (datidx.entries)
        break;
      case 3:
        if (_obj->schidx_segidx != rcount1)
          LOG_WARN ("Possibly wrong schidx_segidx %d for %d", _obj->schidx_segidx, rcount1);
        SUB_FIELD_RL (schidx,num_props, 0); // or RLL
        SUB_FIELD_RL (schidx,si_unknown_1, 0);
        REPEAT2 (schidx.num_props, schidx.props, Dwg_AcDs_SchemaIndex_Prop)
        REPEAT_BLOCK
            SUB_FIELD_RL (schidx.props[rcount2],index, 0);
            SUB_FIELD_RL (schidx.props[rcount2],segidx, 0);
            SUB_FIELD_RL (schidx.props[rcount2],offset, 0);
        END_REPEAT_BLOCK
        END_REPEAT (schidx.props)
        SUB_FIELD_RLL (schidx,si_tag, 0); /* 0x0af10c */
        SUB_FIELD_RL (schidx,num_prop_entries, 0); // or RLL
        SUB_FIELD_RL (schidx,si_unknown_2, 0);
        REPEAT2 (schidx.num_prop_entries, schidx.prop_entries, Dwg_AcDs_SchemaIndex_Prop)
        REPEAT_BLOCK
            SUB_FIELD_RL (schidx.prop_entries[rcount2],index, 0);
            SUB_FIELD_RL (schidx.prop_entries[rcount2],segidx, 0);
            SUB_FIELD_RL (schidx.prop_entries[rcount2],offset, 0);
        END_REPEAT_BLOCK
        END_REPEAT (schidx.prop_entries)
        break;
      case 4:
        DEBUG_HERE
        DECODER { _obj->schdat.num_uprops = 1; } // FIXME
        REPEAT2 (schdat.num_uprops, schdat.uprops, Dwg_AcDs_SchemaData_UProp)
        REPEAT_BLOCK
          SUB_FIELD_RL (schdat.uprops[rcount2], size, 0);
          SUB_FIELD_RL (schdat.uprops[rcount2], flags, 0);
        END_REPEAT_BLOCK
        END_REPEAT (schdat.uprops)
        DEBUG_HERE
        REPEAT2 (schdat.num_schemas, schdat.schemas, Dwg_AcDs_Schema)
        REPEAT_BLOCK
            SUB_FIELD_RS (schdat.schemas[rcount2],num_index, 0);
            SUB_FIELD_VECTOR (schdat.schemas[rcount2],index, RLL, num_index, 0);
            SUB_FIELD_RS (schdat.schemas[rcount2],num_props, 0);
            REPEAT2 (schdat.schemas[rcount2].num_props, schdat.schemas[rcount2].props, Dwg_AcDs_Schema_Prop)
#define prop schdat.schemas[rcount2].props[rcount3] 
            REPEAT_BLOCK
                SUB_FIELD_RL (prop,flags, 91); // 1, 2, 8
                SUB_FIELD_RL (prop,namidx, 2);
                if (!(_obj->prop.flags & 2))
                {
                  SUB_FIELD_RL (prop,type, 280);
                  if (_obj->prop.type  == 0xe)
                    {
                      SUB_FIELD_RL (prop,type_size, 0)
                    }
                  else
                    _obj->prop.type_size = _AcDs_Schema_Prop_types[_obj->prop.type];
                }
                if (_obj->prop.flags == 1)
                  {
                    SUB_FIELD_RL (prop,unknown_1, 0)
                  }
                else if (_obj->prop.flags == 8)
                  {
                    SUB_FIELD_RL (prop,unknown_2, 0)
                  }
                SUB_FIELD_RS (prop,num_values, 0);
                if (_obj->prop.type_size)
                  {
                    SUB_FIELD_VECTOR_TYPESIZE (prop, values, num_values,
                                               _obj->prop.type_size, 0);
                  }
            END_REPEAT_BLOCK
#undef prop
            END_REPEAT (schdat.schemas[rcount2].props)
        END_REPEAT_BLOCK
        END_REPEAT (schdat.schemas)
        LOG_WARN ("AcDs %s segment yet unhandled", _obj->segments[rcount1].name)
        break;
      case 5: // search
        if (_obj->search_segidx != rcount1)
          LOG_WARN ("Possibly wrong search_segidx %d for %d", _obj->search_segidx, rcount1);
        SUB_FIELD_RL (search,num_search, 0);
        REPEAT2 (search.num_search, search.search, Dwg_AcDs_Search_Data)
        REPEAT_BLOCK
          SUB_FIELD_RL (search.search[rcount2],schema_namidx, 0);
          SUB_FIELD_RL (search.search[rcount2],num_sortedidx, 0);
          SUB_FIELD_VECTOR (search.search[rcount2],sortedidx, RLLd, num_sortedidx, 0);
          SUB_FIELD_RL (search.search[rcount2],num_ididxs, 0);
          SUB_FIELD_RL (search.search[rcount2],unknown, 0);
          REPEAT3 (search.search[rcount2].num_ididxs, search.search[rcount2].ididxs, Dwg_AcDs_Search_IdIdxs)
          REPEAT_BLOCK
            SUB_FIELD_RL (search.search[rcount2].ididxs[rcount3],num_ididx, 0);
            REPEAT4 (search.search[rcount2].ididxs[rcount3].num_ididx, search.search[rcount2].ididxs[rcount3].ididx, Dwg_AcDs_Search_IdIdx)
            REPEAT_BLOCK
              SUB_FIELD_RLL (search.search[rcount2].ididxs[rcount3].ididx[rcount4],handle, 0);
              SUB_FIELD_RL (search.search[rcount2].ididxs[rcount3].ididx[rcount4],num_ididx, 0);
              SUB_FIELD_VECTOR (search.search[rcount2].ididxs[rcount3].ididx[rcount4],ididx, RLL, num_ididx, 0);
            END_REPEAT_BLOCK
            END_REPEAT (search.search[rcount2].ididxs[rcount3].ididx)
          END_REPEAT_BLOCK
          END_REPEAT (search.search[rcount2].ididxs)
        END_REPEAT_BLOCK
        END_REPEAT (search.search)
        break;
      case 7: // prvsav
        if (_obj->prvsav_segidx != rcount1)
          LOG_WARN ("Possibly wrong prvsav_segidx %d for %d", _obj->prvsav_segidx, rcount1);
        // fallthru
        //break;
      case 8: // freesp
        //break;
      case 2: // _data_
        //break;
      case 6: // blob01
        //break;
      default:
#ifndef IS_FREE
        LOG_WARN ("AcDs %s segment yet unhandled", _obj->segments[rcount1].name)
#endif
        break;
      }
  END_REPEAT_BLOCK
  END_REPEAT (segments)
#ifdef IS_FREE
  END_REPEAT (segidx)
#endif

#ifdef IS_DECODER
  DECODER {
    char *s, *e;
    unsigned int i = 0;
    char *acis_sab_data;
    const BITCODE_B acis_empty = 0;
    const BITCODE_BS version = 2;
    BITCODE_BL num_acis_sab_data;
    const unsigned int wanted = dwg->num_acis_sab_hdl;
    // 414349532042696E61727946696C65 @10504/2 = 5252
    const char start[] = "ACIS BinaryFile";
    // 0E03456E640E026F660E0341534D0D0464617461 @13822/2 = 6911
    const char end[] = "\016\003End\016\002of\016\003ASM\r\004data";
    LOG_TRACE ("\nSearch for ACIS BinaryFile data:\n");
    num_acis_sab_data = 0;
    while ((s = (char *)memmem (&dat->chain[i], dat->size - i, start,
                                strlen (start))))
      {
        size_t j = s - (char*)&dat->chain[0]; // absolute_offset of found range
        if ((e = (char *)memmem (s, dat->size - j, end, strlen (end))))
          {
            BITCODE_H hdl;
            Dwg_Object *o;
            Dwg_Entity_3DSOLID *sol;
            size_t size = e - s;
            size += strlen (end);
            LOG_TRACE ("acis_sab_data[%d]: found %s at %" PRIuSIZE ", size %" PRIuSIZE "\n",
                       num_acis_sab_data, start, j, size);
            if (!dwg->num_acis_sab_hdl)
              {
                LOG_ERROR ("Not enough %u 3DSOLIDs for the %u-th AcDs SAB data",
                           wanted, num_acis_sab_data)
                return DWG_ERR_INVALIDHANDLE;
              }
            hdl = SHIFT_HV (dwg, num_acis_sab_hdl, acis_sab_hdl);
            o = dwg_resolve_handle (dwg, hdl->handleref.value);
            LOG_TRACE ("%s.acis_sab_hdl[%u] = " FORMAT_REF "\n", o->name,
                       dwg->num_acis_sab_hdl + 1, ARGS_REF (hdl))
            if (!o || !dwg_obj_is_3dsolid (o))
              {
                LOG_ERROR ("Matching object %s " FORMAT_REF " not a 3DSOLID",
                           o ? o->name : "", ARGS_REF (hdl))
                free (hdl);
                error |= DWG_ERR_INVALIDHANDLE;
                continue;
              }
            sol = o->tio.entity->tio._3DSOLID;
            // not NULL terminated
            acis_sab_data = (char*)malloc (size);
            memcpy (acis_sab_data, s, size);
            num_acis_sab_data++;
            dwg_dynapi_entity_set_value (sol, o->name, "acis_data", &acis_sab_data, 0);
            dwg_dynapi_entity_set_value (sol, o->name, "sab_size", &size, 0);
            dwg_dynapi_entity_set_value (sol, o->name, "version", &version, 0);
            // FIXME only until we can write acds:
            dwg_dynapi_entity_set_value (sol, o->name, "acis_empty", &acis_empty, 0);
            // o->tio.entity->has_ds_data = 0; // maybe there is more, like the
            // wires and silhuettes
            LOG_TRACE ("%s.acis_data = %" PRIuSIZE " " FORMAT_REF "\n", o->name, size,
                       ARGS_REF (hdl))
            free (hdl); // it is a non-global, free'able handleref. Created in
                        // common_entity_data.spec
            i = (j + size) & UINT_MAX; // next offset to try
          }
        else
          {
            LOG_WARN ("No End-of-ASM-data found from %" PRIuSIZE " - %" PRIuSIZE
                      " for %d-th SAB data",
                       j, dat->size, num_acis_sab_data);
            i = (j + 20) & UINT_MAX;
          }
      }
    if (wanted == num_acis_sab_data)
      {
        LOG_TRACE ("Matching number of %u 3DSOLID entities and AcDs SAB data\n",
                   wanted)
      }
    else
      {
        LOG_WARN ("Not matching number of %u 3DSOLID entities and %u AcDs SAB "
                  "data\n",
                  wanted, num_acis_sab_data);
        while (dwg->num_acis_sab_hdl > 0)
          free (SHIFT_HV (dwg, num_acis_sab_hdl, acis_sab_hdl));
      }
  }
#endif