#!/usr/bin/env perl -w
# Copyright (C) 2019-2026 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, $embedded_object, );
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_(\w+)/) {    # AuxHeader, Header, R2004_Header, SummaryInfo
        my $n = $1;
        next if length $n <= 1;
        $structs{$n}++;
    }
    elsif ( /_dwg_([A-Z0-9]+(?:_\w+)?)/ && !/_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 (/^#define (DIMENSION_COMMON)/) {
            $n = $1;
        }
    }
    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';
$ENT{$_}->{user_text}  = 'T'
    for qw(ARC_DIMENSION DIMENSION_ORDINATE DIMENSION_LINEAR DIMENSION_ALIGNED
    DIMENSION_ANG3PT DIMENSION_ANG2LN DIMENSION_RADIUS DIMENSION_DIAMETER
    LARGE_RADIAL_DIMENSION);
$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";
    }
}

# Record $f's DXF group code for struct $n.
# A field name can be re-read later inside a DXF 101 "Embedded Object"
# sub-section (see $embedded_object below) -- e.g. MTEXT's R2018
# embedded/annotative section reuses the outer entity's own field names
# (rect_width, rect_height, ...) under different group codes for the
# embedded subobject. That re-use must not silently clobber the field's
# canonical/primary dxf code, established by the first (normal,
# non-embedded) occurrence. So: while inside such a sub-section, the
# first dxf code already recorded for a field wins; outside of it,
# behave as before (last write wins, e.g. for version-conditional
# redefinitions).
sub set_dxf {
    my ( $n, $f, $dxf ) = @_;
    return unless $dxf;
    if ($embedded_object) {
        $DXF{$n}->{$f} = $dxf unless exists $DXF{$n}->{$f};
    }
    else {
        $DXF{$n}->{$f} = $dxf;
    }
}

my %defined;    #define subclass_fields, shared across dxfin_spec calls

