(root)/
libredwg-0.13/
src/
gen-dynapi.pl
#!/usr/bin/perl -w
# Copyright (C) 2019-2024 Free Software Foundation, Inc., GPL3
#
# Generate src/dynapi.c and test/unit-testing/dynapi_test.c
# C structs/arrays for all dwg objects and its fields for a dynamic API.
#   -> name, type, size, offset, dxfgroup, memory-type
# Within each object linear search is good enough.
# This is needed for in_dxf, dwgfilter,
# a maintainable and shorter dwg_api and shorter language bindings.
# Written by: Reini Urban

#dwg.h:
# typedef struct _dwg_header_variables
# typedef struct _dwg_entity_(.*)
# typedef struct _dwg_object_(.*)
#subclasses, typically like
# typedef struct _dwg_TYPE_subclass
# typedef union _dwg_MLEADER_Content

use strict;
use warnings;
use vars qw(@entity_names @object_names @subclasses
            $max_entity_names $max_object_names $max_subclasses);
use Convert::Binary::C;
#use Data::Dumper; # if DEBUG
#BEGIN { chdir 'src' if $0 =~ /src/; }

# add gcc/clang -print-search-dirs paths
my (@ccincdir, $srcdir, $topdir);
if ($0 =~ m{(^\.\./.*src)/gen}) {
  $srcdir = $1;
  $topdir = "$srcdir/..";
  $topdir =~ s{^\.\./src/}{};
} elsif ($0 =~ m{^(\./)?src/gen}) {
  $srcdir = "src";
  $topdir = ".";
} else {
  $srcdir = ".";
  $topdir = "..";
}
my $CC = `grep '^CC =' Makefile`;
if ($CC) {
  $CC =~ s/^CC =//;
  chomp $CC;
  $CC =~ s/^\s+//;
  if ($CC =~ /^afl-(gcc|clang)/) {
    $ENV{AFL_QUIET} = 1;
  }
  my $out = `$CC -print-search-dirs`;
  if ($out) {
    if ($out =~ /^install: (.+?)$/m) {
      my $d = $1;
      $d =~ s/\/$//;
      @ccincdir = ("$d/include") if -d "$d/include";
    } elsif ($out =~ /^libraries: =(.+?)$/m) {
      @ccincdir = grep { -d "$_/include" ? "$_/include" : undef }
                       split ':', $1;
    }
  }
}
#__WORDSIZE=64
# glibc quirks to include stdint.h and stddef.h directly with this old pre-C99
# Convert::Binary::C preprocessor
my @defines = ('__GNUC__=4', '__x86_64__', '__inline=inline',
               '__THROW=', '__attribute__(x)=');
if ($^O =~ /darwin|bsd/) {
  push @defines, ('__signed=signed', '__builtin_va_list=void*', '__extension__=');
}
if (@ccincdir and join(" ",@ccincdir) =~ /clang/) {
  push @defines, ('__has_feature(x)=0', '__has_include_next(x)=0',
                  '__INTPTR_TYPE__=long int', '__UINTPTR_TYPE__=unsigned long int',
                  '__INTMAX_TYPE__=long int', '__UINTMAX_TYPE__=unsigned long int',
                  '__gwchar_t=int', '____gwchar_t_defined',
    );
}

warn "using $CC with @ccincdir\n" if @ccincdir;
my @sys_includes = ('/usr/include');
my $dumpmachine = `$CC -dumpmachine`;
if ($dumpmachine) {
	chomp $dumpmachine;
	push @sys_includes, '/usr/include/'.$dumpmachine;
}
my $c = Convert::Binary::C->new
  ->Include('.', @ccincdir, @sys_includes)
  ->Define(@defines);
my $hdr = "$topdir/include/dwg.h";
$c->parse_file($hdr);

#print Data::Dumper->Dump([$c->struct('_dwg_MLINESTYLE_line')], ['_dwg_MLINESTYLE_line']);
#print Data::Dumper->Dump([$c->struct('_dwg_entity_TEXT')], ['_dwg_entity_TEXT']);
#print Data::Dumper->Dump([$c->struct('struct _dwg_header_variables')], ['Dwg_Header_Variables']);

my (%h, $n, %structs, %unions, %ENT, %DXF, %SIZE, %SUBCLASS, %SUBCLASSES, %DWG_TYPE,
    @unhandled_names);
local (@entity_names, @object_names, @subclasses, $max_entity_names, $max_object_names,
       $max_subclasses);
# todo: harmonize more subclasses, detect abstract classes with concrete typedefs
for (sort $c->struct_names) {
  if (/^_dwg_entity_([A-Z0-9_]+)$/) {
    my $n = $1;
    $structs{$n}++;
    push @entity_names, $n;
  } elsif (/^_dwg_object_([A-Z0-9_]+)$/) {
    my $n = $1;
    $structs{$n}++;
    push @object_names, $n;
  } elsif (/^_dwg_header_variables/) {
    ;
  } elsif (/^Dwg_([A-Z0-9]+)/) { # AuxHeader, Header, R2004_Header, SummaryInfo
    my $n = $1;
    next if length $n <= 1;
    $structs{$n}++;
  } elsif (/_dwg_([A-Z0-9_]+)/ && !/_dwg_ODA/) {
    $structs{$_}++;
    push @subclasses, $_;
  } else {
    warn "skip struct $_\n";
  }
}
for (sort $c->union_names) {
  if (/_dwg_([A-Z0-9_]+)/) {
    $structs{$_}++;
    $unions{$_}++;
    push @subclasses, $_;
  } else {
    warn "skip union $_\n";
  }
}
# typedefs as aliases:
push @entity_names, qw(XLINE);                    # RAY
push @entity_names, qw(VERTEX_MESH VERTEX_PFACE); # VERTEX_3D
push @entity_names, qw(REGION BODY);              # 3DSOLID
$structs{abstractobject_UNDERLAYDEFINITION}++;
$structs{abstractentity_UNDERLAY}++;
push @entity_names, qw(PDFUNDERLAY DGNUNDERLAY DWFUNDERLAY);
push @object_names, qw(PDFDEFINITION DGNDEFINITION DWFDEFINITION);
push @object_names, qw(ASSOCARRAYMODIFYPARAMETERS
                       ASSOCARRAYPATHPARAMETERS
                       ASSOCARRAYPOLARPARAMETERS
                       ASSOCARRAYRECTANGULARPARAMETERS);
$structs{abstractobject_ASSOCARRAYPARAMETERS}++;

@entity_names = sort @entity_names;
# get BITCODE_ macro types for each struct field
open my $in, "<", $hdr or die "hdr: $!";
my $f;
while (<$in>) {
  if (!$n) {
    if (/^typedef struct (_dwg_.+) \{/) {
      $n = $1;
    } elsif (/^typedef struct (_dwg_\S+)$/) {
      $n = $1;
    } elsif (/^typedef union (_dwg_\S+)$/) {
      $n = $1;
    } elsif (/^#define (COMMON_\w+)\((\w+)\)/) { # COMMON_TABLE_CONTROL_FIELDS
      # BUG: Convert::Binary::C cannot separate H* from H, both are just Dwg_Object_Ref
      # So we need to parse the defines
      $n = $1;
      $f = $2;
      $h{$n}{reactors} = 'H*'; # has no ;
    } elsif (/^#define (COMMON_\w+)/) { # COMMON_ENTITY_POLYLINE
      $n = $1;
      $h{$n}{seqend}   = 'H' if $n eq 'COMMON_ENTITY_POLYLINE'; # has no ;
    }
  } elsif (/^\}/) { # close the struct
    $n = '';
  } elsif ($n and $_ =~ /^ +BITCODE_([\w\*]+)\s+(\w.*);/) {
    my $type = $1;
    my $v = $2;
    $v =~ s/^_3/3/;
    $type =~ s/\s+$//;
    if ($n eq 'COMMON_TABLE_CONTROL_FIELDS' && $v eq 'entries') {
      $h{$n}{$_} = 'H*'
        for qw(block_headers layers styles linetypes views ucs vports apps
               dimstyles vport_entity_headers);
    }
    $h{$n}{$v} = $type;
  }
}
#$h{Dwg_Bitcode_3BD} = '3BD';
#$h{Dwg_Bitcode_2BD} = '2BD';
#$h{Dwg_Bitcode_3RD} = '3RD';
#$h{Dwg_Bitcode_2RD} = '2RD';
$ENT{LAYER}->{flag} = 'BS';
$ENT{LAYER}->{name} = 'T';
$ENT{DIMSTYLE}->{name} = 'T';
$SUBCLASSES{DIMENSION_common} = [ qw(AcDbDimension) ];
$SUBCLASSES{ACTION_3DSOLID} = [ qw(AcDbModelerGeometry AcDb3dSolid) ];
$SUBCLASSES{TABLECONTENTs} = [ qw( AcDbLinkedTableData AcDbFormattedTableData AcDbTableContent) ];
$SUBCLASSES{PROXY_OBJECT} = [ qw( AcDbZombieObject) ];
#$ENT{LTYPE}->{strings_area} = 'TF';
close $in;
my @old;

sub expand_define {
  my ($def, $n, $defined, $param) = @_;
  if (exists $defined->{$def}) {
    warn "expand defined $def fields";
    if (!$param) { # replace the macro arg with $param? no. for now ignore
      for (keys %{$DXF{$def}}) {
        $DXF{$n}->{$_} = $DXF{$def}->{$_};
      }
      for (keys %{$ENT{$def}}) {
        $ENT{$n}->{$_} = $ENT{$def}->{$_};
      }
    }
    my $aref = $SUBCLASSES{$n};
    for my $k (@{$SUBCLASSES{$def}}) {
      # if defined subclass is not in the main subclass yet
      if (!grep $_ eq $k, @$aref) {
        if (@$aref) {
          push @$aref, $k;
        } else {
          $aref = [ $k ];
        }
        $SUBCLASSES{$n} = $aref;
      }
    }
  } else {
    unless (/(DXF|DECODE|ENCODE|JSON|FREE)_3DSOLID/) {
      warn "TODO $def fields not defined";
    } else {
      $n = 'ACTION_3DSOLID';
      $defined->{$n}++;
      @old = (); # TODO pop 3DSOLID_material
    }
  }
}

sub embedded_struct {
  my ($pre, $subclass) = @_;
  if ($f =~ /^$pre\.(\w+)$/) {
    $f = $1;
    if ($n ne $subclass) {
      push @old, $n;
      warn "$n pushed";
      $n = $subclass;
    }
  } elsif ($n eq $subclass && $f !~ /\./) {
    $n = pop @old;
    warn "$n popped";
  }
}