# 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;
    while (<$in>) {
        $f = '';
        s/DXF \{ //;
        if ( !$n ) {
            if (/^DWG_(ENTITY|OBJECT|TABLE)\s?\((\w+)\)/) {
                $n = $2;
                $n =~ s/^_3/3/;
                warn $n;
                $embedded_object = 0;
            }
            elsif (/^\s*SECTION\s?\(HEADER\)/) {
                $n = 'header_variables';
                warn $n;
                $embedded_object = 0;
            }
            elsif (/^int DWG_FUNC_N\s?\(ACTION,_HATCH(\w+)\)/) {
                $n = 'HATCH';
                warn $n;
                $embedded_object = 0;
            }
            elsif (/^\#define (COMMON_ENTITY_POLYLINE)/) {
                $n = $1;
                warn "define $n";
                $embedded_object = 0;
            }
            elsif (
                /^\#define (COMMON_3DSOLID|ACTION_3DSOLID|COMMON_ENTITY_DIMENSION|DIMENSION_COMMON)/
                )
            {
                $n = $1;
                $defined{$n}++;
                warn "define $n";
                $embedded_object = 0;
            }
            elsif (/^\#define (\w+)_fields/) {
                $n = $1;
                $defined{$n}++;
                warn "define $n fields";
                $embedded_object = 0;
            }
        }
        elsif (/^\#define (\w+)_fields/) {
            $n = $1;
            $defined{$n}++;
            warn "define $n fields";
            $embedded_object = 0;

            # i.e. after #define
        }
        elsif (/^DWG_(ENTITY|OBJECT|TABLE)\s?\((\w+)\)/) {
            $n = $2;
            $n =~ s/^_3/3/;
            warn $n;
            $embedded_object = 0;
        }
        elsif (/^DWG_(ENTITY|OBJECT)_END/) {    # close
            $n               = '';
            @old             = ();
            $outdef          = 0;
            $embedded_object = 0;
        }
        elsif (/VALUE_TFF\s*\(\s*"Embedded Object"\s*,\s*101\s*\)/) {

            # DXF 101 subclass marker: an embedded/annotative sub-object
            # follows, whose fields reuse the enclosing struct's own
            # field names under unrelated group codes (see set_dxf above).
            $embedded_object = 1;
        }
        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
            }
            set_dxf( $n, $f, $2 );
        }
        elsif (/^\s+VALUE_HANDLE\s*\(.+,\s*(\w+),\s*\d,\s*(\d+)\)/) {
            $f = $1;
            set_dxf( $n, $f, $2 );
        }
        elsif (/^\s+FIELD_TF\s*\((\w+),\s*(\d*),\s*(\d+)\)/) {
            my $type = 'TF';
            $f = $1;
            set_dxf( $n, $f, $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
            set_dxf( $n, $f, $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;
            set_dxf( $n, $f, $2 );
        }
        elsif ( @old
            && /^\s+SUB_FIELD_VECTOR\s*\($v,\s*(\w+),\s*\w+,\s*\w+,\s*(\d+)\)/
            )
        {
            my $type = $1;
            $f = $1;
            set_dxf( $n, $f, $2 );
        }
        elsif ( @old && /^\s+SUB_FIELD_(.+?)\s*\($v,\s*(\w+),\s*(\d+)\)/ ) {
            my $type = $1;
            $f = $2;
            set_dxf( $n, $f, $3 );
        }
        elsif (/^\s+FIELD_(?:CMC|ENC)\s*\((\w+),\s*(\d+)\)/) {
            $f = $1;
            if ($2) {
                set_dxf( $n, $f, $2 );
                if ( $2 < 90 ) {
                    set_dxf( $n, "$f.index",     $2 );
                    set_dxf( $n, "$f.rbg",       $2 + 420 - 62 );
                    set_dxf( $n, "$f.name",      $2 + 430 - 62 );
                    set_dxf( $n, "$f.book_name", $2 + 430 - 62 );
                    set_dxf( $n, "$f.alpha",     $2 + 440 - 62 );
                }
                else {
                    set_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
            set_dxf( $n, $f, $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;
            set_dxf( $n, $f, $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;
            set_dxf( $n, $f, $2 );
        }
        elsif (/^\s+SUB_HANDLE_VECTOR\s*\([^,]+?, (\w+), .+, (\d+)\)/) {
            $f = $1;
            set_dxf( $n, $f, $2 );
        }
        elsif (/^\s+HEADER_.+\s*\((\w+),\s*(\d+)\)/) {    # HEADER_RC
            $f = $1;
            set_dxf( $n, $f, $2 );
        }
        elsif (/^\s+HEADER_.+\s*\((\w+),\s*(\d+),\s*\w+\)/) {
            $f = $1;
            set_dxf( $n, $f, $2 );
        }
        elsif (/^\s+HEADER_.+\s*\((\w+),\s*\w+,\s*(\d+),\s*\D.+\)/) {
            $f = $1;

            #$f =~ s/^_3/3/;
            set_dxf( $n, $f, $2 );
        }
        elsif (/^\s+HEADER_([23])D\s*\((\w+)\)/) {
            $f = $2;
            set_dxf( $n, $f, $1 eq '2' ? 20 : 30 );
        }
        elsif (/^\s+VALUE_(.+)\s*\((\w.+),.*,\s*(\d+)\)/) {
            my $type = $1;
            $f = $2;
            set_dxf( $n, $f, $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 (/^\s+(DIMENSION_COMMON)/) {
            expand_define( $1, $n, \%defined );    # not reached
        }
        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_shared.h";
dxfin_spec "$srcdir/dwg.spec";
dxfin_spec "$srcdir/dwg2.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'}->{'dwg_version'}      = 95;
$DXF{'PROXY_ENTITY'}->{'maint_version'}    = 95;
$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{'MLINESTYLE_line'}->{'lt_index'}      = 6;
$DXF{'MLINESTYLE_line'}->{'lt_ltype'}      = 6;

# $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           => "AcDbAssocGeomDependency",
    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};
    }
    elsif ( !$bc && $ns =~ /_DIMENSION_common/ ) {
        $bc = $h{DIMENSION_COMMON}{$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-2026 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, 3DLINE
    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';    # for 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';         # for 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}++;
$STABLE{_3DLINE}++;
$FIXED{_3DSOLID}++;
$FIXED{_3DFACE}++;
$FIXED{_3DLINE}++;
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, 3DLINE
        #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);
    if ($name =~ /^3/) {
        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/^\s*\/\* Start auto-generated/) {
        print $out $_;
        print $out "// clang-format: off\n";

        $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 "// clang-format: on\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.
 */
// clang-format: off
//< \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';

// clang-format: on
/********************************************************************
 *     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.
 */

// clang-format: off
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';

// clang-format: on
/*******************************************************************
 *     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
 */
// clang-format: off
/* 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';

// clang-format: on
/*******************************************************************
 *     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
 */
// clang-format: off
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/^\s*\/\* Start auto-generated/) {
        print $out $_;
        print $out "  // clang-format: off\n";

        $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";
        $tmpl = "    dwg_get_OBJECT_DECL (ent_\$lname, \$name);\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";
        $tmpl = "    dwg_get_OBJECT_DECL (obj_\$lname, \$name);\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 "  // clang-format: on\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( "$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/^\s*\/\* Start auto-generated entity-union/) {
        print $out $_;
        print $out "    // clang-format: off\n";
        $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 "    // clang-format: on\n";
        print $out "    /* End auto-generated entity-union */\n";
        $gen = 1;
    }
    elsif (m/\s*\/\* Start auto-generated object-union/) {
        print $out $_;
        print $out "    // clang-format: off\n";
        $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 "    // clang-format: off\n";
        print $out "    /* End auto-generated object-union */\n";
        $gen = 1;
    }
    elsif (m/^\s*\/\* Start auto-generated content/) {
        print $out $_;

        print $out "// clang-format: off\n";
        $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 "// clang-format: on\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/^\s*\/\* Start auto-generated content/) {
            print $out $_;

            print $out "// clang-format: off\n";
            $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 "// clang-format: on\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( "$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 $_;
            print $out "  // clang-format: off\n";
            my $e = 500;
            for (@VARTYPES) {
                printf $out "  %-59s /* %d */\n", "\"$_\",", $e++;
            }
            print $out "  // clang-format: on\n";
            print $out "  /* End auto-generated variable */\n";
            $gen = 1;
        }
        if (m/^\s+\/\* Start auto-generated dxfnames/) {
            print $out $_;
            print $out "  // clang-format: off\n";
            my $e = 500;
            for (@VARTYPES) {
                my $dxfname = dxfname $_;
                printf $out "  %-59s /* %d */\n", "\"$dxfname\",", $e++;
            }
            print $out "  // clang-format: on\n";
            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 "// clang-format: off\n";
        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 "// clang-format: on\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 2894 "gen-dynapi.pl"
/*****************************************************************************/
/*  LibreDWG - free implementation of the DWG file format                    */
/*                                                                           */
/*  Copyright (C) 2018-2026 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 2978 "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.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 (obj && 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)
{
  Dwg_DYNAPI_field *f;
#ifndef HAVE_NONNULL
  if (!ptr || !subclass || !fieldname || !out)
    return false;
#endif
  f = (Dwg_DYNAPI_field *)dwg_dynapi_subclass_field (subclass, fieldname);
  if (!f) // TODO maybe search via dwg_dynapi_subclass_name ()
    {
      // Dwg_Object_ASSOCDEPENDENCY is also a valid subclass, embedded into other objects
      if (memBEGINc (subclass, "Dwg_Object_"))
        f = (Dwg_DYNAPI_field *)dwg_dynapi_entity_field (&subclass[strlen("Dwg_Object_")], fieldname);
      if (!f)
        return false;
    }
  memcpy (out, &((char*)ptr)[f->offset], f->size);
  if (fp)
    memcpy (fp, f, sizeof(Dwg_DYNAPI_field));
  return true;
}

// ptr points to the subclass field offset
EXPORT bool
dwg_dynapi_subclass_set_value (Dwg_Data *restrict dwg,
                               const void *restrict ptr,
                               const char *restrict subclass,
                               const char *restrict fieldname,
                               const void *restrict value, const bool is_utf8)
{
  Dwg_DYNAPI_field *f;
  void *old;
#ifndef HAVE_NONNULL
  if (!dwg || !ptr || !subclass || !fieldname || !value)
    return false;
#endif
  f = (Dwg_DYNAPI_field *)dwg_dynapi_subclass_field (subclass, fieldname);
  if (!f) // TODO maybe search via dwg_dynapi_subclass_name ()
    {
      // Dwg_Object_ASSOCDEPENDENCY is also a valid subclass, embedded into other objects
      if (memBEGINc (subclass, "Dwg_Object_"))
        f = (Dwg_DYNAPI_field *)dwg_dynapi_entity_field (&subclass[strlen("Dwg_Object_")], fieldname);
      if (!f)
        return false;
    }
  old = &((char*)ptr)[f->offset];
  if (f->is_string)
    dynapi_set_helper (old, f, dwg->header.version, value, is_utf8);
  else
    memcpy (old, value, f->size);
  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: */