# parse a spec for its objects, subclasses and dxf values
sub dxf_in {
  $in = shift;
  my $v = qr /[\w\.\[\]]+/;
  my $vx = qr /[\w\.\[\]>-]+/;
  my $outdef;
  my %defined; #define subclass_fields
  while (<$in>) {
    $f = '';
    s/DXF \{ //;
    if (!$n) {
      if (/^DWG_(ENTITY|OBJECT|TABLE)\s?\((\w+)\)/) {
        $n = $2;
        $n =~ s/^_3/3/;
        warn $n;
      } elsif (/^\s*SECTION\s?\(HEADER\)/) {
        $n = 'header_variables';
        warn $n;
      } elsif (/^int DWG_FUNC_N\s?\(ACTION,_HATCH(\w+)\)/) {
        $n = 'HATCH';
        warn $n;
      } elsif (/^\#define (COMMON_ENTITY_POLYLINE)/) {
        $n = $1;
        warn "define $n";
      } elsif (/^\#define (COMMON_3DSOLID|ACTION_3DSOLID|COMMON_ENTITY_DIMENSION)/) {
        $n = $1;
        $defined{$n}++;
        warn "define $n";
      } elsif (/^\#define (\w+)_fields/) {
        $n = $1;
        $defined{$n}++;
        warn "define $n fields";
      }
    } elsif (/^\#define (\w+)_fields/) {
      $n = $1;
      $defined{$n}++;
      warn "define $n fields";
    # i.e. after #define
    } elsif (/^DWG_(ENTITY|OBJECT|TABLE)\s?\((\w+)\)/) {
      $n = $2;
      $n =~ s/^_3/3/;
      warn $n;
    } elsif (/^DWG_(ENTITY|OBJECT)_END/) { # close
      $n = '';
      @old = ();
      $outdef = 0;
    } elsif (!$n) {
      ;
    #} elsif ($outdef) {
    #  ;
    #} elsif (/^\s*\#ifdef IS_JSON/) {
    #  $outdef++;
    #} elsif ($outdef && /^\s*\#endif/) {
    #  $outdef = 0;
    # single-line REPEAT
    } elsif (/^\s+REPEAT.*\($v,\s*$v,\s*(\w+)\)/) {
      my $tmp = $1; # subclass?
      if ($tmp =~ /^Dwg_(.*)/) {
        push @old, $n;
        $n = $1; # not _dwg_$1
        warn "$n pushed";
      } else {
        push @old, '';
      }
    } elsif (/^\s+_REPEAT_C?N\s*\($vx,\s*$v,\s*(\w+),/) {
      my $tmp = $1; # subclass?
      if ($tmp =~ /^Dwg_(.*)/) {
        push @old, $n;
        $n = $1;
        warn "$n pushed";
      } else {
        push @old, '';
      }
    # multiline REPEAT
    } elsif (/^\s+REPEAT.*\($v,\s*$v,$/) {
      $_ = <$in>;
      if (/^\s+(\w+)\)/) {
        my $tmp = $1; # subclass?
        if ($tmp =~ /^Dwg_(.*)/) {
          push @old, $n;
          $n = $1;
          warn "$n pushed";
        } else {
          push @old, '';
        }
      }
    # skip END_REPEAT(paths); return DWG_ERR_VALUEOUTOFBOUNDS;
    } elsif (/^\s+END_REPEAT\s*\(($vx)\);?$/) { # close
      my $tmp = pop @old;
      if ($tmp) {
        $n = $tmp;
        warn "$n popped";
      } elsif (!defined $tmp) {
        # mostly due to type being a primitive, not a subclass (like T)
        warn "missing REPEAT block in $n: $1";
      }
    } elsif (/^\s+FIELD_HANDLE\s*\((\w+),\s*\d+,\s*(\d+)\)/) {
      $f = $1;
      if ($n eq 'MLEADER_AnnotContext' && $f eq 'mleaderstyle') {
        $n = 'MULTILEADER'; # pop back
      }
      $DXF{$n}->{$f} = $2 if $2;
    } elsif (/^\s+VALUE_HANDLE\s*\(.+,\s*(\w+),\s*\d,\s*(\d+)\)/) {
      $f = $1;
      $DXF{$n}->{$f} = $2 if $2;
    } elsif (/^\s+FIELD_TF\s*\((\w+),\s*(\d*),\s*(\d+)\)/) {
      my $type = 'TF';
      $f = $1;
      $DXF{$n}->{$f} = $3 if $3;
      $SIZE{$n}->{$f} = $2;
      $ENT{$n}->{$f} = 'TF';
    } elsif (/^\s+FIELD_(.+?)\s*\(([\w\.]+),\s*(\d+)\)/) {
      my $type = $1;
      $f = $2;
      my $dxf = $3;
      $type =~ s/0$//; # strip ending 0, optional dxf (default 0 not printed)
      $type =~ s/^BD1$/BD/;
      # inlined unions
      $f =~ s/^(?:fmt|sty|name|value)\.//;
      # inlined struct: ctx.
      if ($f =~ /^ctx\.(\w+)$/) {
        $f = $1;
        $n = 'MLEADER_AnnotContext';
      }
      embedded_struct ('plotsettings', 'PLOTSETTINGS');
      embedded_struct ('cellstyle', 'TABLESTYLE_Cell');
      embedded_struct ('body', 'ACTIONBODY');
      embedded_struct ('ldata', 'LinkedData');
      embedded_struct ('tdata', 'LinkedTableData');
      embedded_struct ('fdata', 'FormattedTableData');
      embedded_struct ('dimension', 'OCD_Dimension');
      if ($n eq 'VISUALSTYLE') {
        $DXF{$n}->{$f."_int"} = 176;
        next if $DXF{$n}->{$f};
      }
      # (scale.x, 41) as is
      $DXF{$n}->{$f} = $dxf if $dxf;
      $ENT{$n}->{$f} = 'TF' if $type eq 'BINARY';
      $ENT{$n}->{$f} = $type if $type =~ /^T/;
      $ENT{$n}->{$f} = $type if $type =~ /^[23][RB]D_1/;
    } elsif (@old && /^\s+SUB_FIELD_HANDLE\s*\($v,\s*(\w+),\s*\d+,\s*(\d+)\)/) {
      my $type = $1;
      $f = $1;
      $DXF{$n}->{$f} = $2 if $2;
    } elsif (@old && /^\s+SUB_FIELD_(.+?)\s*\($v,\s*(\w+),\s*(\d+)\)/) {
      my $type = $1;
      $f = $2;
      $DXF{$n}->{$f} = $3 if $3;
    } elsif (/^\s+FIELD_(?:CMC|ENC)\s*\((\w+),\s*(\d+)\)/) {
      $f = $1;
      if ($2) {
        $DXF{$n}->{$f} = $2;
        if ($2 < 90) {
          $DXF{$n}->{"$f.index"} = $2;
          $DXF{$n}->{"$f.rbg"} = $2 + 420 - 62;
          $DXF{$n}->{"$f.name"} = $2 + 430 - 62;
          $DXF{$n}->{"$f.book_name"} = $2 + 430 - 62;
          $DXF{$n}->{"$f.alpha"} = $2 + 440 - 62;
        } else {
          $DXF{$n}->{"$f.rbg"} = $2;
        }
      }
    } elsif (/^\s+FIELD_(.+?)\s*\((\w+),.*,\s*(\d+)\)/) {
      my $type = $1;
      $f = $2;
      $type =~ s/^TFv$/TV/; # fixed but alloc'd
      $DXF{$n}->{$f} = $3 if $3;
      $ENT{$n}->{$f} = 'TF' if $type eq 'BINARY';
      $ENT{$n}->{$f} = $type if $type =~ /^T/;
      $ENT{$n}->{$f} = $type if $type =~ /^[23][RB]D_1/;
    } elsif (@old && /^\s+SUB_FIELD_(.+?)\s*\(\w+,\s*(\w+),.*,\s*(\d+)\)/) {
      my $type = $1;
      $f = $2;
      $DXF{$n}->{$f} = $3 if $3;
      $ENT{$n}->{$f} = 'TF' if $type eq 'BINARY';
      $ENT{$n}->{$f} = $type if $type =~ /^T/;
      $ENT{$n}->{$f} = $type if $type =~ /^[23][RB]D_1/;
    } elsif (/^\s+HANDLE_VECTOR(?:_N)?\s*\((\w+),.*?,\s*(\d+)\)/) {
      $f = $1;
      $DXF{$n}->{$f} = $2 if $2;
    } elsif (/^\s+SUB_HANDLE_VECTOR\s*\([^,]+?, (\w+), .+, (\d+)\)/) {
      $f = $1;
      $DXF{$n}->{$f} = $2 if $2;
    } elsif (/^\s+HEADER_.+\s*\((\w+),\s*(\d+)\)/) { # HEADER_RC
      $f = $1;
      $DXF{$n}->{$f} = $2 if $2;
    } elsif (/^\s+HEADER_.+\s*\((\w+),\s*(\d+),\s*\w+\)/) {
      $f = $1;
      $DXF{$n}->{$f} = $2 if $2;
    } elsif (/^\s+HEADER_.+\s*\((\w+),\s*\w+,\s*(\d+),\s*\D.+\)/) {
      $f = $1;
      #$f =~ s/^_3/3/;
      $DXF{$n}->{$f} = $2 if $2;
    } elsif (/^\s+HEADER_([23])D\s*\((\w+)\)/) {
      $f = $2;
      $DXF{$n}->{$f} = $1 eq '2' ? 20 : 30;
    } elsif (/^\s+VALUE_(.+)\s*\((\w.+),.*,\s*(\d+)\)/) {
      my $type = $1;
      $f = $2;
      $DXF{$n}->{$f} = $3 if $3;
      $ENT{$n}->{$f} = $type if $type =~ /^T/;
    } elsif (/^\s+SUBCLASS\s*\((\w.+)\)/) {
      my $sc = $1;
      if ($sc eq 'AcDbLayout') { # FIXME: pop PLOTSETTINGS earlier in LAYOUT
        if (@old && $old[0] eq 'LAYOUT') {
          $n = pop @old;
          warn "$n popped";
        }
      }
      if (exists $SUBCLASSES{$n}) {
        my $aref = $SUBCLASSES{$n};
        push @$aref, $sc;
        $SUBCLASSES{$n} = $aref;
      } else {
        $SUBCLASSES{$n} = [ $sc ];
      }
    } elsif (/^\s+(\w+)_fields;/) {
      expand_define ($1, $n, \%defined);
    } elsif (/^\s+(\w+_3DSOLID)/) { # special defines
      expand_define ($1, $n, \%defined);
    } elsif (/^\s+(\w+)_fields \((\w+)\)/) { # parametric macro
      expand_define ($1, $n, \%defined, $2);
    } elsif (/^\s+(COMMON_ENTITY_DIMENSION)/) {
      expand_define ($1, $n, \%defined);
    } elsif (/^$/ && $defined{$n}) {
      warn "undef $n\n";
      @old = ();
      undef $n;
    }
    if ($f and $n and exists $DXF{$n}->{$f}) {
      warn "  $f $DXF{$n}->{$f}\n";
    }
  }
}

# get dxf group for each struct field
sub dxfin_spec {
  my $fn = shift;
  open my $in, "<", $fn  or die "$fn: $!";
  dxf_in ($in);
  close $in;
}
dxfin_spec "$srcdir/dwg.spec";
$DXF{'BLOCK'}->{'name'} = 2; # and 3
$DXF{'BLOCK'}->{'filename'} = 4;
$DXF{'3DLINE'}->{'thickness'} = 39;
$DXF{'INSERT'}->{'block_header'} = 2;
$DXF{'MINSERT'}->{'block_header'} = 2;
$DXF{'POLYLINE_3D'}->{'flag'} = 70;
$DXF{'POLYLINE_MESH'}->{'flag'} = 70;
$DXF{'SHAPE'}->{'style_id'} = 2; # r11_idx => name
$DXF{'VISUALSTYLE'}->{'edge_hide_precision_flag'} = 290;
$DXF{'VISUALSTYLE'}->{'is_internal_use_only'} = 291;
# $DXF{'VISUALSTYLE'}->{'face_lighting_model'} = 71;
$DXF{'DIMSTYLE_CONTROL'}->{'morehandles'} = 340;
# $DXF{'DIMSTYLE'}->{'DIMFIT'} = 287;   # <= r14 only
$DXF{'PROXY_ENTITY'}->{'version'} = 95; # or 91 <= r14
$DXF{'DIMASSOC'}->{'intsect_gsmarker'} = 92;
$DXF{'DIMASSOC_Ref'}->{'xrefpaths'} = 301;
$DXF{'DIMSTYLE'}->{'flag'} = 70;
$DXF{'PLOTSETTINGS'}->{'plotview'} = 6;
$DXF{'SORTENTSTABLE'}->{'ents'} = 331;
$DXF{'SORTENTSTABLE'}->{'sort_ents'} = 5;
$DXF{'PLOTSETTINGS'}->{'shadeplot'} = 333;
$DXF{'OCD_Dimension'}->{'block'} = 2;
$DXF{'TABLECONTENT'}->{'tablestyle'} = 340;
$DXF{'TABLESTYLE'}->{'name'} = 3; # not 300
$DXF{'TABLE_Cell'}->{'cell_flag_override'} = 177;
$DXF{'ACSH_HistoryNode'}->{'trans'} = 40; # but inc by 1 for 16
# $DXF{'DIMENSION_ORDINATE'}->{'def_pt'} = 10;
# $DXF{'DIMENSION_ORDINATE'}->{'feature_location_pt'} = 13;
# $DXF{'DIMENSION_ORDINATE'}->{'leader_endpt'} = 14;
# $DXF{'DIMENSION_ORDINATE'}->{'flag2'} = 70;
# $DXF{'DIMENSION_ORDINATE'}->{'dimstyle'} = 3;
# $DXF{'DIMENSION_ORDINATE'}->{'block'} = 2;
$DXF{$_}->{'class_version'} = 280 for qw(ATTRIB ATTDEF); #r2010 only
$DXF{$_}->{'elevation'} = 30 for qw(TEXT ATTRIB ATTDEF); # not 31
$DXF{$_}->{'has_attribs'} = 66 for qw(INSERT MINSERT);
#$DXF{$_}->{'has_vertex'} = 66 for qw(POLYLINE_2D POLYLINE_3D POLYLINE_PFACE);
$DXF{UNDERLAYDEFINITION}->{'filename'} = 1;
$DXF{UNDERLAYDEFINITION}->{'name'} = 2;
$DXF{$_}->{'flag'} = 70 for qw(VERTEX_3D VERTEX_MESH VERTEX_PFACE_FACE POLYLINE_PFACE);
my @solids = qw(3DSOLID REGION BODY
                EXTRUDEDSURFACE LOFTEDSURFACE NURBSURFACE PLANESURFACE REVOLVEDSURFACE SWEPTSURFACE
                ACSH_BREP_CLASS);
$DXF{$_}->{'version'} = 70 for @solids;
$DXF{$_}->{'encr_sat_data'} = 1 for @solids;
$DXF{$_}->{'history_id'} = 350 for @solids;
$DXF{$_}->{'acis_empty'} = 290 for @solids;
$DXF{$_}->{'revision_guid'} = 2 for @solids;
my @annotscale = qw (TEXTOBJECTCONTEXTDATA MTEXTOBJECTCONTEXTDATA ALDIMOBJECTCONTEXTDATA
                     MTEXTATTRIBUTEOBJECTCONTEXTDATA MLEADEROBJECTCONTEXTDATA LEADEROBJECTCONTEXTDATA
                     BLKREFOBJECTCONTEXTDATA);
$DXF{$_}->{'class_version'} = 70 for @annotscale;
$DXF{$_}->{'is_default'} = 290 for @annotscale;
$DXF{$_}->{'scale'} = 340 for @annotscale;

dxfin_spec "$srcdir/header_variables_dxf.spec";
$DXF{header_variables}->{'_3DDWFPREC'} = 40;
# $DXF{header_variables}->{'TEXTSTYLE'} = 7;
# $DXF{header_variables}->{'CLAYER'} = 8;

$n = 'object_entity';
dxfin_spec "$srcdir/common_entity_data.spec";
dxfin_spec "$srcdir/common_entity_handle_data.spec";
$DXF{$n}->{'color'} = $DXF{$n}->{'color_r11'} = 62;
$DXF{$n}->{'color.rgb'} = 420; # handle 420?
$DXF{$n}->{'color.book'} = 430;
$DXF{$n}->{'color.alpha'} = 440;
$DXF{$n}->{'paper_r11'} = 67;
$DXF{$n}->{'plotstyle'} = 390;
$DXF{$n}->{'ownerhandle'} = 330;
$DXF{$n}->{'xdicobjhandle'} = 360;
$DXF{$n}->{'reactors'} = 330;
$DXF{$n}->{'preview_size'} = 160; # or 92

$n = 'object_object';
#dxfin_spec "$srcdir/common_object_handle_data.spec";
$DXF{$n}->{'ownerhandle'} = 330;
$DXF{$n}->{'xdicobjhandle'} = 360;
$DXF{$n}->{'reactors'} = 330;
# $DXF{$n}->{'flag'} = 70;
# $DXF{$n}->{'name'} = 2;

$n = 'summaryinfo';
dxfin_spec "$srcdir/summaryinfo.spec";

# dxfclassname for each of our classes and subclasses (not complete)
# Our NAME => 100
%SUBCLASS = (
  '3DSOLID' => "AcDbModelerGeometry",
  DIMENSION_ALIGNED => "AcDbAlignedDimension",
  DIMENSION_ORDINATE => "AcDbOrdinateDimension",
  DIMENSION_ORDINATE => "AcDb2LineAngularDimension",
  ARC_DIMENSION => "AcDbArcDimension",
  LWPOLYLINE => "AcDbPolyline",
  POLYLINE_3D => "AcDb3dPolyline",
  #VERTEX => "AcDbVertex",
  VERTEX_3D => "AcDb3dPolylineVertex",
  HATCH => "AcDbHatch",
  '3DFACE' => "AcDbFace",
  DICTIONARYVAR => "DictionaryVariables",

  "3DSOLID_silhouette" => "",
  "3DSOLID_wire" => "",
  "ACTIONBODY" => "AcDbAssocActionBody",
  # AcDbAssocParamBasedActionBody?
  # ASSOCDEPENDENCY AcDbAssocDependency
  # ASSOCGEOMDEPENDENCY AcDbAssocDependency
  # ASSOCGEOMDEPENDENCY AcDbAssocGeomDependency
  # ASSOCOSNAPPOINTREFACTIONPARAM => AcDbAssocActionParam,
  # ASSOCOSNAPPOINTREFACTIONPARAM => AcDbAssocCompoundActionParam,
  # ASSOCVERTEXACTIONPARAM => AcDbAssocActionParam,
  # ASSOCVERTEXACTIONPARAM => AcDbAssocSingleDependencyActionParam,
  # ASSOCVERTEXACTIONPARAM => AcDbAssocVertexActionParam,
  "CELLSTYLEMAP_Cell" => "",
  "DIMASSOC_Ref" => "",
  "DIMENSION_common" => "AcDbDimension",
  "EVAL_Node" => "",
  "FIELD_ChildValue" => "",
  "GEODATA_meshface" => "",
  "GEODATA_meshpt" => "",
  "HATCH_DefLine" => "",
  "HATCH_color" => "",
  "HATCH_control_point" => "",
  "HATCH_path" => "",
  "HATCH_pathseg" => "",
  "HATCH_polylinepath" => "",
  "ACSH_HistoryNode," => 'AcDbShHistoryNode',
  "LEADER_ArrowHead" => "",
  "LEADER_BlockLabel" => "",
  "LEADER_Break" => "",
  "LEADER_Line" => "",
  "LEADER_Node" => "",
  "LTYPE_dash" => "",
  "LWPOLYLINE_width" => "",
  "MLEADER_Content" => "",
  "MLEADER_AnnotContext" => "AcDbMLeaderAnnotContext",
  "MLINESTYLE_line" => "",
  "MLINE_line" => "",
  "MLINE_vertex" => "",
  # "OBJECTCONTEXTDATA" => "AcDbObjectContextData", # inlined, no subclass
  "OCD_Dimension" => "AcDbDimensionObjectContextData",
  "SPLINE_control_point" => "",
  "SPLINE_point" => "",
  "SUNSTUDY_Dates" => "",
  "TABLEGEOMETRY_Cell" => "",
  "TABLESTYLE_Cell" => "",
  "TABLE_BreakHeight" => "",
  "TABLE_BreakRow" => "",
  "TABLE_CustomDataItem" => "",
  "TABLE_cell" => "",
  "TABLE_value" => "",

  # TABLECONTENT => AcDbLinkedData
  # TABLECONTENT => AcDbLinkedTableData
  # TABLECONTENT => AcDbFormattedTableData
  "TABLECONTENT" => "AcDbTableContent",
  "TABLEGEOMETRY" => "AcDbTableGeometry",
  "DATATABLE" => "ACDBDATATABLE",
  # VIEWSTYLE_ModelDoc => "AcDbModelDocViewStyle",
  DETAILVIEWSTYLE => "AcDbDetailViewStyle",
  SECTIONVIEWSTYLE => "AcDbSectionViewStyle",
  ASSOCGEOMDEPENDENCY => "AcDbAssocDependency",
  ASSOCOSNAPPOINTREFACTIONPARAM => "ACDBASSOCOSNAPPOINTREFACTIONPARAM",
  ASSOCALIGNEDDIMACTIONBODY => "ACDBASSOCALIGNEDDIMACTIONBODY",
  ASSOCOSNAPPOINTREFACTIONPARAM => "ACDBASSOCOSNAPPOINTREFACTIONPARAM",
  # ACSH_HISTORY_CLASS => AcDbShHistory
  # ACSH_HISTORY_CLASS_Node => AcDbShHistoryNode
  );

# 300 CONTEXT_DATA{
# unused
my %SUBGROUP = (
  MLEADER_AnnotContext => "CONTEXT_DATA{",
  LEADER  => "LEADER{",
  LEADER_Line => "LEADER_LINE{",
  );

# DXFNAME 0 => our name
my %DXFALIAS = # see also CLASS.dxfname
  (
  ACDBDETAILVIEWSTYLE => "DETAILVIEWSTYLE",
  ACDBSECTIONVIEWSTYLE => "SECTIONVIEWSTYLE",
  ACDBPLACEHOLDER => "PLACEHOLDER",
  ACDBASSOCACTION => "ASSOCACTION",
  ACDBASSOCALIGNEDDIMACTIONBODY => "ASSOCALIGNEDDIMACTIONBODY",
  ACDBASSOCGEOMDEPENDENCY => "ASSOCGEOMDEPENDENCY",
  ACDBASSOCOSNAPPOINTREFACTIONPARAM => "ASSOCOSNAPPOINTREFACTIONPARAM",
  ACDBASSOCALIGNEDDIMACTIONBODY => "ASSOCALIGNEDDIMACTIONBODY",
  ACDBDATATABLE => "DATATABLE",
  );
my %DXFNAME =
  (
    _3DLINE        => "3DLINE",
    POLYLINE_2D    => "POLYLINE",
    POLYLINE_3D    => "POLYLINE",
    POLYLINE_MESH  => "POLYLINE",
    POLYLINE_PFACE => "POLYLINE",
    VERTEX_2D         => "VERTEX",
    VERTEX_3D         => "VERTEX",
    VERTEX_MESH       => "VERTEX",
    VERTEX_PFACE      => "VERTEX",
    VERTEX_PFACE_FACE => "VERTEX",
    DIMENSION_ALIGNED  => "DIMENSION",
    DIMENSION_ANG2LN   => "DIMENSION",
    DIMENSION_ANG3PT   => "DIMENSION",
    DIMENSION_DIAMETER => "DIMENSION",
    DIMENSION_LINEAR   => "DIMENSION",
    DIMENSION_ORDINATE => "DIMENSION",
    DIMENSION_RADIUS   => "DIMENSION",
    TABLE => "ACAD_TABLE",
    DETAILVIEWSTYLE => "ACDBDETAILVIEWSTYLE",
    SECTIONVIEWSTYLE => "ACDBSECTIONVIEWSTYLE",
    EVALUATION_GRAPH => "ACAD_EVALUATION_GRAPH",
    DICTIONARYWDFLT => "ACDBDICTIONARYWDFLT",
    PLACEHOLDER => "ACDBPLACEHOLDER",
    CURVEPATH => "ACDBCURVEPATH",
    MOTIONPATH => "ACDBMOTIONPATH",
    POINTPATH => "ACDBPOINTPATH",
    IBL_BACKGROUND => "RAPIDRTRENDERENVIRONMENT",
    NAVISWORKSMODEL => "COORDINATION_MODEL", #?
    PERSUBENTMGR => "ACDBPERSSUBENTMANAGER",
    DYNAMICBLOCKPURGEPREVENTER => "ACDB_DYNAMICBLOCKPURGEPREVENTER_VERSION",
    BLOCKREPRESENTATION => "ACDB_BLOCKREPRESENTATION_DATA",
    DYNAMICBLOCKPROXYNODE => "ACAD_DYNAMICBLOCKPROXYNODE",
    XREFPANELOBJECT => "EXACXREFPANELOBJECT",
    GEOPOSITIONMARKER => "POSITIONMARKER",
    PROXY_ENTITY => "ACAD_ENTITY_OBJECT",
    PROXY_OBJECT => "ACAD_PROXY_OBJECT",
    #PROXY_ENTITY => "ACAD_PROXY_ENTITY_WRAPPER",
    #PROXY_OBJECT => "ACAD_PROXY_OBJECT_WRAPPER",
   );

sub dxfname {
  $_ = shift;
  return $DXFNAME{$_} if exists $DXFNAME{$_};
  my $dxfname = $_;
  if (/^(ASSOC|NAVISWORKS|POINTCLOUD)/) {
    return "ACDB$dxfname";
  }
  if (/OBJECTCONTEXTDATA$/) {
    return "ACDB_${_}_CLASS";
  }
  return $dxfname;
}

my $cfile = "$srcdir/dynapi.c";
chmod 0644, $cfile if -e $cfile;
open my $fh, ">", $cfile or die "$cfile: $!";

# generate docs (GH #127)
my $docfile = "$topdir/doc/dynapi.texi";
chmod 0644, $docfile if -e $docfile;
open my $doc, ">", $docfile or die "$docfile: $!";
print $doc <<'EOF';
@c This file is automatically generated by src/gen-dynapi.pl. Do not modify here.
@c It is intended to be included within the main document.

EOF

sub is_table {
  return shift =~ /^(?:BLOCK_HEADER|LAYER|STYLE|LTYPE|VIEW|UCS|
                     VPORT|APPID|DIMSTYLE|VX_TABLE_RECORD)$/x;
}

sub is_table_control {
  return shift =~ /^(?:BLOCK|LAYER|STYLE|LTYPE|VIEW|UCS|
                     VPORT|APPID|DIMSTYLE|VX)_CONTROL$/x;
}

sub out_declarator {
  my ($d,$tmpl,$key,$prefix) = @_;
  my $n = "_dwg_$key" unless $key =~ /^_dwg_/;
  my $ns = $tmpl;
  $ns =~ s/^struct //;
  my $type = $d->{type};
  my $decl = $d->{declarators}->[0];
  my $name = $decl->{declarator};
  while ($name =~ /^\*/) {
    $name =~ s/^\*//;
    $type .= '*';
  }
  if ($prefix) { # optional, for unions only
    $name = $prefix . "." . $name;
  }
  # unexpand BITCODE_ macros: e.g. unsigned int -> BITCODE_BL
  my $bc = exists $h{$ns} ? $h{$ns}{$name} : undef;
  if (!$bc && $ns =~ /_CONTROL$/) {
    $bc = $h{COMMON_TABLE_CONTROL_FIELDS}{$name};
  } elsif (!$bc && $ns =~ /_entity_POLYLINE_/) {
    $bc = $h{COMMON_ENTITY_POLYLINE}{$name};
  }
  $type = $bc if $bc;
  if ($name eq 'encr_sat_data') {
    $type = 'char **'; $bc = '';
  }
  $type =~ s/\s+$//;
  my $size = $bc ? "sizeof (BITCODE_$type)" : "sizeof ($type)";
  $type =~ s/BITCODE_//;
  # TODO: DIMENSION_COMMON, _3DSOLID_FIELDS macros
  if ($type eq 'unsigned char') {
    $type = 'RC';
  } elsif ($type eq 'unsigned char*') {
    $type = 'RC*';
  } elsif ($type eq 'double') {
    $type = 'BD';
  } elsif ($type eq 'double*') {
    $type = 'BD*';
  } elsif ($type =~ /^Dwg_Bitcode_(\w+)/) {
    $type = $1;
  } elsif ($type eq 'char*') {
    $type = 'TV';
  } elsif ($type eq 'unsigned short int') {
    $type = 'BS';
  } elsif ($type eq 'uint16_t') {
    $type = 'BS';
  } elsif ($type eq 'unsigned int') {
    $type = 'BL';
  } elsif ($type eq 'unsigned int*') {
    $type = 'BL*';
  } elsif ($type eq 'uint32_t') {
    $type = 'BL';
  } elsif ($type eq 'uint32_t*') {
    $type = 'BL*';
  #} elsif ($type eq 'TFv') {
  #  $type = 'TV';
  } elsif ($type eq 'Dwg_Object_Ref*') {
    $type = 'H';
  } elsif ($type eq 'Dwg_Object_Ref**') {
    $type = 'H*';
  } elsif ($type =~ /\b(unsigned|char|int|long|double)\b/) {
    warn "unexpanded $type $n.$name\n";
  } elsif ($type =~ /^struct/) {
    if ($type =~ /\*$/) {
      $size = "sizeof (void *)";
    } else {
      # e.g. MLEADER_Content.txt.
      warn "inline struct $key.$name\n";
      for (@{$c->struct($d->{type})->{declarations}}) {
        out_declarator ($_, $tmpl, $key, $name);
      }
      #next;
    }
  } elsif ($type =~ /^union/) {
    warn "inline union $key.$name\n";
    for (@{$c->union($d->{type})->{declarations}}) {
      out_declarator ($_, $tmpl, $key, $name);
    }
    #next;
  } elsif ($type =~ /^HASH\(/) { # inlined struct or union
    if ($type->{type} eq 'union' && $n !~ /^_dwg_object_/) {
      # take all declarators and add the "$name." prefix
      warn "note: union field $n.$name\n";
      for (@{$type->{declarations}}) {
        out_declarator ($_, $tmpl, $key, $name);
      }
      next;
    } else {
      warn "ignore inlined field $n.$name\n";
      next;
    }
  }
  if ($ENT{$key}->{$name}) {
    $type = $ENT{$key}->{$name};
  } else {
    $ENT{$key}->{$name} = $type;
  }
  my $is_malloc = ($type =~ /\*$/ or $type =~ /^(T$|T[UVF]|D2T)/) ? 1 : 0;
  my $is_indirect = ($is_malloc or $type =~ /^(struct|[23T]|H$)/) ? 1 : 0;
  my $is_string = ($is_malloc and $type =~ /^(T[UV]?|D2T)$/) ? 1 : 0; # not TF or TFF
  my $sname = $name;
  if ($name =~ /\[(\d+)\]$/) {
    $is_malloc = 0;
    $size = "$1 * $size";
    $sname =~ s/\[(\d+)\]$//;
      $name = $sname; # if $sname eq 'conn_pts';
  }
  if ($type =~ /^TF/ && exists $SIZE{$key}->{$name}) {
    $size = $SIZE{$key}->{$name};
  }
  my $dxf = $DXF{$key}->{$name};
  if (!$dxf && $key =~ /DIMENSION/) {
    $dxf = $DXF{COMMON_ENTITY_DIMENSION}->{$name};
  }
  if (!$dxf && $key =~ /ASSOC/) {
    $dxf = $DXF{ASSOCACTION}->{$name};
  }
  $dxf = 0 unless $dxf;
  warn "no dxf for $key: $name 0\n" unless $dxf or
    ($name eq 'parent') or
    ($key eq 'header_variables' and $name eq lc($name));

  printf $fh "  { \"%s\",\t\"%s\", %s,  OFF (%s, %s),\n    %d,%d,%d, %d },\n",
    $name, $type, $size, $tmpl, $sname, $is_indirect, $is_malloc, $is_string, $dxf;

  print $doc "\@item $name\n$type", $dxf ? ",\tDXF $dxf" : "", "\n";
}

# until the type is a struct or union
sub expand_typedef {
  my $s = shift;
  if ($s =~ /^(struct|union)/) {
    return $s;
  }
  my $typedef = $c->typedef($s);
  return undef unless $typedef;
  my $type;
  $s = $typedef->{type};
  while ($s and $s !~ /^(struct|union)/) {
    $s = expand_typedef ($s);
  }
  return $s;
}

my %out_struct;
sub out_struct {
  my ($tmpl, $n) = @_;
  #print $fh " /* ", Data::Dumper->Dump([$s], [$n]), "*/\n";
  my $key = $n;
  my $sortedby = 'offset';
  my $s = $c->struct($tmpl);
  unless ($s) {
    $s = $c->union($tmpl);
  }
  unless ($s) { # resolve abstract typedef to struct|union
    $s = expand_typedef ($tmpl);
    $s = $c->struct($s) if $s;
  }
  unless ($s->{declarations}) {
    delete $ENT{$n};   # don't show up in dynapi_test.c
    return;
  }
  if (exists $out_struct{$tmpl}) {
    warn "skip duplicate $tmpl\n";
    my $see = $out_struct{$tmpl};
    print $doc "\@indentedblock\n";
    print $doc "\@xref{$see}\n";
    print $doc "\@end indentedblock\n\n";
    return;
  }
  $out_struct{$tmpl} = $key;
  $n = "_dwg_$n" unless $n =~ /^_dwg_/;
  my @declarations = @{$s->{declarations}};
  if ($n =~ /^_dwg_(header_variables|object_object|object_entity)$/) {
    @declarations = sort {
      my $aname = $a->{declarators}->[0]->{declarator};
      my $bname = $b->{declarators}->[0]->{declarator};
      $aname =~ s/^\*//g;
      $bname =~ s/^\*//g;
      return $aname cmp $bname
    } @declarations;
    $sortedby = 'name';
  }
  if ($tmpl =~ /_dwg_object_/) {
    if (is_table($key)) {
      print $doc "\@cindex table, $key\n\n";
      print $doc "$key is a table object.\n\n";
    }
    elsif (is_table_control($key)) {
      print $doc "\@cindex table_control, $key\n\n";
      print $doc "$key is a table_control object.\n\n";
    }
  }
  print $doc "\@indentedblock\n";
  print $doc "\@vtable \@code\n\n";
  print $fh "/* from typedef $tmpl: (sorted by $sortedby) */\n",
    "static const Dwg_DYNAPI_field $n","_fields[] = {\n";
  for my $d (@declarations) {
    out_declarator($d, $tmpl, $key);
  }
  print $fh "  {NULL,\tNULL,\t0,\t0,\t0,0,0, 0},\n";
  print $fh "};\n";
  print $doc "\n\@end vtable\n";
  print $doc "\@end indentedblock\n\n";
}

sub maxlen {
  my $maxlen = 0;
  for (@_) {
    $maxlen = length($_) if $maxlen < length($_);
  }
  $maxlen
}
$max_entity_names = 1+maxlen(@entity_names);
$max_object_names = 1+maxlen(@object_names);
my %entity_names = map {$_ => 1} @entity_names;
my %object_names = map {$_ => 1} @object_names;
$max_subclasses = 0;
for (keys %SUBCLASSES) {
  if (!exists $entity_names{$_} and !exists $object_names{$_}) {
    # FIXME: find parent class and add array to it.
    # delete $SUBCLASSES{$_};
  }
}
for (sort @entity_names) {
# FIXME: fixup duplicates in TEXT
  if ($_ eq 'TEXT') {
    $SUBCLASSES{$_} = [ 'AcDbText' ];
  } elsif (/^...UNDERLAY$/) {
    $SUBCLASSES{$_} = [ 'AcDbUnderlayReference' ];
  }
  my $aref = $SUBCLASSES{$_};
  unshift @$aref, 'AcDbEntity';
  $SUBCLASSES{$_} = $aref; # if it didn't exist
  my $len = @$aref;
  $max_subclasses = $len if $len > $max_subclasses;
}
# /^(BLOCK_HEADER|LAYER|STYLE|LTYPE|VIEW|UCS|VPORT|APPID|DIMSTYLE|VX_TABLE_RECORD)$/
my %table_dxfname = ( # See COMMON_TABLE_FLAGS
  BLOCK_HEADER => 'Block',
  LAYER => 'Layer',
  STYLE => 'TextStyle',
  LTYPE => 'Linetype',
  VIEW => 'View',
  UCS => 'UCS',
  VPORT => 'Viewport',
  APPID => 'RegApp',
  DIMSTYLE => 'DimStyle',
  VX_TABLE_RECORD => 'VX',
);
for (sort @object_names) {
  my $aref = $SUBCLASSES{$_};
  if (is_table ($_)) {
    unshift @$aref, ('AcDbSymbolTableRecord', 'AcDb' . $table_dxfname{$_} . 'TableRecord');
  } elsif (is_table_control ($_)) {
    unshift @$aref, 'AcDbSymbolTable';
  } else {
    unshift @$aref, 'AcDbObject';
  }
  $SUBCLASSES{$_} = $aref; # if it didn't exist
  my $len = @$aref;
  $max_subclasses = $len if $len > $max_subclasses;
}

# ---------------------------------------------------------------
for (<DATA>) {
  # expand enum or struct
  if (/^(.*)\@\@(\w+ \w+)\@\@(.*)/) {
    my ($pre, $post) = ($1, $3);
    my $tmpl = $2;
    print $fh $pre;
    if ($tmpl =~ /^enum (\w+)/) {
      my $s = $c->enum($tmpl);
      #print $fh "\n/* ";
      #print $fh Data::Dumper->Dump([$s], [$1]);
      #print $fh "\n*/";
      my $i = 0;
      my @keys = map { s/^DWG_TYPE__3D/DWG_TYPE_3D/; $_ } keys %{$s->{enumerators}};
      for (sort @keys) {
        my ($k,$v) = ($_, $s->{enumerators}->{$_});
        if ($tmpl eq 'enum DWG_OBJECT_TYPE') {
          $k =~ s/^DWG_TYPE_//;
          # Let the type rather be symbolic: DWG_TYPE_SOLID, not int
          my $vs = $_;
          if (!$v && $k =~ /^3D/) {
            $v = $s->{enumerators}->{'DWG_TYPE__'.$k};
            $vs = 'DWG_TYPE__'.$k;
          }
          my $size = "0";
          if ($c->struct("_dwg_entity_$k")) {
            $size = "sizeof (struct _dwg_entity_$k)";
          } elsif ($c->struct("_dwg_object_$k")) {
            $size = "sizeof (struct _dwg_object_$k)";
          #} elsif ($c->typedef("Dwg_Object_$k")) {
          #  my $type = expand_typedef ("Dwg_Object_$k");
          #  if ($type) {
          #    $size = "sizeof (Dwg_Object_$k)";
          #    $k = $type;
          #    $k =~ s/struct //;
          #    $k =~ s/_dwg_(?:abstract)?object_//;
          #    $k =~ s/ //g;
          #  }
          #} elsif ($c->typedef("Dwg_Entity_$k")) {
          #  my $type = expand_typedef ("Dwg_Entity_$k");
          #  if ($type) {
          #    $size = "sizeof (Dwg_Entity_$k)";
          #    $k = $type;
          #    $k =~ s/struct //;
          #    $k =~ s/_dwg_(?:abstract)?entity_//;
          #    $k =~ s/ //g;
          #  }
          }
          # see if the fields do exist:
          my $fields = exists $structs{$k} ? "_dwg_".$k."_fields" : "NULL";
          if ($k =~ /^(BODY|REGION)$/) {
            $fields = "_dwg_3DSOLID_fields";
            $size = "sizeof (struct _dwg_entity_3DSOLID)";
          } elsif ($k eq 'XLINE') {
            $fields = "_dwg_RAY_fields";
            $size = "sizeof (struct _dwg_entity_RAY)";
          } elsif ($k =~ /^(PDF|DWF|DGN)DEFINITION$/) {
            $fields = "_dwg_UNDERLAYDEFINITION_fields";
            $size = "sizeof (Dwg_Object_$k)";
          } elsif ($k =~ /^(PDF|DWF|DGN)UNDERLAY$/) {
            $fields = "_dwg_UNDERLAY_fields";
            $size = "sizeof (Dwg_Entity_$k)";
          } elsif ($k =~ /^ASSOCARRAY(?:MODIFY|PATH|POLAR|RECTANGULAR)PARAMETERS$/) {
            $fields = "_dwg_ASSOCARRAYPARAMETERS_fields";
            $size = "sizeof (Dwg_Object_ASSOCARRAYPARAMETERS)";
          } elsif ($k =~ /^VERTEX_(MESH|PFACE)$/) {
            $fields = "_dwg_VERTEX_3D_fields";
            $size = "sizeof (struct _dwg_entity_VERTEX_3D)";
          } elsif ($k =~ /^PROXY_LWPOLYLINE$/) { # TODO subent, not entity
            $size = "sizeof (struct _dwg_entity_PROXY_LWPOLYLINE)";
          }
          $DWG_TYPE{$k} = $vs;
          printf $fh "  { \"%s\", %s /*(%d)*/, %s, %s },\t/* %d */\n",
            $k, $vs, $v, $fields, $size, $i++;
        } else {
          printf $fh "  { \"%s\", %d },\t/* %d */\n",
            $k, $v, $i++;
        }
      }
    } elsif ($tmpl =~ /^list (\w+)/) {
      no strict 'refs';
      my $n = $1;
      my $i = 0;
      my $maxlen = 0;
      for (@{$n}) {
        $maxlen = length($_) if $maxlen < length($_);
      }
      if ($n eq 'subclasses') {
        for (sort @{$n}) {
          if (/^_dwg_(.*)/) {
            my $n = $1;
            # find class type for this subclass. DWG_TYPE_parent if unique, or -1
            my $type = 0;
            for my $o (keys %DWG_TYPE) {
              my $match = qr '^' . $o . '_\w+';
              if ($n =~ $match) {
                if ($type) {
                  $type = '-1'; # multiple
                } else {
                  $type = '(int)'.$DWG_TYPE{$o};
                }
              }
            }
            my $size = "sizeof (Dwg_$n)";
            if ($c->union("$_")) {
              $size = "sizeof (Dwg_$n)";
            }
            my $subclass = $SUBCLASS{$n};
            if ($subclass) {
              $subclass = '"' . $subclass . '"';
            } else {
              $subclass = "NULL";
            }
            printf $fh "  { \"%s\", %s, %s, %s, %s },\t/* %d */\n",
              $n, $type, $subclass, $_ . "_fields", $size, $i++;
          }
        }
      } elsif ($n eq 'name_subclasses') {
        #print Dumper \%SUBCLASSES;
        for (sort keys %SUBCLASSES) {
          my $cl = $_;
          if (exists $entity_names{$cl} or exists $object_names{$cl}) {
            my $a = $SUBCLASSES{$cl};
            # $cl =~ s/^3D/_3D/;
            printf $fh "  { \"%s\", {", $cl;
            for (0 .. $max_subclasses-1) {
              if ($a->[$_]) {
                printf $fh "\"%s\"", $a->[$_];
              } else {
                printf $fh "NULL";
              }
              if ($_ < $max_subclasses - 1) {
                printf $fh ", ";
              }
            }
            printf $fh "} },\n";
          }
        }
      } else {
        for (@{$n}) {
          my $len = length($_);
          printf $fh "  \"%s\" \"%s\",\t/* %d */\n", $_, "\\0" x ($maxlen-$len), $i++;
        }
      }
    } elsif ($tmpl =~ /^scalar (\w+)/) {
      no strict 'refs';
      my $n = $1;
      printf $fh ${$n};
    } elsif ($tmpl =~ /^for dwg_entity_ENTITY/) {
      print $doc "\n\@node ENTITIES\n\@section ENTITIES\n\@cindex ENTITIES\n\n";
      print $doc "All graphical objects with its fields. \@xref{Common Entity fields}\n\n";
      for (@entity_names) {
        print $doc "\@strong{$_} \@anchor{$_}\n\@cindex entity, $_\n",
          "\@vindex $_\n" unless $_ eq 'DIMENSION_';
        print $doc "\@anchor{UNDERLAY}\n\@vindex UNDERLAY\n" if /^DGNUNDERLAY/;
        print $doc "\n" unless $_ eq 'DIMENSION_';
        my $typedef = $c->typedef("Dwg_Entity_$_");
        # multiple type aliases, only emit one _field[]
        if ($typedef and $typedef->{type} ne "struct _dwg_entity_$_") {
          my $type = expand_typedef ($typedef->{type});
          if ($type) {
            my $n = $type;
            $n =~ s/struct //;
            $n =~ s/_dwg_(?:abstract)?entity_//;
            $n =~ s/ //g;
            out_struct($type, $n);
          } else {
            out_struct("struct _dwg_entity_$_", $_);
          }
        } else {
          out_struct("struct _dwg_entity_$_", $_);
        }
      }
    } elsif ($tmpl =~ /^for dwg_object_OBJECT/) {
      print $doc "\n\@node OBJECTS\n\@section OBJECTS\n\@cindex OBJECTS\n\n";
      print $doc "All non-graphical objects with its fields. \@xref{Common Object fields}\n\n";
      for (@object_names) {
        print $doc "\@strong{$_} \@anchor{$_}\n\@cindex object, $_\n\@vindex $_\n";
        print $doc "\@anchor{UNDERLAYDEFINITION}\n\@vindex UNDERLAYDEFINITION\n" if /^PDFDEFINITION/;
        print $doc "\@anchor{ASSOCARRAYPARAMETERS}\n\@vindex ASSOCARRAYPARAMETERS\n" if /^ASSOCARRAYMODIFYPARAMETERS/;
        print $doc "\n";
        my $typedef = $c->typedef("Dwg_Object_$_");
        if ($typedef and $typedef->{type} ne "struct _dwg_object_$_") {
          my $type = expand_typedef ($typedef->{type}); # unify to one struct
          if ($type) {
            my $n = $type;
            $n =~ s/struct //;
            $n =~ s/_dwg_(?:abstract)?object_//;
            $n =~ s/ //g;
            out_struct($type, $n);
          } else {
            out_struct("struct _dwg_object_$_", $_);
          }
        } else {
          out_struct("struct _dwg_object_$_", $_);
        }
      }
    } elsif ($tmpl =~ /^for dwg_subclasses/) {
      for (@subclasses) {
        my ($name) = $_ =~ /^_dwg_(.*)/;
        print $doc "\@strong{Dwg_$name} \@anchor{Dwg_$name}\n\@vindex Dwg_$name\n\n";
        if ($unions{$_}) {
          out_struct("union $_", $name);
        } else {
          out_struct("struct $_", $name);
        }
      }
    } elsif ($tmpl =~ /^struct _dwg_(\w+)/) {
      if ($1 eq 'header_variables') {
        print $doc "\n\@node HEADER\n\@section HEADER\n\@cindex HEADER\n\n";
        print $doc "All header variables.\n\n";
      } elsif ($1 eq 'object_object') {
        print $doc "\@strong{Common Object fields} \@anchor{Common Object fields}\n";
        print $doc "\@cindex Common Object fields\n\n";
      } elsif ($1 eq 'object_entity') {
        print $doc "\@strong{Common Entity fields} \@anchor{Common Entity fields}\n";
        print $doc "\@cindex Common Entity fields\n\n";
      } elsif ($1 eq 'summaryinfo') {
        print $doc "\@strong{SummaryInfo fields} \@anchor{SummaryInfo fields}\n";
        print $doc "\@cindex SummaryInfo fields\n\n";
        print $doc "\@pxref{SummaryInfo}\n\n";
      } else {
        print $doc "\@strong{$1}\n";
        print $doc "\@vindex $1\n\n";
      }
      out_struct($tmpl, $1);
    } elsif ($tmpl =~ /^struct Dwg_(\w+)/) {
      print $doc "\@strong{$1}\n";
      print $doc "\@vindex $1\n\n";
      out_struct($tmpl, $1);
    }
    print $fh $post,"\n";
  } else {
    print $fh $_;
  }
}
chmod 0444, $fh;
close $fh;
chmod 0444, $doc;
close $doc;

# TODO: use dwg.h formats
my %FMT = (
    'double' => '%g',
    'unsigned char' => '%c',
    'unsigned int' => '%u',
    'unsigned long' => '%lu',
    'unsigned short int' => '%hu',
    'short' => '%hd',
    'int16_t' => '%hd',
    'long' => '%l',
    'char**' => '%p',
    'TV' => '%s',
    'T'  => '%s',
    'D2T' => '%s',
    'TU' => '%ls',
    'TFF' => '%s',
    'BD' => '%g',
    'BL' => '%u',
    'BS' => '%hu',
    'RD' => '%g',
    'RL' => '%u',
    'RS' => '%hu',
    'RC' => '%u',
    'RC*' => '%s',
    );

# ---------------------------------------------------------------
# The simple list of macro defs (linear search)
my $objfile = "$srcdir/objects.inc";
chmod 0644, $objfile if -e $objfile;
open my $inc, ">", "$objfile.tmp" or die "$objfile: $!";
print $inc <<"EOF";
/* ex: set ro ft=c: -*- mode: c; buffer-read-only: t -*- */
/*****************************************************************************/
/*  LibreDWG - free implementation of the DWG file format                    */
/*                                                                           */
/*  Copyright (C) 2019-2024 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/>.    */
/*****************************************************************************/

/*
 * objects.inc: define all object and entities
 * written by Reini Urban
 * generated by src/gen-dynapi.pl from include/dwg.h, do not modify.
 */

EOF

for my $name (@entity_names) {
  my $xname = $name =~ /^3/ ? "_$name" : $name; # 3DFACE, 3DSOLID
  next if $name eq 'DIMENSION_';
  next if $name eq 'PROXY_LWPOLYLINE';
  print $inc "DWG_ENTITY ($xname)\n";
}
print $inc "\n";
for my $name (@object_names) {
  print $inc "DWG_OBJECT ($name)\n";
}
close $inc;
mv_if_not_same ("$objfile.tmp", $objfile);
chmod 0444, $objfile;

# ---------------------------------------------------------------
my $infile = "$topdir/test/unit-testing/dynapi_test.c.in";
open $in, $infile or die "$infile: $!";
$cfile  = "$topdir/test/unit-testing/dynapi_test.c";
chmod 0644, $cfile if -e $cfile;
open $fh, ">", $cfile or die "$cfile: $!";
print $fh "/* ex: set ro ft=c: -*- mode: c; buffer-read-only: t -*- */\n";

for (<$in>) {
  print $fh $_;
  if (m{/\* \@\@for test_HEADER\@@ \*/}) {
    my $s = $c->struct('_dwg_header_variables');
    for my $d (@{$s->{declarations}}) {
      my $type = $d->{type};
      my $decl = $d->{declarators}->[0];
      my $name = $decl->{declarator};
      while ($name =~ /^\*/) {
        $name =~ s/^\*//;
        $type .= '*';
      }
      $type =~ s/ $//g;
      my $xname = $name =~ /^3/ ? "_$name" : $name;
      my $lname = lc $xname;
      my $var = $lname;
      my $sname = $name;
      if (exists $ENT{header_variables}->{$name}) {
        $type = $ENT{header_variables}->{$name};
      }
      $type =~ s/D_1$/D/;
      my $fmt = exists $FMT{$type} ? $FMT{$type} : undef;
      if (!$fmt) {
        if ($type =~ /[ \*]/ or $type eq 'H') {
          $fmt = '%p';
        } else {
          $fmt = "\" FORMAT_$type \"";
        }
      }
      my $is_ptr = ($type =~ /^(struct|Dwg_)/ or
                    $type =~ /^[23HT]/ or
                    $type =~ /\*$/ or
                    $var  =~ /\[\d+\]$/ or
                    $type =~ /^(BE|CMC)$/)
        ? 1 : 0;
      if ($var  =~ /\[\d+\]$/) {
        $lname =~ s/\[\d+\]$//g;
        $sname =~ s/\[\d+\]$//g;
      }
      $var  =~ s/^timezone/timezn/; # global on darwin
      my $stype = $type;
      $type = 'BITCODE_'.$type unless ($type =~ /^(struct|Dwg_)/ or $type =~ /^[a-z]/);
      if (!$is_ptr) {
        print $fh <<"EOF";
  {
    $type $var;
    if (dwg_dynapi_header_value (dwg, "$sname", &$var, NULL)
        && $var == dwg->header_vars.$name)
      pass ();
    else
      fail ("HEADER.$name [$stype] $fmt != $fmt", dwg->header_vars.$sname, $var);
EOF
        if ($type =~ /(int|long|short|char ||double|_B\b|_B[BSLD]\b|_R[CSLD])/) {
          print $fh "    $var++;\n";
        }
        print $fh <<"EOF";
    if (dwg_dynapi_header_set_value (dwg, "$sname", &$var, 0)
        && $var == dwg->header_vars.$name)
      pass ();
    else
      fail ("HEADER.$name [$stype] set+1 $fmt != $fmt",
            dwg->header_vars.$sname, $var);
EOF
        if ($type =~ /(int|long|short|char ||double|_B\b|_B[BSLD]\b|_R[CSLD])/) {
          print $fh "    $var--;\n";
          print $fh "    dwg_dynapi_header_set_value (dwg, \"$sname\", &$var, 0);\n";
        }
        print $fh "\n  }\n";
      } else {
        print $fh <<"EOF";
  {
    $type $var;
    if (dwg_dynapi_header_value (dwg, "$sname", &$lname, NULL)
EOF
        if ($type !~ /\*\*/) {
          print $fh <<"EOF";
        && !memcmp (&$lname, &dwg->header_vars.$sname, sizeof (dwg->header_vars.$sname))
EOF
        }
        print $fh <<"EOF";
       )
      pass ();
    else
      fail ("HEADER.$name [$stype]");
  }
EOF
      }
    }
  }
  if (m{/\* \@\@for if_test_OBJECT\@\@ \*/}) { # The impl, inside test_object
    for my $name (@entity_names, @object_names) {
      my $xname = $name =~ /^3/ ? "_$name" : $name; # 3DFACE, 3DSOLID
      #next if $name eq 'DIMENSION_';
      next if $name =~ /^(PROXY_LWPOLYLINE|UNKNOWN_)/;
      print $fh "  else" if $name ne '3DFACE'; # the first
      print $fh <<"EOF";
  if (obj->fixedtype == DWG_TYPE_$xname)
    error += test_$xname(obj);
EOF
    }
  }
  # The first, as decl
  if (m{/\* \@\@for test_OBJECT\@\@ \*/}) {
    for my $name (@entity_names, @object_names) {
      #next if $name eq 'DIMENSION_';
      #TABLE is stored as fixedtype UNKNOWN_ENT, so the dynapi test would fail
      next if $name =~ /^(PROXY_LWPOLYLINE|UNKNOWN_)/;
      my $is_ent = grep { $name eq $_ } @entity_names;
      my ($Entity, $lentity) = $is_ent ? ('Entity', 'entity') : ('Object', 'object');
      my $xname = $name =~ /^3/ ? "_$name" : $name;
      my $lname = lc $xname;
      my $struct = "Dwg_$Entity" . "_$xname";
      print $fh <<"EOF";
static int test_$xname (const Dwg_Object *obj)
{
  int error = 0;
  const Dwg_Object_$Entity *restrict obj_obj = obj->tio.$lentity;
  $struct *restrict $lname = obj->tio.$lentity->tio.$xname;
  failed = 0;
  if (!obj_obj || !$lname)
    {
      fail ("NULL $xname");
      return 1;
    }
EOF

  for my $var (sort keys %{$ENT{$name}}) {
    my $type = $ENT{$name}->{$var};
    # if 0 ignored in .spec
    # next if $type eq 'T' and $name eq 'LIGHT' and $var eq 'web_file';
    # next if $type eq 'TF' and $name eq 'SUN' and $var eq 'bytes';
    # LAYER.flag is computed since r2000
    next if $name eq 'LAYER' and $var eq 'flag';
    my $fmt = exists $FMT{$type} ? $FMT{$type} : undef;
    if (!$fmt) {
      if ($type =~ /[ \*]/ or $type eq 'H') {
        $fmt = '%p';
      } else {
        $fmt = "\" FORMAT_$type \"";
      }
    }
    my $key = $var;
    my $svar = $var;
    my $skey = $var;
    my $is_ptr = ($type =~ /^(struct|Dwg_)/ or
                  $type =~ /^[TH23]/ or
                  $type =~ /\*$/ or
                  $var =~ /\[\d+\]$/ or
                  $type =~ /^(BE|CMC)$/)
      ? 1 : 0;
    if ($var  =~ /\./) { # embedded structs, like ovr.name. some have fields, some not
      next if $var =~ /^ovr\./;
      $svar =~ s/\./_/g;
      $var = $svar;
    }
    if ($var =~ /\[\d+\]$/) {
      $svar =~ s/\[\d+\]$//g;
      $skey =~ s/\[\d+\]$//g;
      $key = $skey;
    }
    next if $key eq 'evalexpr.value.text1'; # already handled by evalexpr memcmp
    my $stype = $type;
    $type =~ s/D_1$/D/;
    $type = 'BITCODE_'.$type unless ($type =~ /^(struct|Dwg_)/ or $type =~ /^[a-z]/);
    if (!$is_ptr) {
      # TODO DEBUGGING [BR]D can be nan
      print $fh <<"EOF";
  {
    $type $var;
    if (dwg_dynapi_entity_value ($lname, "$name", "$key", &$svar, NULL)
        && $var == $lname->$key)
      pass ();
    else
      fail ("$name.$key [$stype] $fmt != $fmt", $lname->$key, $svar);
EOF
      if ($type =~ /(int|long|short|char|double|_B\b|_B[BSLD]\b|_R[CSLD])/) {
        print $fh "    $svar++;\n";
      }
      print $fh <<"EOF";
    if (dwg_dynapi_entity_set_value ($lname, "$name", "$key", &$svar, 0)
        && $var == $lname->$key)
      pass ();
    else
      fail ("$name.$key [$stype] set+1 $fmt != $fmt", $lname->$key, $svar);
EOF
      if ($type =~ /(int|long|short|char ||double|_B\b|_B[BSLD]\b|_R[CSLD])/) {
        print $fh "    $lname->$key--;";
      }
      print $fh "\n  }\n";
    } elsif ($type =~ /\*$/ and $type !~ /(RC\*|struct _dwg_object_)/
             # no countfield
             and $var !~ /^(ref|block_size|extra_acis_data|objid_object_handles)$/
             # VECTOR_N
             and $var !~ /(_transform|_transmatrix1?|shhn_pts)$/) {
      my %countfield = (
        attribs => 'num_owned', # XXX TABLE
        vertex => 'num_owned',
        itemhandles => 'numitems',
        entities => 'num_owned',
        #inserts => 'num_inserts',
        #groups => 'num_groups',
        #field_handles => 'num_fields',
        sort_ents => 'num_ents',
        attr_def_id => 'num_attr_defs',
        layer_entries => 'num_entries',
        readdeps  => 'num_deps',
        writedeps => 'num_deps',
        dashes_r11 => 'numdashes',
        texts => 'numitems',
        encr_sat_data => 'num_blocks',
        );
      my $countfield = exists $countfield{$var} ? $countfield{$var} : "num_$var";
      $countfield = 'numdashes' if $name eq 'LTYPE' and $var =~ /^(styles|dashes)$/;
      my $count = 1;
      if ($var eq 'encr_sat_data') {
        print $fh <<"EOF";
  {
    $type $var;
    if (dwg_dynapi_entity_value ($lname, "$name", "$key", &$svar, NULL)
        && !memcmp (&$svar, &$lname->$skey, sizeof ($lname->$skey)))
      pass ();
    else
      fail ("$name.$key [$stype]");
  }
EOF
      }
      elsif ($var eq 'reactors' and $type eq 'BITCODE_H*') {
        print $fh <<"EOF";
  {
    $type $var;
    BITCODE_BL count = obj_obj->num_reactors;
    if (dwg_dynapi_entity_value ($lname, "$name", "$key", &$svar, NULL)
        && $svar == $lname->$key)
      pass ();
    else
      fail ("$name.$var [$stype] * %u $countfield", count);
  }
EOF
      } else {
        print $fh <<"EOF";
  {
    $type $var;
    BITCODE_BL count = 0;
    if (dwg_dynapi_entity_value ($lname, "$name", "$countfield", &count, NULL)
        && dwg_dynapi_entity_value ($lname, "$name", "$var", &$svar, NULL)
EOF
        if ($type eq 'BITCODE_BD') {
          print $fh "        && (isnan ($svar) || $svar == $lname->$svar)";
        }
        elsif ($type !~ /\*\*/) {
          print $fh "        && $svar == $lname->$svar";
        }
        print $fh ")\n";
        print $fh <<"EOF";
      pass ();
    else
      fail ("$name.$var [$stype] * %u $countfield", count);
  }
EOF
      }
    } else { # is_ptr
      my $is_str;
      my $vardecl = $var;
      my $size = "sizeof ($type)";
      if (0 and $stype =~ /^TF/) {
        my $_size = $SIZE{$name}->{$var};
        if ($_size && $_size =~ /^\d+$/) {
          $type = 'char';
          $size = $_size;
          $vardecl .= "[$size]";
          if ($var eq 'strings_area') {
            $vardecl .= ";\n    const Dwg_Data* dwg = obj->parent;";
            $vardecl .= "\n    const int size = dwg->header.version >= 2004 ? 512 : 256";
            $size = 'size';
          }
        }
      }
      print $fh <<"EOF";
  {
    $type $vardecl;
    if (dwg_dynapi_entity_value ($lname, "$name", "$key", &$svar, NULL)
EOF
        if ($stype =~ /^(TV|T|TU|RC\*|unsigned char\*|char\*)$/) {
          $is_str = 1;
          print $fh "        && $svar\n";
          print $fh "           ? strEQ ((char *)$svar, (char *)$lname->$key)\n";
          print $fh "           : !$lname->$key)\n";
        } elsif (0 and $stype =~ /^TF/ and $size !~ /^sizeof/) {
          print $fh "        && !memcmp ($svar, $lname->$skey, $size))\n";
        } elsif ($type !~ /\*\*/) {
          print $fh "        && !memcmp (&$svar, &$lname->$skey, $size))\n";
        } else {
          print $fh ")\n";
        }
        if ($is_str) {
          print $fh <<"EOF";
      pass ();
    else
      fail ("$name.$key [$stype] '$fmt' <> '$fmt'", $svar, $lname->$skey);
  }
EOF
        } else {
          print $fh <<"EOF";
        pass ();
    else
        fail ("$name.$key [$stype]");
  }
EOF
        }
      }
    }
    print $fh <<"EOF";
  if (failed && (is_class_unstable ("$name") || is_class_debugging ("$name")))
    {
      ok ("%s failed %d tests (TODO unstable)", "$name", failed);
      failed = 0;
    }
  return failed;
}
EOF
    }
  }

  if (m{/\* \@\@for if_test_OBJECT\@\@ \*/}) {
    for my $name (@entity_names, @object_names) {
      my $xname = $name =~ /^3/ ? "_$name" : $name; # 3DFACE, 3DSOLID
      next if $name eq 'DIMENSION_';
      next if $name =~ /^(PROXY_LWPOLYLINE|UNKNOWN_)/;
      print $fh "  else" if $name ne '3DFACE'; # the first
      print $fh <<"EOF";
  if (obj->fixedtype == DWG_TYPE_$xname)
    error += test_$xname (obj);
EOF
    }
  }

  if (m{/\* \@\@for test_SIZES\@\@ \*/}) {
    for my $name (@entity_names) {
      my $xname = $name =~ /^3/ ? "_$name" : $name; # 3DFACE, 3DSOLID
      print $fh <<"EOF";
  size1 = sizeof (Dwg_Entity_$xname);
  size2 = dwg_dynapi_fields_size (\"$name\");
  if (size1 != size2)
    {
      fprintf (stderr, "sizeof(Dwg_Entity_$xname): %d != "
               "dwg_dynapi_fields_size (\\\"$name\\\"): %d\\n", size1, size2);
      error++;
    }
EOF
      if ($name eq 'PROXY_LWPOLYLINE') {
        print $fh <<"EOF";
  if (size1 != size2) // TODO
    error--;
EOF
      }
    }
    for my $name (@object_names) {
      my $xname = $name;
      print $fh <<"EOF";
  size1 = sizeof (Dwg_Object_$xname);
  size2 = dwg_dynapi_fields_size (\"$name\");
  if (size1 != size2)
    {
      fprintf (stderr, "sizeof(Dwg_Object_$xname): %d != "
               "dwg_dynapi_fields_size (\\\"$name\\\"): %d\\n", size1, size2);
      error++;
    }
EOF
    }
    for my $name (@subclasses) {
      my $xname = $name;
      my $struct = "struct $name";
      if ($unions{$name}) {
        $struct = "union $name";
      }
      $xname =~ s/^_dwg_//;
      print $fh <<"EOF";
  size1 = sizeof ($struct);
  size2 = dwg_dynapi_fields_size (\"$xname\");
  if (size1 != size2)
    {
      fprintf (stderr, "sizeof($struct): %d != "
               "dwg_dynapi_fields_size (\\\"$xname\\\"): %d\\n", size1, size2);
      error++;
    }
EOF
    }
  }
}
close $in;
chmod 0444, $fh;
close $fh;

sub mv_if_not_same {
  my ($tmp, $orig) = @_;
  if (`cmp "$tmp" "$orig"`) {
    system("mv", "-f", $orig, "$orig.bak");
    system("mv", "-f", $tmp, $orig);
    warn "new $orig\n";
  } else {
    unlink $tmp;
    warn "keep $orig\n";
  }
}

# find DEBUGGING classes
my $classes_inc = "$srcdir/classes.inc";
my (%STABLE, %UNSTABLE, %DEBUGGING, %UNHANDLED, %FIXED, %STABLEVAR);
open $in, "<", $classes_inc or die "$classes_inc: $!";
while (<$in>) {
  if (/^\s*STABLE_CLASS(?:_DXF|_CPP|)\s*\(ACTION,\s+(.+?)[,\)]/) {
      $STABLE{$1}++;
  }
  elsif (/^\s*UNSTABLE_CLASS(?:_DXF|_CPP|)\s*\(ACTION,\s+(.+?)[,\)]/) {
      $UNSTABLE{$1}++;
  }
  elsif (/^\s*DEBUGGING_CLASS(?:_DXF|_CPP|)\s*\(ACTION,\s+(.+?)[,\)]/) {
      $DEBUGGING{$1}++;
  }
  elsif (/^\s*UNHANDLED_CLASS(?:_DXF|_CPP|)\s+\(ACTION,\s+(\S+?)[,\)]/) {
      $UNHANDLED{$1}++;
  }
}
delete $UNHANDLED{PROXY_LWPOLYLINE};
close $in;
for (sort keys %UNHANDLED) {
  push @unhandled_names, $_ if !exists $entity_names{$_} and !exists $object_names{$_};
}
# many stable/fixed names are not in classes.inc
for (@entity_names) {
  if (!$STABLE{$_} && !$UNSTABLE{$_} && !$DEBUGGING{$_} && !$UNHANDLED{$_}) {
    $FIXED{$_}++;
    $STABLE{$_}++;
  }
}
for (@object_names) {
  if (!$STABLE{$_} && !$UNSTABLE{$_} && !$DEBUGGING{$_} && !$UNHANDLED{$_}) {
    $FIXED{$_}++;
    $STABLE{$_}++;
  }
}
$STABLE{_3DSOLID}++;
$STABLE{_3DFACE}++;
# $UNSTABLE{_3DLINE}++;
delete $STABLE{'3DLINE'};
delete $FIXED{'3DLINE'};
$FIXED{_3DSOLID}++;
$FIXED{_3DFACE}++;
for (keys %STABLE) {
  $STABLEVAR{$_}++ unless $FIXED{$_};
}

sub stability {
  my $n = shift;
  return 'STABLE' if $STABLE{$n};
  return 'STABLE' if $FIXED{$n};
  return 'UNSTABLE' if $UNSTABLE{$n};
  return 'DEBUGGING' if $DEBUGGING{$n};
  return 'UNHANDLED' if $UNHANDLED{$n};
  die "no stability class for $n";
}

# ---------------------------------------------------------------
# The gperf hash
my $ofile = "$srcdir/objects.in";
chmod 0644, $ofile if -e $ofile;
open $inc, ">", "$ofile.tmp" or die "$ofile: $!";
print $inc <<'EOF';
%{ // -*- mode: c -*-
/*****************************************************************************/
/*  LibreDWG - free implementation of the DWG file format                    */
/*                                                                           */
/*  Copyright (C) 2020 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/>.    */
/*****************************************************************************/

/*
 * objects.c: define all our entity and object names as hashmap,
 *            generated via gperf from object.in,
 *            which is generated by gen-dynapi.pl
 *
 * written by Reini Urban
 */

#include <string.h>
#include <limits.h>
#include "config.h"
#include "dwg.h"
#include "common.h"
#include "classes.h"

// v3.1 changed len type from unsigned int to size_t (gperf d519d1a821511eaa22eae6d9019a548aea21e6)
static const struct _dwg_dxfname * in_word_set (const char *str, size_t len);

#define STABLE (unsigned)DWG_CLASS_STABLE
#define UNSTABLE (unsigned)DWG_CLASS_UNSTABLE
#define DEBUGGING (unsigned)DWG_CLASS_DEBUGGING
#define UNHANDLED (unsigned)DWG_CLASS_UNHANDLED

%}
%7bit
%language=ANSI-C
%struct-type
%readonly-tables
%pic

struct _dwg_dxfname {int name; const char *const dxfname; const Dwg_Object_Type type; const unsigned isent:1; const unsigned stability:4; };

GCC46_DIAG_IGNORE(-Wmissing-field-initializers)

%%
# Entities
EOF
$n = 28;
for my $name (@entity_names) {
  my $xname = $name =~ /^3/ ? "_$name" : $name; # 3DFACE, 3DSOLID
  #next if $name eq 'DIMENSION_';
  #next if $name eq 'PROXY_LWPOLYLINE';
  my $dxfname = dxfname($name);
  printf $inc "%-${n}s %-${n}s  DWG_TYPE_%s,\t1,\t%s\n", "\"$name\",", "\"$dxfname\",",
    $xname, stability($xname);
}
print $inc "# Objects\n";
$n = 35;
for my $name (sort @object_names) {
  my $dxfname = dxfname($name);
  printf $inc "%-${n}s %-${n}s  DWG_TYPE_%s,\t0,\t%s\n", "\"$name\",", "\"$dxfname\",",
    $name, stability($name);
}
print $inc <<'EOF';
%%

/* Find if an object name (our internal name, not anything used elsewhere)
   is defined, and return our fixed type, the public dxfname and if it's an entity. */
EXPORT int dwg_object_name (const char *const restrict name,
                            const char **restrict dxfname,
                            Dwg_Object_Type *restrict typep, int *restrict is_entp,
                            Dwg_Class_Stability *restrict stabilityp)
{
  const struct _dwg_dxfname* result;
  const size_t len = strlen (name);
  // only allow UPPERCASE 7-bit names
  if (strspn (name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_23") != len)
    return 0;
  result = in_word_set (name, len);
  if (result)
    {
      if (dxfname)
        *dxfname = result->dxfname;
      if (typep)
        *typep   = result->type;
      if (is_entp)
        *is_entp = result->isent;
      if (stabilityp)
        *stabilityp = (Dwg_Class_Stability)result->stability;
      return 1;
    }
  return 0;
}
GCC46_DIAG_RESTORE

/*
 * Local variables:
 *   c-file-style: "gnu"
 * End:
 * vim: expandtab shiftwidth=4 cinoptions='\:2=2' :
 */
EOF
close $inc;
mv_if_not_same ("$ofile.tmp", $ofile);
chmod 0444, $ofile;
 
sub out_classes {
  my ($fh, $names, $STABILITY, $tmpl) = @_;
  my $lname;
  for my $name (@$names) {
      if ($name =~ /^3/) {
        $name =~ s/^3/_3/;
      }
      if ($STABILITY->{$name}) {
        my $s = $tmpl;
        my $xname = $name;
        $xname =~ s/^3/_3/;
        $s =~ s/\$name/$name/g;
        $s =~ s/\$xname/$xname/g;
        if ($s =~ /\$lname/) {
          $lname = lc $name;
          # skip typedefs of
          if ($lname =~ /^(xline|vertex_mesh|vertex_pface|region|body)$/) {
            next;
          }
          $lname =~ s/dimension_/dim_/;
          $lname =~ s/lwpolyline/lwpline/;
          $lname =~ s/multileader/mleader/;
          $lname =~ s/vertex_pface_face/vert_pface_face/;
          $s =~ s/\$lname/$lname/;
        }
        $s =~ s/_dwg_entity__3DLINE/_dwg_entity_3DLINE/;
        print $fh $s;
      }
    }
}

# generate API's lists per stability
my $tmpl;
my $api_c = "$srcdir/dwg_api.c";
open $in, "<", $api_c or die "$api_c: $!";
open my $out, ">", "$api_c.tmp" or die "$api_c.tmp: $!";
my $gen = 0;
while (<$in>) {
  if (m/^\/\* Start auto-generated/) {
    print $out $_;

    $tmpl = "dwg_get_OBJECT (ent_\$lname, \$name)\n";
    # out_classes ($out, \@entity_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@entity_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "#  ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "// ".$tmpl);
    print $out "#  endif\n\n";

    $tmpl = "dwg_get_OBJECT (obj_\$lname, \$name)\n";
    out_classes ($out, \@object_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@object_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#  ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@object_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "// ".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "// ".$tmpl);
    print $out "#  endif\n";

    print $out <<'EOF';

/********************************************************************
 * Functions to return NULL-terminated array of all owned entities  *
 ********************************************************************/

/**
 * \fn Dwg_Entity_ENTITY* dwg_getall_ENTITY(Dwg_Object_Ref *hdr)
 * \code
 * Usage:
 * Dwg_Entity_TEXT* texts = dwg_getall_TEXT (text,
 *                                           dwg->header_vars.mspace_block);
 * \endcode
 * \param[in]    hdr Dwg_Object_Ref     to a BLOCK_CONTROL obj
 * \return       malloced NULL-terminated array
 *
 * Extracts all entities of this type from a block header (mspace or pspace),
 * and returns a malloced NULL-terminated array.
 */
//< \fn Dwg_Entity_TEXT* dwg_getall_TEXT (Dwg_Object_Ref *hdr)
EOF

    $tmpl = "DWG_GETALL_ENTITY (\$name)\n";
    out_classes ($out, \@entity_names, \%STABLE, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "/* debugging */\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "// ".$tmpl);

    print $out <<'EOF';

/********************************************************************
 *     Functions to return NULL-terminated array of all objects     *
 ********************************************************************/

/**
 * \fn Dwg_Object_OBJECT dwg_getall_OBJECT(Dwg_Data *dwg)
 * Extracts all objects of this type from a dwg, and returns a malloced
 * NULL-terminated array.
 */

EOF

    $tmpl = "DWG_GETALL_OBJECT (\$name)\n";
    out_classes ($out, \@object_names, \%STABLE, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#  ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@object_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "// ".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "// ".$tmpl);
    print $out "#  endif\n";

    print $out <<'EOF';

/*******************************************************************
 *     Functions created from macro to cast dwg_object to entity     *
 *                 Usage :- dwg_object_to_ENTITY(),                  *
 *                where ENTITY can be LINE or CIRCLE                 *
 ********************************************************************/

/**
 * \fn Dwg_Entity_ENTITY *dwg_object_to_ENTITY(Dwg_Object *obj)
 * cast a Dwg_Object to Entity
 */
/* fixed <500 */
EOF

    $tmpl = "CAST_DWG_OBJECT_TO_ENTITY (\$name)\n";
    out_classes ($out, \@entity_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    $tmpl = "CAST_DWG_OBJECT_TO_ENTITY_BYNAME (\$name)\n";
    out_classes ($out, \@entity_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "#  ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "  // ".$tmpl);
    print $out "#  endif\n";

    print $out <<'EOF';

/*******************************************************************
 *     Functions created from macro to cast dwg object to object     *
 *                 Usage :- dwg_object_to_OBJECT(),                  *
 *            where OBJECT can be LAYER or BLOCK_HEADER              *
 ********************************************************************/
/**
 * \fn Dwg_Object_OBJECT *dwg_object_to_OBJECT(Dwg_Object *obj)
 * cast a Dwg_Object to Object
 */
EOF

    $tmpl = "CAST_DWG_OBJECT_TO_OBJECT (\$name)\n";
    out_classes ($out, \@object_names, \%STABLE, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#  ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@object_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "  // ".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "  // ".$tmpl);
    print $out "#  endif\n";
    print $out "// clang-format: on\n";
    print $out "/* End auto-generated content */\n";
    $gen = 1;
  }
  if (!$gen) {
    print $out $_;
  }
  if (m/^\/\* End auto-generated/) {
    $gen = 0;
  }
}
close $in;
close $out;
mv_if_not_same ("$api_c.tmp", $api_c);

my $api_h = "$topdir/include/dwg_api.h";
open $in, "<", $api_h or die "$api_h: $!";
open $out, ">", "$api_h.tmp" or die "$api_h.tmp: $!";
$gen = 0;
while (<$in>) {
  if (m/^\/\* Start auto-generated/) {
    print $out $_;

    $tmpl = "typedef struct _dwg_entity_\$name\t\tdwg_ent_\$lname;\n";
    out_classes ($out, \@entity_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@entity_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "/* debugging */\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "// ".$tmpl);

    $tmpl = "typedef struct _dwg_object_\$name\t\tdwg_obj_\$lname;\n";
    out_classes ($out, \@object_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    # without UNDERLAYDEFINITION
    my %STABLEVAR1 = %STABLEVAR;
    delete %STABLEVAR1{qw(PDFDEFINITION DGNDEFINITION DWFDEFINITION)};
    out_classes ($out, \@object_names, \%STABLEVAR1, $tmpl);
    my %STABLEVAR2 = map {$_ => 1} qw(PDFDEFINITION DGNDEFINITION DWFDEFINITION);
    $tmpl = "typedef struct _dwg_abstractobject_UNDERLAYDEFINITION\t\tdwg_obj_\$lname;\n";
    out_classes ($out, \@object_names, \%STABLEVAR2, $tmpl);
    $tmpl = "typedef struct _dwg_object_\$name\t\tdwg_obj_\$lname;\n";

    print $out "/* unstable */\n";
    # without ASSOCARRAYPARAMETERS
    my %UNSTABLE1 = %UNSTABLE;
    delete %UNSTABLE1{qw(ASSOCARRAYMODIFYPARAMETERS ASSOCARRAYPATHPARAMETERS
                         ASSOCARRAYPOLARPARAMETERS ASSOCARRAYRECTANGULARPARAMETERS)};
    out_classes ($out, \@object_names, \%UNSTABLE1, $tmpl);
    my %UNSTABLE2 = map {$_ => 1} qw(ASSOCARRAYMODIFYPARAMETERS ASSOCARRAYPATHPARAMETERS
                                       ASSOCARRAYPOLARPARAMETERS ASSOCARRAYRECTANGULARPARAMETERS);
    $tmpl = "typedef struct _dwg_abstractobject_ASSOCARRAYPARAMETERS\t\tdwg_obj_\$lname;\n";
    out_classes ($out, \@object_names, \%UNSTABLE2, $tmpl);
    #out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "/* debugging */\n";
    $tmpl = "typedef struct _dwg_object_\$name\t\tdwg_obj_\$lname;\n";
    out_classes ($out, \@object_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "// ".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "// ".$tmpl);
    print $out "\n\n";

    $tmpl = "dwg_get_OBJECT_DECL (ent_\$lname, \$name);\n";
    out_classes ($out, \@entity_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@entity_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "// ".$tmpl);
    print $out "#endif\n\n";

    $tmpl = "dwg_get_OBJECT_DECL (obj_\$lname, \$name);\n";
    out_classes ($out, \@object_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@object_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@object_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "// ".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "// ".$tmpl);
    print $out "#endif\n";

    print $out <<'EOF';

/********************************************************************
 * Functions to return NULL-terminated array of all owned entities  *
 ********************************************************************/

/// extract all owned entities from a block header (mspace or pspace)
EOF

    $tmpl = "DWG_GETALL_ENTITY_DECL (\$name);\n";
    out_classes ($out, \@entity_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@entity_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "/* debugging */\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "// ".$tmpl);

    print $out <<'EOF';

/********************************************************************
 *     Functions to return NULL-terminated array of all objects     *
 ********************************************************************/

/**
 * \fn Dwg_Object_OBJECT dwg_getall_OBJECT(Dwg_Data *dwg)
 * Extracts all objects of this type from a dwg, and returns a malloced
 * NULL-terminated array.
 */

EOF

    $tmpl = "DWG_GETALL_OBJECT_DECL (\$name);\n";
    out_classes ($out, \@object_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@object_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@object_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "// ".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "// ".$tmpl);
    print $out "#endif\n";

    print $out <<'EOF';

/*******************************************************************
 *     Functions created from macro to cast dwg_object to entity     *
 *                 Usage :- dwg_object_to_ENTITY(),                  *
 *                where ENTITY can be LINE or CIRCLE                 *
 ********************************************************************/

/**
 * \fn Dwg_Entity_ENTITY *dwg_object_to_ENTITY(Dwg_Object *obj)
 * cast a Dwg_Object to Entity
 */
/* fixed <500 */
EOF

    $tmpl = "CAST_DWG_OBJECT_TO_ENTITY_DECL (\$name);\n";
    out_classes ($out, \@entity_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    $tmpl = "CAST_DWG_OBJECT_TO_ENTITY_BYNAME_DECL (\$name);\n";
    out_classes ($out, \@entity_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "  // ".$tmpl);
    print $out "#endif\n";

    print $out <<'EOF';

/*******************************************************************
 *     Functions created from macro to cast dwg object to object     *
 *                 Usage :- dwg_object_to_OBJECT(),                  *
 *            where OBJECT can be LAYER or BLOCK_HEADER              *
 ********************************************************************/
/**
 * \fn Dwg_Object_OBJECT *dwg_object_to_OBJECT(Dwg_Object *obj)
 * cast a Dwg_Object to Object
 */
EOF

    $tmpl = "CAST_DWG_OBJECT_TO_OBJECT_DECL (\$name);\n";
    out_classes ($out, \@object_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@object_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@object_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "  // ".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "  // ".$tmpl);
    print $out "#endif\n";

    print $out "/* End auto-generated content */\n";
    $gen = 1;
  }
  if (!$gen) {
    print $out $_;
  }
  if (m/^\/\* End auto-generated/) {
    $gen = 0;
  }
}
close $in;
close $out;
mv_if_not_same ("$api_h.tmp", $api_h);
 
my $dwg_h = "$topdir/include/dwg.h";
open $in, "<", $dwg_h or die "$dwg_h: $!";
open $out, ">", "$dwg_h.tmp" or die "$dwg_h.tmp: $!";
$gen = 0;
my $enum = 0;
my (@VARTYPES, %VARTYPES);
while (<$in>) {
  # generated sorted DWG_TYPE_ enum as array and hash.
  # from PROXY_OBJECT to FREED
  if ($enum and /^\s+DWG_TYPE_([^\t ,]+)/) {
    my $n = $1;
    if ($n =~ /^FREED\s?/) {
      $enum = 0;
      print $out $_; # because of the next shortcut
      next;
    }
    # $n =~ s/^_3/3/;
    push @VARTYPES, $n;
    $VARTYPES{$n} = $enum++;
  }
  if (/^\s+DWG_TYPE_PROXY_OBJECT = 0x1f3/) {
    $enum = 500;
  }
  if (/^} Dwg_Object_Type/) {
    $enum = 0;
  }
  if (m/    \/\* Start auto-generated entity-union/) {
    print $out $_;
    $tmpl =    "    Dwg_Entity_\$name *\$name;\n";
    out_classes ($out, \@entity_names, \%FIXED, $tmpl);
    print $out "    /* untyped > 500 */\n";
    out_classes ($out, \@entity_names, \%STABLEVAR, $tmpl);
    print $out "    /* unstable */\n";
    $tmpl =    "    Dwg_Entity_\$name *\$xname;\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "    /* debugging */\n";
    #print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "//".$tmpl);
    #print $out "#endif\n";
    print $out "    /* End auto-generated entity-union */\n";
    $gen = 1;
  }
  elsif (m/    \/\* Start auto-generated object-union/) {
    print $out $_;
    $tmpl =    "    Dwg_Object_\$name *\$name;\n";
    out_classes ($out, \@object_names, \%FIXED, $tmpl);
    print $out "    /* untyped > 500 */\n";
    out_classes ($out, \@object_names, \%STABLEVAR, $tmpl);
    print $out "    /* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "    /* debugging */\n";
    out_classes ($out, \@object_names, \%DEBUGGING, $tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "//".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "//".$tmpl);
    print $out "    /* End auto-generated object-union */\n";
    $gen = 1;
  }
  elsif (m/^\/\* Start auto-generated content/) {
    print $out $_;

    $tmpl = "EXPORT int dwg_setup_\$name (Dwg_Object *obj);\n";
    out_classes ($out, \@entity_names, \%FIXED, $tmpl);
    out_classes ($out, \@object_names, \%FIXED, $tmpl);
    print $out "/* untyped > 500 */\n";
    out_classes ($out, \@entity_names, \%STABLEVAR, $tmpl);
    out_classes ($out, \@object_names, \%STABLEVAR, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@object_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "  //".$tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "  //".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "  //".$tmpl);
    print $out "#endif\n";

    print $out "/* End auto-generated content */\n";
    $gen = 1;
  }
  if (!$gen) {
    print $out $_;
  }
  if (m/^\s*\/\* End auto-generated/) {
    $gen = 0;
  }
}
close $in;
close $out;
mv_if_not_same ("$dwg_h.tmp", $dwg_h);

if (0) {
my $free_h = "$topdir/src/free.h";
open $in, "<", $free_h or die "$free_h: $!";
open $out, ">", "$free_h.tmp" or die "$free_h.tmp: $!";
$gen = 0;
while (<$in>) {
  if (m/^\/\* Start auto-generated content/) {
    print $out $_;

    $tmpl = "int dwg_free_\$name (Bit_Chain *restrict dat, Dwg_Object *restrict obj);\n" .
      "int dwg_free_\$name_private (Bit_Chain *dat, Bit_Chain *hdl_dat, Bit_Chain *str_dat, Dwg_Object *restrict obj);\n";
    out_classes ($out, \@entity_names, \%STABLE, $tmpl);
    out_classes ($out, \@object_names, \%STABLE, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "/* DEBUG_CLASSES */\n";
    my $dbgtmpl = $tmpl;
    $dbgtmpl =~ s/^int dwg_free/  int dwg_free/gmaa;
    out_classes ($out, \@entity_names, \%DEBUGGING, $dbgtmpl);
    out_classes ($out, \@object_names, \%DEBUGGING, $dbgtmpl);
    my $unhtmpl = $tmpl;
    $unhtmpl =~ s{^int dwg_free}{//  int dwg_free}gmaa;
    out_classes ($out, \@entity_names, \%UNHANDLED, $unhtmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, $unhtmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, $unhtmpl);
    print $out "/* End auto-generated content */\n";
    $gen = 1;
  }
  if (!$gen) {
    print $out $_;
  }
  if (m/^\s*\/\* End auto-generated/) {
    $gen = 0;
  }
}
close $in;
close $out;
mv_if_not_same ("$free_h.tmp", $free_h);
}

if (1) {
  my $file = "$topdir/src/classes.c";
  open $in, "<", $file or die "$file: $!";
  open $out, ">", "$file.tmp" or die "$file.tmp: $!";
  $gen = 0;
  while (<$in>) {
    if (m/^\s+\/\* Start auto-generated variable/) {
      print $out $_;
      my $e = 500;
      for (@VARTYPES) {
        printf $out "  %-37s /* %d */\n", "\"$_\",", $e++;
      }
      print $out "  /* End auto-generated variable */\n";
      $gen = 1;
    }
    if (m/^\s+\/\* Start auto-generated dxfnames/) {
      print $out $_;
      my $e = 500;
      for (@VARTYPES) {
        my $dxfname = dxfname $_;
        printf $out "  %-45s /* %d */\n", "\"$dxfname\",", $e++;
      }
      print $out "  /* End auto-generated dxfnames */\n";
      $gen = 1;
    }
    if (!$gen) {
      print $out $_;
    }
    if (m/^\s*\/\* End auto-generated/) {
      $gen = 0;
    }
  }
  close $in;
  close $out;
  mv_if_not_same ("$file.tmp", $file);
}
 
my $done = 0;
my $ifile = "$topdir/bindings/dwg.i";
open $in, "<", $ifile or die "$ifile: $!";
open $out, ">", "$ifile.tmp" or die "$ifile.tmp: $!";
while (<$in>) {
  if (m/^\/\* Start auto-generated/) {
    print $out $_;
    print $out "/* dwg_getall_ API */\n";
    $tmpl = "EXPORT Dwg_Entity_\$name** dwg_getall_\$name (Dwg_Object_Ref* hdr);\n";
    out_classes ($out, \@entity_names, \%STABLE, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "  //".$tmpl);
    print $out "#endif\n";
    print $out "\n";

    $tmpl = "EXPORT Dwg_Object_\$name** dwg_getall_\$name (Dwg_Data* dwg);\n";
    out_classes ($out, \@object_names, \%STABLE, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@object_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "  //".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "  //".$tmpl);
    print $out "#endif\n";

    print $out "\n/* dwg_object_to_ API */\n";
    $tmpl = "EXPORT Dwg_Entity_\$name* dwg_object_to_\$name (Dwg_Object* obj);\n";
    out_classes ($out, \@entity_names, \%STABLE, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@entity_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@entity_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@entity_names, \%UNHANDLED, "  //".$tmpl);
    print $out "#endif\n";

    $tmpl = "EXPORT Dwg_Object_\$name* dwg_object_to_\$name (Dwg_Object* obj);\n";
    out_classes ($out, \@object_names, \%STABLE, $tmpl);
    print $out "/* unstable */\n";
    out_classes ($out, \@object_names, \%UNSTABLE, $tmpl);
    print $out "#ifdef DEBUG_CLASSES\n";
    out_classes ($out, \@object_names, \%DEBUGGING, "  ".$tmpl);
    out_classes ($out, \@object_names, \%UNHANDLED, "  //".$tmpl);
    out_classes ($out, \@unhandled_names, \%UNHANDLED, "  //".$tmpl);
    print $out "#endif\n";
    print $out "/* End auto-generated content */\n";
    close $out;
    $done++;
    last;
  }
  if (!$done) {
    print $out $_;
  }
}
close $in;
close $out;
mv_if_not_same ("$ifile.tmp", $ifile);

# NOTE: in the 2 #line's below use __LINE__ + 1
__DATA__
/* ex: set ro ft=c: -*- mode: c; buffer-read-only: t -*- */
#line 2512 "gen-dynapi.pl"
/*****************************************************************************/
/*  LibreDWG - free implementation of the DWG file format                    */
/*                                                                           */
/*  Copyright (C) 2018-2024 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/>.    */
/*****************************************************************************/

/*
 * dynapi.c: dynamic access to all object and field names and types
 * written by Reini Urban
 * generated by src/gen-dynapi.pl from include/dwg.h, do not modify.
 */

#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "common.h"
#include "dynapi.h"
#define DWG_LOGLEVEL loglevel
#include "logging.h"
#include "decode.h"
#include "dwg.h"
#include "bits.h"

#ifndef _DWG_API_H_
Dwg_Object *dwg_obj_generic_to_object (const void *restrict obj,
                                       int *restrict error);
#endif

@@struct _dwg_header_variables@@
@@for dwg_entity_ENTITY@@
@@for dwg_object_OBJECT@@
@@for dwg_subclasses@@

/* common fields: */
@@struct _dwg_object_entity@@
@@struct _dwg_object_object@@

/* FIXME: Remove name. Get type via dwg_object_name() */
struct _name_type_fields {
  const char *const name;
  const enum DWG_OBJECT_TYPE type;
  const Dwg_DYNAPI_field *const fields;
  const int size;
};

struct _name_subclass_fields {
  const char *const name;
  const int type;
  const char *const subclass;
  const Dwg_DYNAPI_field *const fields;
  const int size;
};

/* Generated fields for all the objects, sorted for bsearch. from enum DWG_OBJECT_TYPE.
   FIXME: Replace name by type. Get type via dwg_object_name().
   Make it an array of type for O(1) lookup.
 */
static const struct _name_type_fields dwg_name_types[] = {
@@enum DWG_OBJECT_TYPE@@
};

/* Generated fields for all the subclasses, sorted for bsearch */
static const struct _name_subclass_fields dwg_list_subclasses[] = {
@@list subclasses@@
};

struct _name_subclasses {
  const char *const name;
  const char *const subclasses[@@scalar max_subclasses@@];
};

/* List of all allowed subclasses per class. sorted for bsearch. */
static const struct _name_subclasses dwg_name_subclasses[] = {
@@list name_subclasses@@
};

#line 2596 "gen-dynapi.pl"
struct _name
{
  const char *const name;
};

static int
_name_struct_cmp (const void *restrict key, const void *restrict elem)
{
  //https://en.cppreference.com/w/c/algorithm/bsearch
  const struct _name *f = (struct _name *)elem;
  return strcmp ((const char *)key, f->name); //deref
}

#define NUM_NAME_TYPES  ARRAY_SIZE(dwg_name_types)
#define NUM_SUBCLASSES  ARRAY_SIZE(dwg_list_subclasses)

static
const struct _name_type_fields*
 __nonnull ((1))
// FIXME: use type arg only
_find_entity (const char *name)
{
  const char *p = (const char *)bsearch (name, dwg_name_types, NUM_NAME_TYPES,
                           sizeof (dwg_name_types[0]),
                           _name_struct_cmp);
  if (p)
    {
      const ptrdiff_t i = (p - (char *)dwg_name_types) / sizeof (dwg_name_types[0]);
      return &dwg_name_types[i];
    }
  else
    return NULL;
}

static
const struct _name_subclass_fields*
 __nonnull ((1))
_find_subclass (const char *name)
{
  const char *p = (const char *)bsearch (name, dwg_list_subclasses, NUM_SUBCLASSES,
                           sizeof (dwg_list_subclasses[0]),
                           _name_struct_cmp);
  if (p)
    {
      const ptrdiff_t i = (p - (char *)dwg_list_subclasses) / sizeof (dwg_list_subclasses[0]);
      return &dwg_list_subclasses[i];
    }
  else
    return NULL;
}

EXPORT bool
is_dwg_entity (const char *name)
{
  int isent;
  return dwg_object_name (name, NULL, NULL, &isent, NULL)
         && isent;
}

EXPORT bool
is_dwg_object (const char *name)
{
  int isent;
  return dwg_object_name (name, NULL, NULL, &isent, NULL)
         && !isent;
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_entity_fields (const char *name)
{
  const struct _name_type_fields *f = _find_entity (name);
  return f ? f->fields : NULL;
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_subclass_fields (const char *restrict name)
{
  const struct _name_subclass_fields *f = _find_subclass (name);
  return f ? f->fields : NULL;
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_common_entity_fields (void)
{
  return _dwg_object_entity_fields;
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_common_object_fields (void)
{
  return _dwg_object_object_fields;
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_entity_field (const char *restrict name, const char *restrict field)
{
  const Dwg_DYNAPI_field *fields = dwg_dynapi_entity_fields (name);
  if (fields)
    { /* linear search (unsorted) */
      Dwg_DYNAPI_field *f = (Dwg_DYNAPI_field *)fields;
      for (; f->name; f++)
        {
          if (strEQ (f->name, field))
            return f;
        }
    }
  return NULL;
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_subclass_field (const char *restrict name, const char *restrict field)
{
  const Dwg_DYNAPI_field *fields = dwg_dynapi_subclass_fields (name);
  if (fields)
    { /* linear search (unsorted) */
      Dwg_DYNAPI_field *f = (Dwg_DYNAPI_field *)fields;
      for (; f->name; f++)
        {
          if (strEQ (f->name, field))
            return f;
        }
    }
  return NULL;
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_header_field (const char *restrict fieldname)
{
  return (Dwg_DYNAPI_field *)bsearch (
              fieldname, _dwg_header_variables_fields,
              ARRAY_SIZE (_dwg_header_variables_fields) - 1, /* NULL terminated */
              sizeof (_dwg_header_variables_fields[0]), _name_struct_cmp);
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_common_entity_field (const char *restrict fieldname)
{
  return (Dwg_DYNAPI_field *)bsearch (
              fieldname, _dwg_object_entity_fields,
              ARRAY_SIZE (_dwg_object_entity_fields) - 1, /* NULL terminated */
              sizeof (_dwg_object_entity_fields[0]), _name_struct_cmp);
}

EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_common_object_field (const char *restrict fieldname)
{
  return (Dwg_DYNAPI_field *)bsearch (
              fieldname, _dwg_object_object_fields,
              ARRAY_SIZE (_dwg_object_object_fields) - 1, /* NULL terminated */
              sizeof (_dwg_object_object_fields[0]), _name_struct_cmp);
}

// search field by dxf
EXPORT const Dwg_DYNAPI_field *
dwg_dynapi_field_dxf (const Dwg_DYNAPI_field *restrict fields, const int dxf, int *restrict unique)
{
  const Dwg_DYNAPI_field *retval = NULL;
  if (fields)
    { /* linear search (unsorted) */
      Dwg_DYNAPI_field *f = (Dwg_DYNAPI_field *)fields;
      *unique = 1;
      for (; f->name; f++)
        {
          if (f->dxf == dxf)
            {
              if (retval)
                unique = 0;
              else
                retval = f;
            }
        }
    }
  return retval;
}

EXPORT int
dwg_dynapi_entity_size (const char *restrict name)
{
  const struct _name_type_fields *f = _find_entity (name);
  return f ? f->size : 0;
}

EXPORT int
dwg_dynapi_subclass_size (const char *restrict name)
{
  const struct _name_subclass_fields *f = _find_subclass (name);
  return f ? f->size : 0;
}

/* generic field getters */
EXPORT bool
dwg_dynapi_entity_value (void *restrict _obj, const char *restrict name,
                         const char *restrict fieldname,
                         void *restrict out, Dwg_DYNAPI_field *restrict fp)
{
#ifndef HAVE_NONNULL
  if (!_obj || !name || !fieldname || !out)
    return false;
#endif
  {
    int error;
    const Dwg_Object* obj = dwg_obj_generic_to_object (_obj, &error);
    // Here we need to ignore errors, because we allow subentities via
    // CHK_SUBCLASS_* e.g. layout->plotsetting via PLOTSETTING
    if (obj && strNE (obj->name, name)) // objid may be 0
      {
        const int loglevel = obj->parent->opts & DWG_OPTS_LOGLEVEL;
        LOG_ERROR ("%s: Invalid entity type %s, wanted %s", __FUNCTION__,
                   obj->name, name);
        return false;
      }
    {
      const Dwg_DYNAPI_field *f = dwg_dynapi_entity_field (name, fieldname);
      if (!f)
        {
          int loglevel = (obj && obj->parent) ? obj->parent->opts & DWG_OPTS_LOGLEVEL
                                              : DWG_LOGLEVEL_ERROR;
          LOG_ERROR ("%s: Invalid %s field %s", __FUNCTION__, name, fieldname);
          return false;
        }
      if (fp)
        memcpy (fp, f, sizeof (Dwg_DYNAPI_field));
      memcpy (out, &((char *)_obj)[f->offset], f->is_malloc ? sizeof(char*) : f->size);
      return true;
    }
  }
}

EXPORT bool
dwg_dynapi_entity_utf8text (void *restrict _obj, const char *restrict name,
                            const char *restrict fieldname,
                            char **restrict out, int *isnew,
                            Dwg_DYNAPI_field *restrict fp)
{
  if (isnew)
    *isnew = 0;
#ifndef HAVE_NONNULL
  if (!_obj || !name || !fieldname || !out)
    return false;
#endif
  {
    int error;
    const Dwg_Object* obj = dwg_obj_generic_to_object (_obj, &error);
    // Here we need to ignore errors, because we allow subentities via
    // CHK_SUBCLASS_* e.g. layout->plotsetting via PLOTSETTING
    if (obj && strNE (obj->name, name)) // objid may be 0
      {
        const int loglevel = obj->parent->opts & DWG_OPTS_LOGLEVEL;
        LOG_ERROR ("%s: Invalid entity type %s, wanted %s", __FUNCTION__,
                   obj->name, name);
        return false;
      }
    {
      const Dwg_DYNAPI_field *f = dwg_dynapi_entity_field (name, fieldname);
      const Dwg_Data *dwg = obj ? obj->parent : NULL;
      const bool is_tu = dwg ? IS_FROM_TU_DWG (dwg) : false;

      if (!f || !f->is_string)
        {
          int loglevel = dwg ? dwg->opts & DWG_OPTS_LOGLEVEL : DWG_LOGLEVEL_ERROR;
          LOG_ERROR ("%s: Invalid %s text field %s", __FUNCTION__, name, fieldname);
          return false;
        }
      if (fp)
        memcpy (fp, f, sizeof (Dwg_DYNAPI_field));

      if (is_tu && strNE (f->type, "TF")) /* not TF */
        {
          BITCODE_TU wstr = *(BITCODE_TU*)((char*)_obj + f->offset);
          char *utf8 = bit_convert_TU (wstr);
          if (wstr && !utf8) // some conversion error, invalid wchar (nyi)
            return false;
          *out = utf8;
          if (isnew)
            *isnew = 1;
        }
      else
        {
          char *utf8 = *(char **)((char*)_obj + f->offset);
          *out = utf8;
        }

      return true;
    }
  }
}

EXPORT bool
dwg_dynapi_header_value (const Dwg_Data *restrict dwg,
                         const char *restrict fieldname, void *restrict out,
                         Dwg_DYNAPI_field *restrict fp)
{
#ifndef HAVE_NONNULL
  if (!dwg || !fieldname || !out)
    return false;
#endif
  {
    const Dwg_DYNAPI_field *f = dwg_dynapi_header_field (fieldname);
    if (f)
      {
        const Dwg_Header_Variables *const _obj = &dwg->header_vars;
        if (fp)
          memcpy (fp, f, sizeof (Dwg_DYNAPI_field));
        memcpy (out, &((char*)_obj)[f->offset], f->size);
        return true;
      }
    else
      {
        const int loglevel = dwg->opts & DWG_OPTS_LOGLEVEL;
        LOG_ERROR ("%s: Invalid header field %s", __FUNCTION__, fieldname);
        return false;
      }
  }
}

EXPORT bool
dwg_dynapi_header_utf8text (const Dwg_Data *restrict dwg,
                            const char *restrict fieldname,
                            char **restrict out, int *isnew,
                            Dwg_DYNAPI_field *restrict fp)
{
  if (isnew)
    *isnew = 0;
#ifndef HAVE_NONNULL
  if (!dwg || !fieldname || !out)
    return false;
#endif
  {
    const Dwg_DYNAPI_field *f = dwg_dynapi_header_field (fieldname);
    if (f && f->is_string)
      {
        const Dwg_Header_Variables *const _obj = &dwg->header_vars;
        const bool is_tu = IS_FROM_TU_DWG (dwg);
        const bool is_fixed = strEQc (f->type, "TF");

        if (fp)
          memcpy (fp, f, sizeof (Dwg_DYNAPI_field));

        if (is_tu && !is_fixed) /* not TF */
          {
            BITCODE_TU wstr = *(BITCODE_TU*)((char*)_obj + f->offset);
            char *utf8 = bit_convert_TU (wstr);
            if (wstr && !utf8) // some conversion error, invalid wchar (nyi)
              return false;
            *out = utf8;
            if (isnew)
              *isnew = 1;
          }
        else
          {
            char *utf8 = *(char **)((char*)_obj + f->offset);
            *out = utf8;
          }

        return true;
      }
    else
      {
        const int loglevel = dwg->opts & DWG_OPTS_LOGLEVEL;
        LOG_ERROR ("%s: Invalid header text field %s", __FUNCTION__, fieldname);
        return false;
      }
  }
}

EXPORT bool
dwg_dynapi_common_value(void *restrict _obj, const char *restrict fieldname,
                        void *restrict out, Dwg_DYNAPI_field *restrict fp)
{
#ifndef HAVE_NONNULL
  if (!_obj || !fieldname || !out)
    return false;
#endif
  {
    const Dwg_DYNAPI_field *f;
    int error;
    const Dwg_Object *obj = dwg_obj_generic_to_object (_obj, &error);
    if (!obj || error)
      {
        const int loglevel = DWG_LOGLEVEL_ERROR;
        LOG_ERROR ("%s: dwg_obj_generic_to_object failed", __FUNCTION__);
        return false;
      }

    if (obj->supertype == DWG_SUPERTYPE_ENTITY)
      {
        f = dwg_dynapi_common_entity_field (fieldname);
        _obj = obj->tio.entity;
      }
    else if (obj->supertype == DWG_SUPERTYPE_OBJECT)
      {
        f = dwg_dynapi_common_object_field (fieldname);
        _obj = obj->tio.object;
      }
    else
      {
        const int loglevel = obj->parent->opts & DWG_OPTS_LOGLEVEL; // DWG_LOGLEVEL_ERROR;
        LOG_ERROR ("%s: Unhandled %s.supertype ", __FUNCTION__, obj->name);
        return false;
      }

    if (f)
      {
        int size = f->size;
        if (fp)
          memcpy (fp, f, sizeof(Dwg_DYNAPI_field));
        if (f->dxf == 160 && strEQc (fieldname, "preview_size")
            && obj->parent->header.version < R_2010)
          size = 4;
        memcpy (out, &((char *)_obj)[f->offset], size);
        return true;
      }
    else
      {
        const int loglevel = obj->parent->opts & DWG_OPTS_LOGLEVEL;
        LOG_ERROR ("%s: Invalid common field %s", __FUNCTION__, fieldname);
        return false;
      }
  }
}

EXPORT bool
dwg_dynapi_common_utf8text(void *restrict _obj, const char *restrict fieldname,
                           char **restrict out, int *isnew, Dwg_DYNAPI_field *restrict fp)
{
  if (isnew)
    *isnew = 0;
#ifndef HAVE_NONNULL
  if (!_obj || !fieldname || !out)
    return false;
#endif
  {
    Dwg_DYNAPI_field *f;
    int error;
    const Dwg_Object *obj = dwg_obj_generic_to_object (_obj, &error);
    Dwg_Data *dwg = NULL;

    if (!obj || error)
      {
        const int loglevel = DWG_LOGLEVEL_ERROR;
        LOG_ERROR ("%s: dwg_obj_generic_to_object failed", __FUNCTION__);
        return false;
      }
    if (obj->supertype == DWG_SUPERTYPE_ENTITY)
      {
        dwg = obj ? obj->parent : ((Dwg_Entity_UNKNOWN_ENT *)_obj)->parent->dwg;
        _obj = obj->tio.entity;
        f = (Dwg_DYNAPI_field *)bsearch (
            fieldname, _dwg_object_entity_fields,
            ARRAY_SIZE (_dwg_object_entity_fields) - 1, /* NULL terminated */
            sizeof (_dwg_object_entity_fields[0]), _name_struct_cmp);
      }
    else if (obj->supertype == DWG_SUPERTYPE_OBJECT)
      {
        dwg = obj ? obj->parent : ((Dwg_Object_UNKNOWN_OBJ *)_obj)->parent->dwg;
        _obj = obj->tio.object;
        f = (Dwg_DYNAPI_field *)bsearch (
            fieldname, _dwg_object_object_fields,
            ARRAY_SIZE (_dwg_object_object_fields) - 1, /* NULL terminated */
            sizeof (_dwg_object_object_fields[0]), _name_struct_cmp);
      }
    else
      {
        const int loglevel = DWG_LOGLEVEL_ERROR;
        LOG_ERROR ("%s: Unhandled %s.supertype ", __FUNCTION__, obj->name);
        return false;
      }

    if (f && f->is_string)
      {
        const bool is_tu = IS_FROM_TU_DWG (dwg);

        if (fp)
          memcpy (fp, f, sizeof(Dwg_DYNAPI_field));

        if (is_tu && strNE (f->type, "TF")) /* not TF */
          {
            BITCODE_TU wstr = *(BITCODE_TU*)((char*)_obj + f->offset);
            char *utf8 = bit_convert_TU (wstr);
            if (wstr && !utf8) // some conversion error, invalid wchar (nyi)
              return false;
            *out = utf8;
            if (isnew)
              *isnew = 1;
          }
        else
          {
            char *utf8 = *(char **)((char*)_obj + f->offset);
            *out = utf8;
          }

        return true;
      }
    else
      {
        const int loglevel = dwg ? dwg->opts & DWG_OPTS_LOGLEVEL : DWG_LOGLEVEL_ERROR;
        LOG_ERROR ("%s: Invalid common text field %s", __FUNCTION__, fieldname);
        return false;
      }
  }
}

// create a fresh string
static void
dynapi_set_helper (void *restrict old, const Dwg_DYNAPI_field *restrict f,
                   const Dwg_Version_Type dwg_version,
                   const void *restrict value, const bool is_utf8)
{
  // TODO: sanity checks. is_malloc (TF), copy zero's (TFv)
  // if text strcpy or wcscpy, or do utf8 conversion.
  //if ((char*)old && f->is_malloc)
  //  free (old);
  if (f->is_malloc)
    {
      // NULL ptr
      if (!*(char**)value)
        memcpy (old, value, sizeof (char*));
      // fixed length (but not yet TF)
      else if (strEQc (f->type, "TFv"))
        {
          char *str = (char *)calloc (f->size + 1, 1);
          strncpy (str, *(char**)value, f->size);
          // we copy just the pointer to heap-alloced str, not the string
          free (*(char **)old);
          memcpy (old, &str, sizeof (char*)); // size of ptr
        }
      // ascii
      else if (f->is_string && dwg_version < R_2007)
        {
          // FIXME: TF size calc is probably wrong
          size_t len = strlen (*(char**)value);
          char *str = (char *)malloc (len + 1);
          memcpy (str, *(char**)value, len + 1);
          // we copy just the pointer, not the string
          free (*(char **)old);
          memcpy (old, &str, sizeof (char*)); // size of ptr
        }
      // or wide
      else if (strNE (f->type, "TF") && (f->is_string && dwg_version >= R_2007))
        {
          BITCODE_TU wstr;
          if (is_utf8)
            wstr = bit_utf8_to_TU (*(char **)value, 0);
          else // source is already TU
            {
#ifdef HAVE_NATIVE_WCHAR2
              wstr = (BITCODE_TU)malloc (2 * (wcslen (*(wchar_t **)value) + 1));
              wcscpy ((wchar_t *)wstr, *(wchar_t **)value);
#else
              int length = 0;
              for (; (*(BITCODE_TU*)value)[length]; length++)
                ;
              length++;
              wstr = (BITCODE_TU)malloc ((2 * length) + 1);
              memcpy (wstr, value, length * 2);
#endif
            }
          free (*(char **)old);
          memcpy (old, &wstr, sizeof (char*)); // size of ptr
        }
      else
        memcpy (old, value, sizeof (char*));
    }
  else
    memcpy (old, value, f->size);
}

/* generic field setters */
EXPORT bool
dwg_dynapi_entity_set_value (void *restrict _obj, const char *restrict name,
                             const char *restrict fieldname,
                             const void *restrict value, const bool is_utf8)
{
#ifndef HAVE_NONNULL
  if (!_obj || !fieldname || !value) // cannot set NULL value
    return false;
#endif
  {
    int error;
    const Dwg_Object *obj = dwg_obj_generic_to_object (_obj, &error);
    if (error)
      {
        const int loglevel = DWG_LOGLEVEL_ERROR;
        LOG_ERROR ("%s: dwg_obj_generic_to_object failed", __FUNCTION__);
        return false;
      }
    if (obj && strNE (obj->name, name))
      {
        const int loglevel = obj->parent->opts & DWG_OPTS_LOGLEVEL;
        LOG_ERROR ("%s: Invalid entity type %s, wanted %s", __FUNCTION__,
                   obj->name, name);
        return false;
      }
    {
      void *old;
      const Dwg_DYNAPI_field *f = dwg_dynapi_entity_field (name, fieldname);
      const Dwg_Data *dwg
        = obj ? obj->parent
              : ((Dwg_Object_UNKNOWN_OBJ *)_obj)->parent->dwg;
      const Dwg_Version_Type dwg_version = dwg ? dwg->header.from_version : R_INVALID;
      // but there are some fixed-length malloced strings preR13
      static const Dwg_DYNAPI_field r11_fixed_strings[] = {
          { "font_file", "TFv", 64, OFF (struct _dwg_object_STYLE, font_file), 1,1,1, 3 },
          { "bigfont_file", "TFv", 64, OFF (struct _dwg_object_STYLE, bigfont_file), 1,1,1, 4 },
          { "description", "TFv", 48, OFF (struct _dwg_object_LTYPE, description), 1,1,1, 3 },
          // STYLE has no description, and LTYPE no font_file's
          { 0 }
        };

      if (!f)
        {
          const int loglevel = dwg ? dwg->opts & DWG_OPTS_LOGLEVEL : 0;
          LOG_ERROR ("%s: Invalid %s field %s", __FUNCTION__, name, fieldname);
          return false;
        }
      if (f->is_string && dwg->header.from_version < R_13b1 &&
          (obj->fixedtype == DWG_TYPE_STYLE || obj->fixedtype == DWG_TYPE_LTYPE))
        {
          //find_name (fieldname, r11_fixed_strings))
          for (Dwg_DYNAPI_field* f11 = (Dwg_DYNAPI_field*)&r11_fixed_strings[0]; f11->name; f11++)
            {
              if (strEQ (fieldname, f11->name))
                {
                  f = f11;
                  break;
                }
            }
        }
      old = &((char*)_obj)[f->offset];
      dynapi_set_helper (old, f, dwg_version, value, is_utf8);
      return true;
    }
  }
}

EXPORT bool
dwg_dynapi_header_set_value (Dwg_Data *restrict dwg,
                             const char *restrict fieldname,
                             const void *restrict value, const bool is_utf8)
{
#ifndef HAVE_NONNULL
  if (!dwg || !fieldname || !value) // cannot set NULL value
    return false;
#endif
  {
    Dwg_DYNAPI_field *f = (Dwg_DYNAPI_field *)bsearch (
        fieldname, _dwg_header_variables_fields,
        ARRAY_SIZE (_dwg_header_variables_fields) - 1, /* NULL terminated */
        sizeof (_dwg_header_variables_fields[0]), _name_struct_cmp);
    if (f)
      {
        void *old;
        // there are no malloc'd fields in the HEADER, so no need to free().
        const Dwg_Header_Variables *const _obj = &dwg->header_vars;
        // but there are several fixed-length malloced strings preR13
        static const Dwg_DYNAPI_field r11_fixed_strings[] = {
          { "MENU", "TFv", 16, OFF (struct _dwg_header_variables, MENU), 1,1,1, 1 },
          { "DIMBLK_T", "TFv", 33, OFF (struct _dwg_header_variables, DIMBLK_T), 1,1,1, 1 },
          { "DIMBLK1_T", "TFv", 33, OFF (struct _dwg_header_variables, DIMBLK1_T), 1,1,1, 1 },
          { "DIMBLK2_T", "TFv", 33, OFF (struct _dwg_header_variables, DIMBLK2_T), 1,1,1, 1 },
          { "DIMPOST", "TFv", 16, OFF (struct _dwg_header_variables, DIMPOST), 1,1,1, 1 },
          { "DIMAPOST", "TFv", 16, OFF (struct _dwg_header_variables, DIMAPOST), 1,1,1, 1 },
          { "unknown_string", "TFv", 33, OFF (struct _dwg_header_variables, unknown_string), 1,1,1, 1 },
          { "unit1_name", "TFv", 32, OFF (struct _dwg_header_variables, unit1_name), 1,1,1, 1 },
          { "unit2_name", "TFv", 32, OFF (struct _dwg_header_variables, unit2_name), 1,1,1, 1 },
          { "unit3_name", "TFv", 32, OFF (struct _dwg_header_variables, unit3_name), 1,1,1, 1 },
          { "unit4_name", "TFv", 32, OFF (struct _dwg_header_variables, unit4_name), 1,1,1, 1 },
          { 0 }
        };
        if (f->is_string && dwg->header.from_version < R_13b1)
          {
            //find_name (fieldname, r11_fixed_strings))
            for (Dwg_DYNAPI_field* f11 = (Dwg_DYNAPI_field*)&r11_fixed_strings[0]; f11->name; f11++)
              {
                if (strEQ (fieldname, f11->name))
                  {
                    f = f11;
                    break;
                  }
              }
          }
        old = &((char*)_obj)[f->offset];
        dynapi_set_helper (old, f, dwg->header.version, value, is_utf8);

        // Set also FLAGS
        if (strEQc (fieldname, "CELWEIGHT"))
          {
            dwg->header_vars.FLAGS &= ~0x1f; // delete old, and set new
            dwg->header_vars.FLAGS |= dxf_revcvt_lweight (dwg->header_vars.CELWEIGHT);
          }
        else if (strEQc (fieldname, "codepage"))
          {
            dwg->header.codepage = *(BITCODE_RS*)value;
          }
#define SET_HDR_FLAGS(name, bit, inverse)          \
        else if (strEQc (fieldname, #name))        \
          {                                        \
            if (dwg->header_vars.name && !inverse) \
              dwg->header_vars.FLAGS |= bit;       \
            else                                   \
              dwg->header_vars.FLAGS &= ~bit;      \
          }
        SET_HDR_FLAGS (ENDCAPS, 0x60, 0)
        SET_HDR_FLAGS (JOINSTYLE, 0x180, 0)
        SET_HDR_FLAGS (LWDISPLAY, 0x200, 1)
        SET_HDR_FLAGS (XEDIT, 0x400, 1)
        SET_HDR_FLAGS (EXTNAMES, 0x800, 0)
        SET_HDR_FLAGS (PSTYLEMODE, 0x2000, 0)
        SET_HDR_FLAGS (OLESTARTUP, 0x4000, 0)

        return true;
      }
    else
      {
        const int loglevel = dwg->opts & DWG_OPTS_LOGLEVEL;
        LOG_ERROR ("%s: Invalid header field %s", __FUNCTION__, fieldname);
        return false;
      }
  }
}

EXPORT bool
dwg_dynapi_common_set_value (void *restrict _obj,
                             const char *restrict fieldname,
                             const void *restrict value, const bool is_utf8)
{
#ifndef HAVE_NONNULL
  if (!_obj || !fieldname || !value)
    return false;
#endif
  {
    Dwg_DYNAPI_field *f;
    int error;
    void *old;
    const Dwg_Object *obj = dwg_obj_generic_to_object (_obj, &error);
    Dwg_Data *dwg;
    if (!obj || error)
      {
        const int loglevel = DWG_LOGLEVEL_ERROR;
        LOG_ERROR ("%s: dwg_obj_generic_to_object failed", __FUNCTION__);
        return false;
      }
    dwg = obj->parent;
    if (obj->supertype == DWG_SUPERTYPE_ENTITY)
      {
        _obj = obj->tio.entity;
        f = (Dwg_DYNAPI_field *)bsearch (
            fieldname, _dwg_object_entity_fields,
            ARRAY_SIZE (_dwg_object_entity_fields) - 1, /* NULL terminated */
            sizeof (_dwg_object_entity_fields[0]), _name_struct_cmp);
      }
    else if (obj->supertype == DWG_SUPERTYPE_OBJECT)
      {
        _obj = obj->tio.object;
        f = (Dwg_DYNAPI_field *)bsearch (
            fieldname, _dwg_object_object_fields,
            ARRAY_SIZE (_dwg_object_object_fields) - 1, /* NULL terminated */
            sizeof (_dwg_object_object_fields[0]), _name_struct_cmp);
      }
    else
      {
        const int loglevel = DWG_LOGLEVEL_ERROR;
        LOG_ERROR ("%s: Unhandled %s.supertype ", __FUNCTION__, obj->name);
        return false;
      }

    if (!f)
      {
        const int loglevel = obj->parent->opts & DWG_OPTS_LOGLEVEL;
        LOG_ERROR ("%s: Invalid %s common field %s", __FUNCTION__, obj->name, fieldname);
        return false;
      }

    old = &((char*)_obj)[f->offset];
    if (f->dxf == 160 && strEQc (fieldname, "preview_size"))
      {
        int size = f->size;
        if (dwg && dwg->header.version < R_2010)
          size = 4;
        memcpy (old, value, size);
      }
    else
      dynapi_set_helper (old, f, dwg ? dwg->header.version : R_INVALID, value, is_utf8);

    if (dwg && obj->supertype == DWG_SUPERTYPE_ENTITY && strEQc (fieldname, "ltype"))
      { // set also isbylayerlt and ltype_flags
        Dwg_Object_Entity *ent = obj->tio.entity;
        if (!dwg->header_vars.LTYPE_BYLAYER || !ent->ltype)
          ;
        else if (ent->ltype->absolute_ref == dwg->header_vars.LTYPE_BYLAYER->absolute_ref)
          {
            ent->isbylayerlt = 1; // r13-r14 only
            ent->ltype_flags = 0;
          }
        else if (dwg->header_vars.LTYPE_BYBLOCK
                 && ent->ltype->absolute_ref == dwg->header_vars.LTYPE_BYBLOCK->absolute_ref)
          {
            ent->isbylayerlt = 0;
            ent->ltype_flags = 1;
          }
        else if (dwg->header_vars.LTYPE_CONTINUOUS
                 && ent->ltype->absolute_ref == dwg->header_vars.LTYPE_CONTINUOUS->absolute_ref)
          {
            ent->isbylayerlt = 0;
            ent->ltype_flags = 2;
          }
        else
          {
            ent->isbylayerlt = 0;
            ent->ltype_flags = 3;
          }
      }
    return true;
  }
}

// arbitrary structs, no text
EXPORT bool
dwg_dynapi_subclass_value (const void *restrict ptr,
                           const char *restrict subclass,
                           const char *restrict fieldname,
                           void *restrict out, Dwg_DYNAPI_field *restrict fp)
{
  const Dwg_DYNAPI_field *f;
#ifndef HAVE_NONNULL
  if (!ptr || !subclass || !fieldname || !out)
    return false;
#endif
  f = dwg_dynapi_subclass_field (subclass, fieldname);
  if (!f) // TODO maybe search via dwg_dynapi_subclass_name ()
    return false;
  memcpy (out, &((char*)ptr)[f->offset], f->size);
  if (fp)
    memcpy (fp, f, sizeof(Dwg_DYNAPI_field));
  return true;
}

// arbitrary structs, no text
EXPORT bool
dwg_dynapi_field_get_value (const void *restrict ptr,
                            const Dwg_DYNAPI_field *restrict field,
                            void *restrict out)
{
#ifndef HAVE_NONNULL
  if (!ptr || !field || !out)
    return false;
#endif
  memcpy (out, &((char*)ptr)[field->offset], field->size);
  return true;
}

// can do arbitrary structs, like subclasses
EXPORT bool
dwg_dynapi_field_set_value (const Dwg_Data *restrict dwg, /* only needed if unicode strings */
                            void *restrict ptr,
                            const Dwg_DYNAPI_field *restrict field,
                            const void *restrict value,
                            const bool is_utf8)
{
  void *off;
#ifndef HAVE_NONNULL
  if (!ptr || !field || !value)
    return false;
#endif
  off = &((char*)ptr)[field->offset];
  dynapi_set_helper (off, field, dwg ? dwg->header.version : R_INVALID, value, is_utf8);
  return true;
}

// check if the handle points to an object with a name.
// see also dwg_obj_table_get_name, which only supports tables.
EXPORT char*
dwg_dynapi_handle_name (const Dwg_Data *restrict dwg,
                        Dwg_Object_Ref *restrict hdl,
                        int *alloced)
{
  const bool is_tu = IS_FROM_TU_DWG (dwg);
  Dwg_Object *obj;
  *alloced = 0;
#ifndef HAVE_NONNULL
  if (dwg == NULL || hdl == NULL)
    return NULL;
#endif

  obj = dwg_ref_object_silent ((Dwg_Data *)dwg, hdl);
  if (!obj)
    return NULL;
  {
    const Dwg_DYNAPI_field *f = dwg_dynapi_entity_field (obj->name, "name");
    // just some random type is enough.
    Dwg_Object_STYLE *_obj = obj->tio.object->tio.STYLE;

    if (!f || !f->is_string)
      return NULL;
    if (is_tu && strNE (f->type, "TF")) /* not TF */
      {
        BITCODE_TU wstr = *(BITCODE_TU *)((char *)_obj + f->offset);
        *alloced = 1;
        return bit_convert_TU (wstr);
      }
    else
      {
        return *(char **)((char *)_obj + f->offset);
      }
  }
}

// The sum of the size of all fields
int
_fields_size_sum (const Dwg_DYNAPI_field *restrict fields)
{
  Dwg_DYNAPI_field *f = (Dwg_DYNAPI_field *)fields;
  int sum = 0;
  if (!f)
    return 0;
  for (; f->name; f++)
    {
      sum += f->size;
    }
  return sum;
}

// The size of the entity or subclass struct, or the sum of the size of all fields.
EXPORT int
dwg_dynapi_fields_size (const char *restrict name)
{
  const struct _name_type_fields *f;
#ifndef HAVE_NONNULL
  if (!name)
    return 0;
#endif

  f = _find_entity (name);
  // TODO PROXY_LWPOLYLINE
  if (f)
    {
      if (f->size)
        return (int)f->size;
      else
        return _fields_size_sum (f->fields); // VERTEX_PFACE is not entity nor object yet
    }
  else
    {
      int size = dwg_dynapi_subclass_size (name);
      if (size)
        return size;
      else
        return _fields_size_sum (dwg_dynapi_subclass_fields (name));
    }
}

// Converts from the fields type, like "Dwg_MLINESTYLE_line*" to the
// subclass name, like "MLINESTYLE_line".
ATTRIBUTE_MALLOC char*
dwg_dynapi_subclass_name (const char *restrict type)
{
  char *name = NULL;
  size_t len = strlen (type);
  if (memBEGINc (type, "Dwg_Object_"))
    {
      const size_t off = strlen ("Dwg_Object_"); // PLOTSETTINGS
      name = strdup (&type[off]);
      if (type[len - 1] == '*')
        name[len - off - 1] = '\0';
    }
  else if (memBEGINc (type, "Dwg_Entity_"))
    {
      const size_t off = strlen ("Dwg_Entity_");
      name = strdup (&type[off]);
      if (type[len - 1] == '*')
        name[len - off - 1] = '\0';
    }
  else if (memBEGINc (type, "Dwg_"))
    {
      name = strdup (&type[4]);
      if (type[len - 1] == '*')
        name[len - 5] = '\0';
    }
  else if (memBEGINc (type, "struct _dwg_entity_"))
    {
      const size_t off = strlen ("struct _dwg_entity_"); // TABLE
      name = strdup (&type[off]);
      if (type[len - 1] == '*')
        name[len - off - 1] = '\0';
    }
  else if (memBEGINc (type, "struct _dwg_object_"))
    {
      const size_t off = strlen ("struct _dwg_object_"); // CELLSTYLEMAP*, EVALUATION_GRAPH, ...
      name = strdup (&type[off]);
      if (type[len - 1] == '*')
        name[len - off - 1] = '\0';
    }
  else if (memBEGINc (type, "struct _dwg_")) // CellStyle*
    {
      const size_t off = strlen ("struct _dwg_");
      name = strdup (&type[off]);
      if (type[len - 1] == '*')
        name[len - off - 1] = '\0';
    }
  return name;
}

EXPORT bool
dwg_has_subclass (const char *restrict classname, const char *restrict subclass)
{
  struct _name_subclasses *f;
#ifndef HAVE_NONNULL
  if (!classname || !subclass)
    return false;
#endif
  f = (struct _name_subclasses *)bsearch (classname, dwg_name_subclasses,
                                          ARRAY_SIZE (dwg_name_subclasses),
                                          sizeof (dwg_name_subclasses[0]),
                                          _name_struct_cmp);
  if (f) {
    for (unsigned i = 0; i < @@scalar max_subclasses@@ /* max_subclasses */; i++) {
      if (!f->subclasses[i]) // already at NULL
        return false;
      if (strEQ (subclass, f->subclasses[i]))
        return true;
    }
  }
  return false;
}

EXPORT bool
dwg_dynapi_is_angle (const char *restrict name, const char *restrict fieldname)
{
  const Dwg_DYNAPI_field *f1 = NULL;
#ifndef HAVE_NONNULL
  if (!name || !fieldname)
    return 0;
#endif

  f1 = dwg_dynapi_entity_field (name, fieldname);
  if (!f1 && strEQc (name, "HEADER"))
    f1 = dwg_dynapi_header_field (fieldname);
  // there are no common angle fields
  if (f1)
    {
      short dxf = f1->dxf;
      return (dxf >= 50 && dxf < 60);
    }
  return false;
}

EXPORT bool
dwg_dynapi_is_float (const Dwg_DYNAPI_field *f)
{
#ifndef HAVE_NONNULL
  if (!f)
    return false;
#endif
  if (f->size == 8 &&
      (strEQc (f->type, "BD") ||
       strEQc (f->type, "RD")))
        return true;
  else
      return false;
}

/* Local Variables: */
/* mode: c */
/* End: */