/******************************************************************************
 *
 * Project:  Microstation DGN Access Library
 * Purpose:  DGN Access Library element reading code.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2000, Avenza Systems Inc, http://www.avenza.com/
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "dgnlibp.h"

#include <algorithm>

static DGNElemCore *DGNParseTCB(DGNInfo *);
static DGNElemCore *DGNParseColorTable(DGNInfo *);
static DGNElemCore *DGNParseTagSet(DGNInfo *);

/************************************************************************/
/*                             DGN_INT16()                              */
/************************************************************************/

static short int DGN_INT16(const GByte *p)
{
    return static_cast<short>(p[0] | (p[1] << 8));
}

/************************************************************************/
/*                           DGNGotoElement()                           */
/************************************************************************/

/**
 * Seek to indicated element.
 *
 * Changes what element will be read on the next call to DGNReadElement().
 * Note that this function requires and index, and one will be built if
 * not already available.
 *
 * @param hDGN the file to affect.
 * @param element_id the element to seek to.  These values are sequentially
 * ordered starting at zero for the first element.
 *
 * @return returns TRUE on success or FALSE on failure.
 */

int DGNGotoElement(DGNHandle hDGN, int element_id)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    DGNBuildIndex(psDGN);

    if (element_id < 0 || element_id >= psDGN->element_count)
        return FALSE;

    if (VSIFSeekL(psDGN->fp, psDGN->element_index[element_id].offset,
                  SEEK_SET) != 0)
        return FALSE;

    psDGN->next_element_id = element_id;
    psDGN->in_complex_group = false;

    return TRUE;
}

/************************************************************************/
/*                         DGNLoadRawElement()                          */
/************************************************************************/

int DGNLoadRawElement(DGNInfo *psDGN, int *pnType, int *pnLevel)

{
    /* -------------------------------------------------------------------- */
    /*      Read the first four bytes to get the level, type, and word      */
    /*      count.                                                          */
    /* -------------------------------------------------------------------- */
    if (VSIFReadL(psDGN->abyElem, 1, 4, psDGN->fp) != 4)
        return FALSE;

    /* Is this an 0xFFFF endof file marker? */
    if (psDGN->abyElem[0] == 0xff && psDGN->abyElem[1] == 0xff)
        return FALSE;

    int nWords = psDGN->abyElem[2] + psDGN->abyElem[3] * 256;
    int nType = psDGN->abyElem[1] & 0x7f;
    int nLevel = psDGN->abyElem[0] & 0x3f;

    /* -------------------------------------------------------------------- */
    /*      Read the rest of the element data into the working buffer.      */
    /* -------------------------------------------------------------------- */
    if (nWords * 2 + 4 >= (int)sizeof(psDGN->abyElem))
        return FALSE;

    /* coverity[tainted_data] */
    if ((int)VSIFReadL(psDGN->abyElem + 4, 2, nWords, psDGN->fp) != nWords)
        return FALSE;
    psDGN->abyElem[4 + 2 * nWords] = 0;
    psDGN->abyElem[sizeof(psDGN->abyElem) - 1] = 0;

    psDGN->nElemBytes = nWords * 2 + 4;

    psDGN->next_element_id++;

    /* -------------------------------------------------------------------- */
    /*      Return requested info.                                          */
    /* -------------------------------------------------------------------- */
    if (pnType != nullptr)
        *pnType = nType;

    if (pnLevel != nullptr)
        *pnLevel = nLevel;

    return TRUE;
}

/************************************************************************/
/*                          DGNGetRawExtents()                          */
/*                                                                      */
/*      Returns false if the element type does not have recognizable    */
/*      element extents, other true and the extents will be updated.    */
/*                                                                      */
/*      It is assumed the raw element data has been loaded into the     */
/*      working area by DGNLoadRawElement().                            */
/************************************************************************/

static bool DGNGetRawExtents(DGNInfo *psDGN, int nType,
                             unsigned char *pabyRawData, GUInt32 *pnXMin,
                             GUInt32 *pnYMin, GUInt32 *pnZMin, GUInt32 *pnXMax,
                             GUInt32 *pnYMax, GUInt32 *pnZMax)

{
    if (pabyRawData == nullptr)
        pabyRawData = psDGN->abyElem + 0;

    switch (nType)
    {
        case DGNT_LINE:
        case DGNT_LINE_STRING:
        case DGNT_SHAPE:
        case DGNT_CURVE:
        case DGNT_BSPLINE_POLE:
        case DGNT_BSPLINE_SURFACE_HEADER:
        case DGNT_BSPLINE_CURVE_HEADER:
        case DGNT_ELLIPSE:
        case DGNT_ARC:
        case DGNT_TEXT:
        case DGNT_TEXT_NODE:
        case DGNT_COMPLEX_CHAIN_HEADER:
        case DGNT_COMPLEX_SHAPE_HEADER:
        case DGNT_CONE:
        case DGNT_3DSURFACE_HEADER:
        case DGNT_3DSOLID_HEADER:
            *pnXMin = DGN_INT32(pabyRawData + 4);
            *pnYMin = DGN_INT32(pabyRawData + 8);
            if (pnZMin != nullptr)
                *pnZMin = DGN_INT32(pabyRawData + 12);

            *pnXMax = DGN_INT32(pabyRawData + 16);
            *pnYMax = DGN_INT32(pabyRawData + 20);
            if (pnZMax != nullptr)
                *pnZMax = DGN_INT32(pabyRawData + 24);
            return true;

        default:
            return false;
    }
}

/************************************************************************/
/*                        DGNGetElementExtents()                        */
/************************************************************************/

/**
 * Fetch extents of an element.
 *
 * This function will return the extents of the passed element if possible.
 * The extents are extracted from the element header if it contains them,
 * and transformed into master georeferenced format.  Some element types
 * do not have extents at all and will fail.
 *
 * This call will also fail if the extents raw data for the element is not
 * available.  This will occur if it was not the most recently read element,
 * and if the raw_data field is not loaded.
 *
 * @param hDGN the handle of the file to read from.
 *
 * @param psElement the element to extract extents from.
 *
 * @param psMin structure loaded with X, Y and Z minimum values for the
 * extent.
 *
 * @param psMax structure loaded with X, Y and Z maximum values for the
 * extent.
 *
 * @return TRUE on success of FALSE if extracting extents fails.
 */

int DGNGetElementExtents(DGNHandle hDGN, DGNElemCore *psElement,
                         DGNPoint *psMin, DGNPoint *psMax)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;
    bool bResult = false;

    GUInt32 anMin[3] = {0, 0, 0};
    GUInt32 anMax[3] = {0, 0, 0};

    /* -------------------------------------------------------------------- */
    /*      Get the extents if we have raw data in the element, or          */
    /*      loaded in the file buffer.                                      */
    /* -------------------------------------------------------------------- */
    if (psElement->raw_data != nullptr)
        bResult = DGNGetRawExtents(psDGN, psElement->type, psElement->raw_data,
                                   anMin + 0, anMin + 1, anMin + 2, anMax + 0,
                                   anMax + 1, anMax + 2);
    else if (psElement->element_id == psDGN->next_element_id - 1)
        bResult = DGNGetRawExtents(psDGN, psElement->type, psDGN->abyElem + 0,
                                   anMin + 0, anMin + 1, anMin + 2, anMax + 0,
                                   anMax + 1, anMax + 2);
    else
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "DGNGetElementExtents() fails because the requested element "
                 "does not have raw data available.");
        return FALSE;
    }

    if (!bResult)
        return FALSE;

    /* -------------------------------------------------------------------- */
    /*      Transform to user coordinate system and return.  The offset     */
    /*      is to convert from "binary offset" form to twos complement.     */
    /* -------------------------------------------------------------------- */
    psMin->x = anMin[0] - 2147483648.0;
    psMin->y = anMin[1] - 2147483648.0;
    psMin->z = anMin[2] - 2147483648.0;

    psMax->x = anMax[0] - 2147483648.0;
    psMax->y = anMax[1] - 2147483648.0;
    psMax->z = anMax[2] - 2147483648.0;

    DGNTransformPoint(psDGN, psMin);
    DGNTransformPoint(psDGN, psMax);

    return TRUE;
}

/************************************************************************/
/*                         DGNProcessElement()                          */
/*                                                                      */
/*      Assumes the raw element data has already been loaded, and       */
/*      tries to convert it into an element structure.                  */
/************************************************************************/

static DGNElemCore *DGNProcessElement(DGNInfo *psDGN, int nType, int nLevel)

{
    DGNElemCore *psElement = nullptr;

    /* -------------------------------------------------------------------- */
    /*      Handle based on element type.                                   */
    /* -------------------------------------------------------------------- */
    switch (nType)
    {
        case DGNT_CELL_HEADER:
        {
            DGNElemCellHeader *psCell = static_cast<DGNElemCellHeader *>(
                CPLCalloc(sizeof(DGNElemCellHeader), 1));
            psElement = (DGNElemCore *)psCell;
            psElement->stype = DGNST_CELL_HEADER;
            DGNParseCore(psDGN, psElement);

            psCell->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;

            DGNRad50ToAscii(psDGN->abyElem[38] + psDGN->abyElem[39] * 256,
                            psCell->name + 0);
            DGNRad50ToAscii(psDGN->abyElem[40] + psDGN->abyElem[41] * 256,
                            psCell->name + 3);

            psCell->cclass = psDGN->abyElem[42] + psDGN->abyElem[43] * 256;
            psCell->levels[0] = psDGN->abyElem[44] + psDGN->abyElem[45] * 256;
            psCell->levels[1] = psDGN->abyElem[46] + psDGN->abyElem[47] * 256;
            psCell->levels[2] = psDGN->abyElem[48] + psDGN->abyElem[49] * 256;
            psCell->levels[3] = psDGN->abyElem[50] + psDGN->abyElem[51] * 256;

            if (psDGN->dimension == 2)
            {
                psCell->rnglow.x = DGN_INT32(psDGN->abyElem + 52);
                psCell->rnglow.y = DGN_INT32(psDGN->abyElem + 56);
                psCell->rnghigh.x = DGN_INT32(psDGN->abyElem + 60);
                psCell->rnghigh.y = DGN_INT32(psDGN->abyElem + 64);

                psCell->trans[0] =
                    1.0 * DGN_INT32(psDGN->abyElem + 68) / (1U << 31);
                psCell->trans[1] =
                    1.0 * DGN_INT32(psDGN->abyElem + 72) / (1U << 31);
                psCell->trans[2] =
                    1.0 * DGN_INT32(psDGN->abyElem + 76) / (1U << 31);
                psCell->trans[3] =
                    1.0 * DGN_INT32(psDGN->abyElem + 80) / (1U << 31);

                psCell->origin.x = DGN_INT32(psDGN->abyElem + 84);
                psCell->origin.y = DGN_INT32(psDGN->abyElem + 88);

                {
                    const double a = DGN_INT32(psDGN->abyElem + 68);
                    const double b = DGN_INT32(psDGN->abyElem + 72);
                    const double c = DGN_INT32(psDGN->abyElem + 76);
                    const double d = DGN_INT32(psDGN->abyElem + 80);
                    const double a2 = a * a;
                    const double c2 = c * c;

                    psCell->xscale = sqrt(a2 + c2) / 214748;
                    psCell->yscale = sqrt(b * b + d * d) / 214748;
                    if ((a2 + c2) <= 0.0)
                        psCell->rotation = 0.0;
                    else
                        psCell->rotation = acos(a / sqrt(a2 + c2));

                    if (b <= 0)
                        psCell->rotation = psCell->rotation * 180 / M_PI;
                    else
                        psCell->rotation = 360 - psCell->rotation * 180 / M_PI;
                }
            }
            else
            {
                psCell->rnglow.x = DGN_INT32(psDGN->abyElem + 52);
                psCell->rnglow.y = DGN_INT32(psDGN->abyElem + 56);
                psCell->rnglow.z = DGN_INT32(psDGN->abyElem + 60);
                psCell->rnghigh.x = DGN_INT32(psDGN->abyElem + 64);
                psCell->rnghigh.y = DGN_INT32(psDGN->abyElem + 68);
                psCell->rnghigh.z = DGN_INT32(psDGN->abyElem + 72);

                psCell->trans[0] =
                    1.0 * DGN_INT32(psDGN->abyElem + 76) / (1U << 31);
                psCell->trans[1] =
                    1.0 * DGN_INT32(psDGN->abyElem + 80) / (1U << 31);
                psCell->trans[2] =
                    1.0 * DGN_INT32(psDGN->abyElem + 84) / (1U << 31);
                psCell->trans[3] =
                    1.0 * DGN_INT32(psDGN->abyElem + 88) / (1U << 31);
                psCell->trans[4] =
                    1.0 * DGN_INT32(psDGN->abyElem + 92) / (1U << 31);
                psCell->trans[5] =
                    1.0 * DGN_INT32(psDGN->abyElem + 96) / (1U << 31);
                psCell->trans[6] =
                    1.0 * DGN_INT32(psDGN->abyElem + 100) / (1U << 31);
                psCell->trans[7] =
                    1.0 * DGN_INT32(psDGN->abyElem + 104) / (1U << 31);
                psCell->trans[8] =
                    1.0 * DGN_INT32(psDGN->abyElem + 108) / (1U << 31);

                psCell->origin.x = DGN_INT32(psDGN->abyElem + 112);
                psCell->origin.y = DGN_INT32(psDGN->abyElem + 116);
                psCell->origin.z = DGN_INT32(psDGN->abyElem + 120);
            }

            DGNTransformPoint(psDGN, &(psCell->rnglow));
            DGNTransformPoint(psDGN, &(psCell->rnghigh));
            DGNTransformPoint(psDGN, &(psCell->origin));
        }
        break;

        case DGNT_CELL_LIBRARY:
        {
            DGNElemCellLibrary *psCell = static_cast<DGNElemCellLibrary *>(
                CPLCalloc(sizeof(DGNElemCellLibrary), 1));
            psElement = (DGNElemCore *)psCell;
            psElement->stype = DGNST_CELL_LIBRARY;
            DGNParseCore(psDGN, psElement);

            DGNRad50ToAscii(psDGN->abyElem[32] + psDGN->abyElem[33] * 256,
                            psCell->name + 0);
            DGNRad50ToAscii(psDGN->abyElem[34] + psDGN->abyElem[35] * 256,
                            psCell->name + 3);

            psElement->properties =
                psDGN->abyElem[38] + psDGN->abyElem[39] * 256;

            psCell->dispsymb = psDGN->abyElem[40] + psDGN->abyElem[41] * 256;

            psCell->cclass = psDGN->abyElem[42] + psDGN->abyElem[43] * 256;
            psCell->levels[0] = psDGN->abyElem[44] + psDGN->abyElem[45] * 256;
            psCell->levels[1] = psDGN->abyElem[46] + psDGN->abyElem[47] * 256;
            psCell->levels[2] = psDGN->abyElem[48] + psDGN->abyElem[49] * 256;
            psCell->levels[3] = psDGN->abyElem[50] + psDGN->abyElem[51] * 256;

            psCell->numwords = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;

            memset(psCell->description, 0, sizeof(psCell->description));

            for (int iWord = 0; iWord < 9; iWord++)
            {
                int iOffset = 52 + iWord * 2;

                DGNRad50ToAscii(psDGN->abyElem[iOffset] +
                                    psDGN->abyElem[iOffset + 1] * 256,
                                psCell->description + iWord * 3);
            }
        }
        break;

        case DGNT_LINE:
        {
            DGNElemMultiPoint *psLine = static_cast<DGNElemMultiPoint *>(
                CPLCalloc(sizeof(DGNElemMultiPoint) + sizeof(DGNPoint), 1));
            psElement = (DGNElemCore *)psLine;
            psElement->stype = DGNST_MULTIPOINT;
            DGNParseCore(psDGN, psElement);

            int deltaLength = 0, deltaStart = 0;
            if (psLine->core.properties & DGNPF_ATTRIBUTES)
            {
                for (int iAttr = 0; iAttr < psLine->core.attr_bytes - 3;
                     iAttr++)
                {
                    if (psLine->core.attr_data[iAttr] == 0xA9 &&
                        psLine->core.attr_data[iAttr + 1] == 0x51)
                    {
                        deltaLength =
                            (psLine->core.attr_data[iAttr + 2] +
                             psLine->core.attr_data[iAttr + 3] * 256) *
                            2;
                        deltaStart = iAttr + 6;
                        break;
                    }
                }
            }

            psLine->num_vertices = 2;
            if (psDGN->dimension == 2)
            {
                psLine->vertices[0].x = DGN_INT32(psDGN->abyElem + 36);
                psLine->vertices[0].y = DGN_INT32(psDGN->abyElem + 40);
                psLine->vertices[1].x = DGN_INT32(psDGN->abyElem + 44);
                psLine->vertices[1].y = DGN_INT32(psDGN->abyElem + 48);
            }
            else
            {
                psLine->vertices[0].x = DGN_INT32(psDGN->abyElem + 36);
                psLine->vertices[0].y = DGN_INT32(psDGN->abyElem + 40);
                psLine->vertices[0].z = DGN_INT32(psDGN->abyElem + 44);
                psLine->vertices[1].x = DGN_INT32(psDGN->abyElem + 48);
                psLine->vertices[1].y = DGN_INT32(psDGN->abyElem + 52);
                psLine->vertices[1].z = DGN_INT32(psDGN->abyElem + 56);
            }

            if (deltaStart && deltaLength &&
                deltaStart + 1 * 4 + 2 + 2 <= psLine->core.attr_bytes)
            {
                for (int i = 0; i < 2; i++)
                {
                    int dx =
                        DGN_INT16(psLine->core.attr_data + deltaStart + i * 4);
                    int dy = DGN_INT16(psLine->core.attr_data + deltaStart +
                                       i * 4 + 2);
                    psLine->vertices[i].x += dx / 32767.0;
                    psLine->vertices[i].y += dy / 32767.0;
                }
            }

            DGNTransformPoint(psDGN, psLine->vertices + 0);
            DGNTransformPoint(psDGN, psLine->vertices + 1);
        }
        break;

        case DGNT_LINE_STRING:
        case DGNT_SHAPE:
        case DGNT_CURVE:
        case DGNT_BSPLINE_POLE:
        {
            int pntsize = psDGN->dimension * 4;

            int count = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
            if (count < 2)
            {
                CPLError(CE_Failure, CPLE_AssertionFailed, "count < 2");
                return nullptr;
            }
            DGNElemMultiPoint *psLine =
                static_cast<DGNElemMultiPoint *>(VSI_CALLOC_VERBOSE(
                    sizeof(DGNElemMultiPoint) + (count - 1) * sizeof(DGNPoint),
                    1));
            if (psLine == nullptr)
                return nullptr;
            psElement = (DGNElemCore *)psLine;
            psElement->stype = DGNST_MULTIPOINT;
            DGNParseCore(psDGN, psElement);

            if (psDGN->nElemBytes < 38 + count * pntsize)
            {
                int new_count = (psDGN->nElemBytes - 38) / pntsize;
                if (new_count < 0)
                {
                    CPLError(CE_Failure, CPLE_AssertionFailed, "new_count < 2");
                    DGNFreeElement(psDGN, psElement);
                    return nullptr;
                }
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Trimming multipoint vertices to %d from %d because\n"
                         "element is short.\n",
                         new_count, count);
                count = new_count;
            }
            int deltaLength = 0, deltaStart = 0;
            if (psLine->core.properties & DGNPF_ATTRIBUTES)
            {
                for (int iAttr = 0; iAttr < psLine->core.attr_bytes - 3;
                     iAttr++)
                {
                    if (psLine->core.attr_data[iAttr] == 0xA9 &&
                        psLine->core.attr_data[iAttr + 1] == 0x51)
                    {
                        deltaLength =
                            (psLine->core.attr_data[iAttr + 2] +
                             psLine->core.attr_data[iAttr + 3] * 256) *
                            2;
                        deltaStart = iAttr + 6;
                        break;
                    }
                }
            }
            for (int i = 0; i < count && ((psDGN->dimension == 3) ? 46 : 42) +
                                                 i * pntsize + 4 <=
                                             psDGN->nElemBytes;
                 i++)
            {
                psLine->vertices[i].x =
                    DGN_INT32(psDGN->abyElem + 38 + i * pntsize);
                psLine->vertices[i].y =
                    DGN_INT32(psDGN->abyElem + 42 + i * pntsize);
                if (psDGN->dimension == 3)
                    psLine->vertices[i].z =
                        DGN_INT32(psDGN->abyElem + 46 + i * pntsize);
                if (deltaStart && deltaLength &&
                    deltaStart + i * 4 + 2 + 2 <= psLine->core.attr_bytes)
                {
                    int dx =
                        DGN_INT16(psLine->core.attr_data + deltaStart + i * 4);
                    int dy = DGN_INT16(psLine->core.attr_data + deltaStart +
                                       i * 4 + 2);
                    psLine->vertices[i].x += dx / 32767.0;
                    psLine->vertices[i].y += dy / 32767.0;
                }
                DGNTransformPoint(psDGN, psLine->vertices + i);
                psLine->num_vertices = i + 1;
            }
        }
        break;

        case DGNT_TEXT_NODE:
        {
            DGNElemTextNode *psNode = static_cast<DGNElemTextNode *>(
                CPLCalloc(sizeof(DGNElemTextNode), 1));
            psElement = (DGNElemCore *)psNode;
            psElement->stype = DGNST_TEXT_NODE;
            DGNParseCore(psDGN, psElement);

            psNode->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
            psNode->numelems = psDGN->abyElem[38] + psDGN->abyElem[39] * 256;

            psNode->node_number = psDGN->abyElem[40] + psDGN->abyElem[41] * 256;
            psNode->max_length = psDGN->abyElem[42];
            psNode->max_used = psDGN->abyElem[43];
            psNode->font_id = psDGN->abyElem[44];
            psNode->justification = psDGN->abyElem[45];
            psNode->length_mult =
                (DGN_INT32(psDGN->abyElem + 50)) * psDGN->scale * 6.0 / 1000.0;
            psNode->height_mult =
                (DGN_INT32(psDGN->abyElem + 54)) * psDGN->scale * 6.0 / 1000.0;

            if (psDGN->dimension == 2)
            {
                psNode->rotation = DGN_INT32(psDGN->abyElem + 58) / 360000.0;

                psNode->origin.x = DGN_INT32(psDGN->abyElem + 62);
                psNode->origin.y = DGN_INT32(psDGN->abyElem + 66);
            }
            else
            {
                /* leave quaternion for later */

                psNode->origin.x = DGN_INT32(psDGN->abyElem + 74);
                psNode->origin.y = DGN_INT32(psDGN->abyElem + 78);
                psNode->origin.z = DGN_INT32(psDGN->abyElem + 82);
            }
            DGNTransformPoint(psDGN, &(psNode->origin));
        }
        break;

        case DGNT_GROUP_DATA:
            if (nLevel == DGN_GDL_COLOR_TABLE)
            {
                psElement = DGNParseColorTable(psDGN);
            }
            else
            {
                psElement = static_cast<DGNElemCore *>(
                    CPLCalloc(sizeof(DGNElemCore), 1));
                psElement->stype = DGNST_CORE;
                DGNParseCore(psDGN, psElement);
            }
            break;

        case DGNT_ELLIPSE:
        {
            DGNElemArc *psEllipse =
                static_cast<DGNElemArc *>(CPLCalloc(sizeof(DGNElemArc), 1));
            psElement = (DGNElemCore *)psEllipse;
            psElement->stype = DGNST_ARC;
            DGNParseCore(psDGN, psElement);

            memcpy(&(psEllipse->primary_axis), psDGN->abyElem + 36, 8);
            DGN2IEEEDouble(&(psEllipse->primary_axis));
            psEllipse->primary_axis *= psDGN->scale;

            memcpy(&(psEllipse->secondary_axis), psDGN->abyElem + 44, 8);
            DGN2IEEEDouble(&(psEllipse->secondary_axis));
            psEllipse->secondary_axis *= psDGN->scale;

            if (psDGN->dimension == 2)
            {
                psEllipse->rotation = DGN_INT32(psDGN->abyElem + 52);
                psEllipse->rotation = psEllipse->rotation / 360000.0;

                memcpy(&(psEllipse->origin.x), psDGN->abyElem + 56, 8);
                DGN2IEEEDouble(&(psEllipse->origin.x));

                memcpy(&(psEllipse->origin.y), psDGN->abyElem + 64, 8);
                DGN2IEEEDouble(&(psEllipse->origin.y));
            }
            else
            {
                /* leave quaternion for later */

                memcpy(&(psEllipse->origin.x), psDGN->abyElem + 68, 8);
                DGN2IEEEDouble(&(psEllipse->origin.x));

                memcpy(&(psEllipse->origin.y), psDGN->abyElem + 76, 8);
                DGN2IEEEDouble(&(psEllipse->origin.y));

                memcpy(&(psEllipse->origin.z), psDGN->abyElem + 84, 8);
                DGN2IEEEDouble(&(psEllipse->origin.z));

                psEllipse->quat[0] = DGN_INT32(psDGN->abyElem + 52);
                psEllipse->quat[1] = DGN_INT32(psDGN->abyElem + 56);
                psEllipse->quat[2] = DGN_INT32(psDGN->abyElem + 60);
                psEllipse->quat[3] = DGN_INT32(psDGN->abyElem + 64);
            }

            DGNTransformPoint(psDGN, &(psEllipse->origin));

            psEllipse->startang = 0.0;
            psEllipse->sweepang = 360.0;
        }
        break;

        case DGNT_ARC:
        {
            GInt32 nSweepVal = 0;

            DGNElemArc *psEllipse =
                static_cast<DGNElemArc *>(CPLCalloc(sizeof(DGNElemArc), 1));
            psElement = (DGNElemCore *)psEllipse;
            psElement->stype = DGNST_ARC;
            DGNParseCore(psDGN, psElement);

            psEllipse->startang = DGN_INT32(psDGN->abyElem + 36);
            psEllipse->startang = psEllipse->startang / 360000.0;
            if (psDGN->abyElem[41] & 0x80)
            {
                psDGN->abyElem[41] &= 0x7f;
                nSweepVal = -1 * DGN_INT32(psDGN->abyElem + 40);
            }
            else
                nSweepVal = DGN_INT32(psDGN->abyElem + 40);

            if (nSweepVal == 0)
                psEllipse->sweepang = 360.0;
            else
                psEllipse->sweepang = nSweepVal / 360000.0;

            memcpy(&(psEllipse->primary_axis), psDGN->abyElem + 44, 8);
            DGN2IEEEDouble(&(psEllipse->primary_axis));
            psEllipse->primary_axis *= psDGN->scale;

            memcpy(&(psEllipse->secondary_axis), psDGN->abyElem + 52, 8);
            DGN2IEEEDouble(&(psEllipse->secondary_axis));
            psEllipse->secondary_axis *= psDGN->scale;

            if (psDGN->dimension == 2)
            {
                psEllipse->rotation = DGN_INT32(psDGN->abyElem + 60);
                psEllipse->rotation = psEllipse->rotation / 360000.0;

                memcpy(&(psEllipse->origin.x), psDGN->abyElem + 64, 8);
                DGN2IEEEDouble(&(psEllipse->origin.x));

                memcpy(&(psEllipse->origin.y), psDGN->abyElem + 72, 8);
                DGN2IEEEDouble(&(psEllipse->origin.y));
            }
            else
            {
                /* for now we don't try to handle quaternion */
                psEllipse->rotation = 0;

                memcpy(&(psEllipse->origin.x), psDGN->abyElem + 76, 8);
                DGN2IEEEDouble(&(psEllipse->origin.x));

                memcpy(&(psEllipse->origin.y), psDGN->abyElem + 84, 8);
                DGN2IEEEDouble(&(psEllipse->origin.y));

                memcpy(&(psEllipse->origin.z), psDGN->abyElem + 92, 8);
                DGN2IEEEDouble(&(psEllipse->origin.z));

                psEllipse->quat[0] = DGN_INT32(psDGN->abyElem + 60);
                psEllipse->quat[1] = DGN_INT32(psDGN->abyElem + 64);
                psEllipse->quat[2] = DGN_INT32(psDGN->abyElem + 68);
                psEllipse->quat[3] = DGN_INT32(psDGN->abyElem + 72);
            }

            DGNTransformPoint(psDGN, &(psEllipse->origin));
        }
        break;

        case DGNT_TEXT:
        {
            int num_chars = 0;
            int text_off = 0;

            if (psDGN->dimension == 2)
                num_chars = psDGN->abyElem[58];
            else
                num_chars = psDGN->abyElem[74];

            DGNElemText *psText = static_cast<DGNElemText *>(
                CPLCalloc(sizeof(DGNElemText) + num_chars, 1));
            psElement = (DGNElemCore *)psText;
            psElement->stype = DGNST_TEXT;
            DGNParseCore(psDGN, psElement);

            psText->font_id = psDGN->abyElem[36];
            psText->justification = psDGN->abyElem[37];
            psText->length_mult =
                (DGN_INT32(psDGN->abyElem + 38)) * psDGN->scale * 6.0 / 1000.0;
            psText->height_mult =
                (DGN_INT32(psDGN->abyElem + 42)) * psDGN->scale * 6.0 / 1000.0;

            if (psDGN->dimension == 2)
            {
                psText->rotation = DGN_INT32(psDGN->abyElem + 46);
                psText->rotation = psText->rotation / 360000.0;

                psText->origin.x = DGN_INT32(psDGN->abyElem + 50);
                psText->origin.y = DGN_INT32(psDGN->abyElem + 54);
                text_off = 60;
            }
            else
            {
                /* leave quaternion for later */

                psText->origin.x = DGN_INT32(psDGN->abyElem + 62);
                psText->origin.y = DGN_INT32(psDGN->abyElem + 66);
                psText->origin.z = DGN_INT32(psDGN->abyElem + 70);
                text_off = 76;
            }

            DGNTransformPoint(psDGN, &(psText->origin));

            /* experimental multibyte support from Ason Kang
             * (hiska@netian.com)*/
            if (*(psDGN->abyElem + text_off) == 0xFF &&
                *(psDGN->abyElem + text_off + 1) == 0xFD)
            {
                int n = 0;
                for (int i = 0; i < num_chars / 2 - 1; i++)
                {
                    unsigned short w = 0;
                    memcpy(&w, psDGN->abyElem + text_off + 2 + i * 2, 2);
                    CPL_LSBPTR16(&w);
                    if (w < 256)
                    {  // if alpa-numeric code area : Normal character
                        *(psText->string + n) = (char)(w & 0xFF);
                        n++;  // skip 1 byte;
                    }
                    else
                    {  // if extend code area : 2 byte Korean character
                        *(psText->string + n) = (char)(w >> 8);        // hi
                        *(psText->string + n + 1) = (char)(w & 0xFF);  // lo
                        n += 2;                                        // 2 byte
                    }
                }
                psText->string[n] = '\0';  // terminate C string
            }
            else
            {
                memcpy(psText->string, psDGN->abyElem + text_off, num_chars);
                psText->string[num_chars] = '\0';
            }
        }
        break;

        case DGNT_TCB:
            psElement = DGNParseTCB(psDGN);
            break;

        case DGNT_COMPLEX_CHAIN_HEADER:
        case DGNT_COMPLEX_SHAPE_HEADER:
        {
            DGNElemComplexHeader *psHdr = static_cast<DGNElemComplexHeader *>(
                CPLCalloc(sizeof(DGNElemComplexHeader), 1));
            psElement = (DGNElemCore *)psHdr;
            psElement->stype = DGNST_COMPLEX_HEADER;
            DGNParseCore(psDGN, psElement);

            psHdr->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
            psHdr->numelems = psDGN->abyElem[38] + psDGN->abyElem[39] * 256;
        }
        break;

        case DGNT_TAG_VALUE:
        {
            DGNElemTagValue *psTag = static_cast<DGNElemTagValue *>(
                CPLCalloc(sizeof(DGNElemTagValue), 1));
            psElement = (DGNElemCore *)psTag;
            psElement->stype = DGNST_TAG_VALUE;
            DGNParseCore(psDGN, psElement);

            psTag->tagType = psDGN->abyElem[74] + psDGN->abyElem[75] * 256;
            memcpy(&(psTag->tagSet), psDGN->abyElem + 68, 4);
            CPL_LSBPTR32(&(psTag->tagSet));
            psTag->tagIndex = psDGN->abyElem[72] + psDGN->abyElem[73] * 256;
            psTag->tagLength = psDGN->abyElem[150] + psDGN->abyElem[151] * 256;

            if (psTag->tagType == 1)
            {
                psTag->tagValue.string =
                    CPLStrdup((char *)psDGN->abyElem + 154);
            }
            else if (psTag->tagType == 3)
            {
                memcpy(&(psTag->tagValue.integer), psDGN->abyElem + 154, 4);
                CPL_LSBPTR32(&(psTag->tagValue.integer));
            }
            else if (psTag->tagType == 4)
            {
                memcpy(&(psTag->tagValue.real), psDGN->abyElem + 154, 8);
                DGN2IEEEDouble(&(psTag->tagValue.real));
            }
        }
        break;

        case DGNT_APPLICATION_ELEM:
            if (nLevel == 24)
            {
                psElement = DGNParseTagSet(psDGN);
                if (psElement == nullptr)
                    return nullptr;
            }
            else
            {
                psElement = static_cast<DGNElemCore *>(
                    CPLCalloc(sizeof(DGNElemCore), 1));
                psElement->stype = DGNST_CORE;
                DGNParseCore(psDGN, psElement);
            }
            break;

        case DGNT_CONE:
        {
            if (psDGN->dimension != 3)
            {
                CPLError(CE_Failure, CPLE_AssertionFailed,
                         "psDGN->dimension != 3");
                return nullptr;
            }

            DGNElemCone *psCone =
                static_cast<DGNElemCone *>(CPLCalloc(sizeof(DGNElemCone), 1));
            psElement = (DGNElemCore *)psCone;
            psElement->stype = DGNST_CONE;
            DGNParseCore(psDGN, psElement);

            psCone->unknown = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
            psCone->quat[0] = DGN_INT32(psDGN->abyElem + 38);
            psCone->quat[1] = DGN_INT32(psDGN->abyElem + 42);
            psCone->quat[2] = DGN_INT32(psDGN->abyElem + 46);
            psCone->quat[3] = DGN_INT32(psDGN->abyElem + 50);

            memcpy(&(psCone->center_1.x), psDGN->abyElem + 54, 8);
            DGN2IEEEDouble(&(psCone->center_1.x));
            memcpy(&(psCone->center_1.y), psDGN->abyElem + 62, 8);
            DGN2IEEEDouble(&(psCone->center_1.y));
            memcpy(&(psCone->center_1.z), psDGN->abyElem + 70, 8);
            DGN2IEEEDouble(&(psCone->center_1.z));
            memcpy(&(psCone->radius_1), psDGN->abyElem + 78, 8);
            DGN2IEEEDouble(&(psCone->radius_1));

            memcpy(&(psCone->center_2.x), psDGN->abyElem + 86, 8);
            DGN2IEEEDouble(&(psCone->center_2.x));
            memcpy(&(psCone->center_2.y), psDGN->abyElem + 94, 8);
            DGN2IEEEDouble(&(psCone->center_2.y));
            memcpy(&(psCone->center_2.z), psDGN->abyElem + 102, 8);
            DGN2IEEEDouble(&(psCone->center_2.z));
            memcpy(&(psCone->radius_2), psDGN->abyElem + 110, 8);
            DGN2IEEEDouble(&(psCone->radius_2));

            psCone->radius_1 *= psDGN->scale;
            psCone->radius_2 *= psDGN->scale;
            DGNTransformPoint(psDGN, &psCone->center_1);
            DGNTransformPoint(psDGN, &psCone->center_2);
        }
        break;

        case DGNT_3DSURFACE_HEADER:
        case DGNT_3DSOLID_HEADER:
        {
            DGNElemComplexHeader *psShape = static_cast<DGNElemComplexHeader *>(
                CPLCalloc(sizeof(DGNElemComplexHeader), 1));
            psElement = (DGNElemCore *)psShape;
            psElement->stype = DGNST_COMPLEX_HEADER;
            DGNParseCore(psDGN, psElement);

            // Read complex header
            psShape->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
            psShape->numelems = psDGN->abyElem[38] + psDGN->abyElem[39] * 256;
            psShape->surftype = psDGN->abyElem[40];
            psShape->boundelms = psDGN->abyElem[41] + 1;
        }
        break;
        case DGNT_BSPLINE_SURFACE_HEADER:
        {
            DGNElemBSplineSurfaceHeader *psSpline =
                static_cast<DGNElemBSplineSurfaceHeader *>(
                    CPLCalloc(sizeof(DGNElemBSplineSurfaceHeader), 1));
            psElement = (DGNElemCore *)psSpline;
            psElement->stype = DGNST_BSPLINE_SURFACE_HEADER;
            DGNParseCore(psDGN, psElement);

            // Read B-Spline surface header
            psSpline->desc_words =
                static_cast<long>(DGN_INT32(psDGN->abyElem + 36));
            psSpline->curve_type = psDGN->abyElem[41];

            // U
            psSpline->u_order = (psDGN->abyElem[40] & 0x0f) + 2;
            psSpline->u_properties = psDGN->abyElem[40] & 0xf0;
            psSpline->num_poles_u =
                psDGN->abyElem[42] + psDGN->abyElem[43] * 256;
            psSpline->num_knots_u =
                psDGN->abyElem[44] + psDGN->abyElem[45] * 256;
            psSpline->rule_lines_u =
                psDGN->abyElem[46] + psDGN->abyElem[47] * 256;

            // V
            psSpline->v_order = (psDGN->abyElem[48] & 0x0f) + 2;
            psSpline->v_properties = psDGN->abyElem[48] & 0xf0;
            psSpline->num_poles_v =
                psDGN->abyElem[50] + psDGN->abyElem[51] * 256;
            psSpline->num_knots_v =
                psDGN->abyElem[52] + psDGN->abyElem[53] * 256;
            psSpline->rule_lines_v =
                psDGN->abyElem[54] + psDGN->abyElem[55] * 256;

            psSpline->num_bounds =
                psDGN->abyElem[56] + psDGN->abyElem[57] * 556;
        }
        break;
        case DGNT_BSPLINE_CURVE_HEADER:
        {
            DGNElemBSplineCurveHeader *psSpline =
                static_cast<DGNElemBSplineCurveHeader *>(
                    CPLCalloc(sizeof(DGNElemBSplineCurveHeader), 1));
            psElement = (DGNElemCore *)psSpline;
            psElement->stype = DGNST_BSPLINE_CURVE_HEADER;
            DGNParseCore(psDGN, psElement);

            // Read B-Spline curve header
            psSpline->desc_words =
                static_cast<long>(DGN_INT32(psDGN->abyElem + 36));

            // flags
            psSpline->order = (psDGN->abyElem[40] & 0x0f) + 2;
            psSpline->properties = psDGN->abyElem[40] & 0xf0;
            psSpline->curve_type = psDGN->abyElem[41];

            psSpline->num_poles = psDGN->abyElem[42] + psDGN->abyElem[43] * 256;
            psSpline->num_knots = psDGN->abyElem[44] + psDGN->abyElem[45] * 256;
        }
        break;
        case DGNT_BSPLINE_SURFACE_BOUNDARY:
        {
            short numverts = psDGN->abyElem[38] + psDGN->abyElem[39] * 256;
            if (numverts <= 0)
            {
                CPLError(CE_Failure, CPLE_AssertionFailed, "numverts <= 0");
                return nullptr;
            }

            DGNElemBSplineSurfaceBoundary *psBounds =
                static_cast<DGNElemBSplineSurfaceBoundary *>(
                    CPLCalloc(sizeof(DGNElemBSplineSurfaceBoundary) +
                                  (numverts - 1) * sizeof(DGNPoint),
                              1));
            psElement = (DGNElemCore *)psBounds;
            psElement->stype = DGNST_BSPLINE_SURFACE_BOUNDARY;
            DGNParseCore(psDGN, psElement);

            int deltaLength = 0, deltaStart = 0;
            if (psBounds->core.properties & DGNPF_ATTRIBUTES)
            {
                for (int iAttr = 0; iAttr < psBounds->core.attr_bytes - 3;
                     iAttr++)
                {
                    if (psBounds->core.attr_data[iAttr] == 0xA9 &&
                        psBounds->core.attr_data[iAttr + 1] == 0x51)
                    {
                        deltaLength =
                            (psBounds->core.attr_data[iAttr + 2] +
                             psBounds->core.attr_data[iAttr + 3] * 256) *
                            2;
                        deltaStart = iAttr + 6;
                        break;
                    }
                }
            }
            // Read B-Spline surface boundary
            psBounds->number = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;

            for (int i = 0; i < numverts && 44 + i * 8 + 4 <= psDGN->nElemBytes;
                 i++)
            {
                psBounds->vertices[i].x =
                    DGN_INT32(psDGN->abyElem + 40 + i * 8);
                psBounds->vertices[i].y =
                    DGN_INT32(psDGN->abyElem + 44 + i * 8);
                psBounds->vertices[i].z = 0;
                if (deltaStart && deltaLength &&
                    deltaStart + i * 4 + 2 + 2 <= psBounds->core.attr_bytes)
                {
                    int dx = DGN_INT16(psBounds->core.attr_data + deltaStart +
                                       i * 4);
                    int dy = DGN_INT16(psBounds->core.attr_data + deltaStart +
                                       i * 4 + 2);
                    psBounds->vertices[i].x += dx / 32767.0;
                    psBounds->vertices[i].y += dy / 32767.0;
                }
                psBounds->numverts = static_cast<short>(i + 1);
            }
        }
        break;
        case DGNT_BSPLINE_KNOT:
        case DGNT_BSPLINE_WEIGHT_FACTOR:
        {
            // FIXME: Is it OK to assume that the # of elements corresponds
            // directly to the element size? kintel 20051215.
            int attr_bytes =
                psDGN->nElemBytes -
                (psDGN->abyElem[30] + psDGN->abyElem[31] * 256) * 2 - 32;
            int numelems = (psDGN->nElemBytes - 36 - attr_bytes) / 4;
            if (numelems < 1)
            {
                CPLError(CE_Failure, CPLE_AssertionFailed, "numelems < 1");
                return nullptr;
            }
            DGNElemKnotWeight *psArray =
                static_cast<DGNElemKnotWeight *>(CPLCalloc(
                    sizeof(DGNElemKnotWeight) + (numelems - 1) * sizeof(float),
                    1));

            psElement = (DGNElemCore *)psArray;
            psElement->stype = DGNST_KNOT_WEIGHT;
            DGNParseCore(psDGN, psElement);

            // Read array
            for (int i = 0; i < numelems; i++)
            {
                psArray->array[i] = static_cast<float>(
                    1.0 * DGN_INT32(psDGN->abyElem + 36 + i * 4) /
                    ((1UL << 31) - 1));
            }
        }
        break;
        case DGNT_SHARED_CELL_DEFN:
        {
            DGNElemSharedCellDefn *psShared =
                static_cast<DGNElemSharedCellDefn *>(
                    CPLCalloc(sizeof(DGNElemSharedCellDefn), 1));
            psElement = (DGNElemCore *)psShared;
            psElement->stype = DGNST_SHARED_CELL_DEFN;
            DGNParseCore(psDGN, psElement);

            psShared->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
        }
        break;
        default:
        {
            psElement =
                static_cast<DGNElemCore *>(CPLCalloc(sizeof(DGNElemCore), 1));
            psElement->stype = DGNST_CORE;
            DGNParseCore(psDGN, psElement);
        }
        break;
    }

    /* -------------------------------------------------------------------- */
    /*      If the element structure type is "core" or if we are running    */
    /*      in "capture all" mode, record the complete binary image of      */
    /*      the element.                                                    */
    /* -------------------------------------------------------------------- */
    if (psElement->stype == DGNST_CORE ||
        (psDGN->options & DGNO_CAPTURE_RAW_DATA))
    {
        psElement->raw_bytes = psDGN->nElemBytes;
        psElement->raw_data =
            static_cast<unsigned char *>(CPLMalloc(psElement->raw_bytes));

        memcpy(psElement->raw_data, psDGN->abyElem, psElement->raw_bytes);
    }

    /* -------------------------------------------------------------------- */
    /*      Collect some additional generic information.                    */
    /* -------------------------------------------------------------------- */
    psElement->element_id = psDGN->next_element_id - 1;

    psElement->offset =
        static_cast<int>(VSIFTellL(psDGN->fp)) - psDGN->nElemBytes;
    psElement->size = psDGN->nElemBytes;

    return psElement;
}

/************************************************************************/
/*                           DGNReadElement()                           */
/************************************************************************/

/**
 * Read a DGN element.
 *
 * This function will return the next element in the file, starting with the
 * first.  It is affected by DGNGotoElement() calls.
 *
 * The element is read into a structure which includes the DGNElemCore
 * structure.  It is expected that applications will inspect the stype
 * field of the returned DGNElemCore and use it to cast the pointer to the
 * appropriate element structure type such as DGNElemMultiPoint.
 *
 * @param hDGN the handle of the file to read from.
 *
 * @return pointer to element structure, or NULL on EOF or processing error.
 * The structure should be freed with DGNFreeElement() when no longer needed.
 */

DGNElemCore *DGNReadElement(DGNHandle hDGN)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;
    int nType = 0;
    int nLevel = 0;
    bool bInsideFilter = false;

    /* -------------------------------------------------------------------- */
    /*      Load the element data into the current buffer.  If a spatial    */
    /*      filter is in effect, loop until we get something within our     */
    /*      spatial constraints.                                            */
    /* -------------------------------------------------------------------- */
    do
    {
        bInsideFilter = true;

        if (!DGNLoadRawElement(psDGN, &nType, &nLevel))
            return nullptr;

        if (psDGN->has_spatial_filter)
        {
            if (!psDGN->sf_converted_to_uor)
                DGNSpatialFilterToUOR(psDGN);

            GUInt32 nXMin = 0;
            GUInt32 nXMax = 0;
            GUInt32 nYMin = 0;
            GUInt32 nYMax = 0;
            if (!DGNGetRawExtents(psDGN, nType, nullptr, &nXMin, &nYMin,
                                  nullptr, &nXMax, &nYMax, nullptr))
            {
                /* If we don't have spatial characteristics for the element
                   we will pass it through. */
                bInsideFilter = true;
            }
            else if (nXMin > psDGN->sf_max_x || nYMin > psDGN->sf_max_y ||
                     nXMax < psDGN->sf_min_x || nYMax < psDGN->sf_min_y)
            {
                bInsideFilter = false;
            }

            /*
            ** We want to select complex elements based on the extents of
            ** the header, not the individual elements.
            */
            if (nType == DGNT_COMPLEX_CHAIN_HEADER ||
                nType == DGNT_COMPLEX_SHAPE_HEADER)
            {
                psDGN->in_complex_group = true;
                psDGN->select_complex_group = bInsideFilter;
            }
            else if (psDGN->abyElem[0] & 0x80 /* complex flag set */)
            {
                if (psDGN->in_complex_group)
                    bInsideFilter = psDGN->select_complex_group;
            }
            else
            {
                psDGN->in_complex_group = false;
            }
        }
    } while (!bInsideFilter);

    /* -------------------------------------------------------------------- */
    /*      Convert into an element structure.                              */
    /* -------------------------------------------------------------------- */
    DGNElemCore *psElement = DGNProcessElement(psDGN, nType, nLevel);

    return psElement;
}

/************************************************************************/
/*                       DGNElemTypeHasDispHdr()                        */
/************************************************************************/

/**
 * Does element type have display header.
 *
 * @param nElemType element type (0-63) to test.
 *
 * @return TRUE if elements of passed in type have a display header after the
 * core element header, or FALSE otherwise.
 */

int DGNElemTypeHasDispHdr(int nElemType)

{
    switch (nElemType)
    {
        case 0:
        case DGNT_TCB:
        case DGNT_CELL_LIBRARY:
        case DGNT_LEVEL_SYMBOLOGY:
        case 32:
        case 44:
        case 48:
        case 49:
        case 50:
        case 51:
        case 57:
        case 60:
        case 61:
        case 62:
        case 63:
            return FALSE;

        default:
            return TRUE;
    }
}

/************************************************************************/
/*                            DGNParseCore()                            */
/************************************************************************/

int DGNParseCore(DGNInfo *psDGN, DGNElemCore *psElement)

{
    GByte *psData = psDGN->abyElem + 0;

    psElement->level = psData[0] & 0x3f;
    psElement->complex = psData[0] & 0x80;
    psElement->deleted = psData[1] & 0x80;
    psElement->type = psData[1] & 0x7f;

    if (psDGN->nElemBytes >= 36 && DGNElemTypeHasDispHdr(psElement->type))
    {
        psElement->graphic_group = psData[28] + psData[29] * 256;
        psElement->properties = psData[32] + psData[33] * 256;
        psElement->style = psData[34] & 0x7;
        psElement->weight = (psData[34] & 0xf8) >> 3;
        psElement->color = psData[35];
    }
    else
    {
        psElement->graphic_group = 0;
        psElement->properties = 0;
        psElement->style = 0;
        psElement->weight = 0;
        psElement->color = 0;
    }

    if (psElement->properties & DGNPF_ATTRIBUTES)
    {
        const int nAttIndex = psData[30] + psData[31] * 256;

        psElement->attr_bytes = psDGN->nElemBytes - nAttIndex * 2 - 32;
        if (psElement->attr_bytes > 0)
        {
            psElement->attr_data =
                static_cast<unsigned char *>(CPLMalloc(psElement->attr_bytes));
            memcpy(psElement->attr_data, psData + nAttIndex * 2 + 32,
                   psElement->attr_bytes);
        }
        else
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Computed %d bytes for attribute info on element,\n"
                     "perhaps this element type doesn't really have a disphdr?",
                     psElement->attr_bytes);
            psElement->attr_bytes = 0;
        }
    }

    return TRUE;
}

/************************************************************************/
/*                         DGNParseColorTable()                         */
/************************************************************************/

static DGNElemCore *DGNParseColorTable(DGNInfo *psDGN)

{
    DGNElemColorTable *psColorTable = static_cast<DGNElemColorTable *>(
        CPLCalloc(sizeof(DGNElemColorTable), 1));
    DGNElemCore *psElement = (DGNElemCore *)psColorTable;
    psElement->stype = DGNST_COLORTABLE;

    DGNParseCore(psDGN, psElement);

    psColorTable->screen_flag = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;

    memcpy(psColorTable->color_info[255], psDGN->abyElem + 38, 3);
    memcpy(psColorTable->color_info, psDGN->abyElem + 41, 765);

    // We used to only install a color table as the default color
    // table if it was the first in the file.  But apparently we should
    // really be using the last one.  This doesn't necessarily accomplish
    // that either if the elements are being read out of order but it will
    // usually do better at least.
    memcpy(psDGN->color_table, psColorTable->color_info, 768);
    psDGN->got_color_table = 1;

    return psElement;
}

/************************************************************************/
/*                           DGNParseTagSet()                           */
/************************************************************************/

static DGNElemCore *DGNParseTagSet(DGNInfo *psDGN)

{
    DGNElemTagSet *psTagSet =
        static_cast<DGNElemTagSet *>(CPLCalloc(sizeof(DGNElemTagSet), 1));
    DGNElemCore *psElement = (DGNElemCore *)psTagSet;
    psElement->stype = DGNST_TAG_SET;

    DGNParseCore(psDGN, psElement);

    /* -------------------------------------------------------------------- */
    /*      Parse the overall information.                                  */
    /* -------------------------------------------------------------------- */
    psTagSet->tagCount = psDGN->abyElem[44] + psDGN->abyElem[45] * 256;
    psTagSet->flags = psDGN->abyElem[46] + psDGN->abyElem[47] * 256;
    psTagSet->tagSetName = CPLStrdup((const char *)(psDGN->abyElem + 48));

    /* -------------------------------------------------------------------- */
    /*      Get the tag set number out of the attributes, if available.     */
    /* -------------------------------------------------------------------- */
    psTagSet->tagSet = -1;

    if (psElement->attr_bytes >= 8 && psElement->attr_data[0] == 0x03 &&
        psElement->attr_data[1] == 0x10 && psElement->attr_data[2] == 0x2f &&
        psElement->attr_data[3] == 0x7d)
        psTagSet->tagSet =
            psElement->attr_data[4] + psElement->attr_data[5] * 256;

    /* -------------------------------------------------------------------- */
    /*      Parse each of the tag definitions.                              */
    /* -------------------------------------------------------------------- */
    psTagSet->tagList = static_cast<DGNTagDef *>(
        CPLCalloc(sizeof(DGNTagDef), psTagSet->tagCount));

    size_t nDataOffset = 48 + strlen(psTagSet->tagSetName) + 1 + 1;

    for (int iTag = 0; iTag < psTagSet->tagCount; iTag++)
    {
        DGNTagDef *tagDef = psTagSet->tagList + iTag;

        // Check the buffer is large enough to read all tagDef components
        size_t nDataOffsetEnd = nDataOffset;
        if (nDataOffsetEnd <= static_cast<size_t>(psDGN->nElemBytes))
        {
            nDataOffsetEnd +=
                strlen((char *)psDGN->abyElem + nDataOffsetEnd) + 1 + 2;
        }
        if (nDataOffsetEnd <= static_cast<size_t>(psDGN->nElemBytes))
        {
            nDataOffsetEnd +=
                strlen((char *)psDGN->abyElem + nDataOffsetEnd) + 1 + 2 + 5;
            if (tagDef->type == 1)
            {
                nDataOffsetEnd += strlen(tagDef->defaultValue.string) + 1;
            }
            else if (tagDef->type == 3 || tagDef->type == 5)
            {
                nDataOffsetEnd += 4;
            }
            else if (tagDef->type == 4)
            {
                nDataOffsetEnd += 8;
            }
            else
            {
                nDataOffsetEnd += 4;
            }
        }
        if (nDataOffsetEnd > static_cast<size_t>(psDGN->nElemBytes))
        {
            CPLError(CE_Failure, CPLE_AssertionFailed,
                     "nDataOffset >= static_cast<size_t>(psDGN->nElemBytes)");
            DGNFreeElement(psDGN, psElement);
            return nullptr;
        }

        /* collect tag name. */
        tagDef->name = CPLStrdup((char *)psDGN->abyElem + nDataOffset);
        nDataOffset += strlen(tagDef->name) + 1;

        /* Get tag id */
        tagDef->id =
            psDGN->abyElem[nDataOffset] + psDGN->abyElem[nDataOffset + 1] * 256;
        nDataOffset += 2;

        /* Get User Prompt */
        tagDef->prompt = CPLStrdup((char *)psDGN->abyElem + nDataOffset);
        nDataOffset += strlen(tagDef->prompt) + 1;

        /* Get type */
        tagDef->type =
            psDGN->abyElem[nDataOffset] + psDGN->abyElem[nDataOffset + 1] * 256;
        nDataOffset += 2;

        /* skip five zeros */
        nDataOffset += 5;

        /* Get the default */
        if (tagDef->type == 1)
        {
            tagDef->defaultValue.string =
                CPLStrdup((char *)psDGN->abyElem + nDataOffset);
            nDataOffset += strlen(tagDef->defaultValue.string) + 1;
        }
        else if (tagDef->type == 3 || tagDef->type == 5)
        {
            memcpy(&(tagDef->defaultValue.integer),
                   psDGN->abyElem + nDataOffset, 4);
            CPL_LSBPTR32(&(tagDef->defaultValue.integer));
            nDataOffset += 4;
        }
        else if (tagDef->type == 4)
        {
            memcpy(&(tagDef->defaultValue.real), psDGN->abyElem + nDataOffset,
                   8);
            DGN2IEEEDouble(&(tagDef->defaultValue.real));
            nDataOffset += 8;
        }
        else
            nDataOffset += 4;
    }
    return psElement;
}

/************************************************************************/
/*                            DGNParseTCB()                             */
/************************************************************************/

static DGNElemCore *DGNParseTCB(DGNInfo *psDGN)

{
    DGNElemTCB *psTCB =
        static_cast<DGNElemTCB *>(CPLCalloc(sizeof(DGNElemTCB), 1));
    DGNElemCore *psElement = (DGNElemCore *)psTCB;
    psElement->stype = DGNST_TCB;
    DGNParseCore(psDGN, psElement);

    if (psDGN->abyElem[1214] & 0x40)
        psTCB->dimension = 3;
    else
        psTCB->dimension = 2;

    psTCB->subunits_per_master =
        static_cast<long>(DGN_INT32(psDGN->abyElem + 1112));

    psTCB->master_units[0] = (char)psDGN->abyElem[1120];
    psTCB->master_units[1] = (char)psDGN->abyElem[1121];
    psTCB->master_units[2] = '\0';

    psTCB->uor_per_subunit =
        static_cast<long>(DGN_INT32(psDGN->abyElem + 1116));

    psTCB->sub_units[0] = (char)psDGN->abyElem[1122];
    psTCB->sub_units[1] = (char)psDGN->abyElem[1123];
    psTCB->sub_units[2] = '\0';

    /* Get global origin */
    memcpy(&(psTCB->origin_x), psDGN->abyElem + 1240, 8);
    memcpy(&(psTCB->origin_y), psDGN->abyElem + 1248, 8);
    memcpy(&(psTCB->origin_z), psDGN->abyElem + 1256, 8);

    /* Transform to IEEE */
    DGN2IEEEDouble(&(psTCB->origin_x));
    DGN2IEEEDouble(&(psTCB->origin_y));
    DGN2IEEEDouble(&(psTCB->origin_z));

    /* Convert from UORs to master units. */
    if (psTCB->uor_per_subunit != 0 && psTCB->subunits_per_master != 0)
    {
        psTCB->origin_x = psTCB->origin_x /
                          (psTCB->uor_per_subunit * psTCB->subunits_per_master);
        psTCB->origin_y = psTCB->origin_y /
                          (psTCB->uor_per_subunit * psTCB->subunits_per_master);
        psTCB->origin_z = psTCB->origin_z /
                          (psTCB->uor_per_subunit * psTCB->subunits_per_master);
    }

    if (!psDGN->got_tcb)
    {
        psDGN->got_tcb = true;
        psDGN->dimension = psTCB->dimension;
        psDGN->origin_x = psTCB->origin_x;
        psDGN->origin_y = psTCB->origin_y;
        psDGN->origin_z = psTCB->origin_z;

        if (psTCB->uor_per_subunit != 0 && psTCB->subunits_per_master != 0)
            psDGN->scale =
                1.0 / (psTCB->uor_per_subunit * psTCB->subunits_per_master);
    }

    /* Collect views */
    for (int iView = 0; iView < 8; iView++)
    {
        unsigned char *pabyRawView = psDGN->abyElem + 46 + iView * 118;
        DGNViewInfo *psView = psTCB->views + iView;

        psView->flags = pabyRawView[0] + pabyRawView[1] * 256;
        memcpy(psView->levels, pabyRawView + 2, 8);

        psView->origin.x = DGN_INT32(pabyRawView + 10);
        psView->origin.y = DGN_INT32(pabyRawView + 14);
        psView->origin.z = DGN_INT32(pabyRawView + 18);

        DGNTransformPoint(psDGN, &(psView->origin));

        psView->delta.x = DGN_INT32(pabyRawView + 22);
        psView->delta.y = DGN_INT32(pabyRawView + 26);
        psView->delta.z = DGN_INT32(pabyRawView + 30);

        psView->delta.x *= psDGN->scale;
        psView->delta.y *= psDGN->scale;
        psView->delta.z *= psDGN->scale;

        memcpy(psView->transmatrx, pabyRawView + 34, sizeof(double) * 9);
        for (int i = 0; i < 9; i++)
            DGN2IEEEDouble(psView->transmatrx + i);

        memcpy(&(psView->conversion), pabyRawView + 106, sizeof(double));
        DGN2IEEEDouble(&(psView->conversion));

        psView->activez =
            static_cast<unsigned long>(DGN_INT32(pabyRawView + 114));
    }

    return psElement;
}

/************************************************************************/
/*                           DGNFreeElement()                           */
/************************************************************************/

/**
 * Free an element structure.
 *
 * This function will deallocate all resources associated with any element
 * structure returned by DGNReadElement().
 *
 * @param hDGN handle to file from which the element was read.
 * @param psElement the element structure returned by DGNReadElement().
 */

void DGNFreeElement(CPL_UNUSED DGNHandle hDGN, DGNElemCore *psElement)
{
    if (psElement->attr_data != nullptr)
        VSIFree(psElement->attr_data);

    if (psElement->raw_data != nullptr)
        VSIFree(psElement->raw_data);

    if (psElement->stype == DGNST_TAG_SET)
    {
        DGNElemTagSet *psTagSet = (DGNElemTagSet *)psElement;
        CPLFree(psTagSet->tagSetName);

        for (int iTag = 0; iTag < psTagSet->tagCount; iTag++)
        {
            CPLFree(psTagSet->tagList[iTag].name);
            CPLFree(psTagSet->tagList[iTag].prompt);

            if (psTagSet->tagList[iTag].type == 1)
                CPLFree(psTagSet->tagList[iTag].defaultValue.string);
        }
        CPLFree(psTagSet->tagList);
    }
    else if (psElement->stype == DGNST_TAG_VALUE)
    {
        if (((DGNElemTagValue *)psElement)->tagType == 1)
            CPLFree(((DGNElemTagValue *)psElement)->tagValue.string);
    }

    CPLFree(psElement);
}

/************************************************************************/
/*                             DGNRewind()                              */
/************************************************************************/

/**
 * Rewind element reading.
 *
 * Rewind the indicated DGN file, so the next element read with
 * DGNReadElement() will be the first.  Does not require indexing like
 * the more general DGNReadElement() function.
 *
 * @param hDGN handle to file.
 */

void DGNRewind(DGNHandle hDGN)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    VSIRewindL(psDGN->fp);

    psDGN->next_element_id = 0;
    psDGN->in_complex_group = false;
}

/************************************************************************/
/*                         DGNTransformPoint()                          */
/************************************************************************/

void DGNTransformPoint(DGNInfo *psDGN, DGNPoint *psPoint)

{
    psPoint->x = psPoint->x * psDGN->scale - psDGN->origin_x;
    psPoint->y = psPoint->y * psDGN->scale - psDGN->origin_y;
    psPoint->z = psPoint->z * psDGN->scale - psDGN->origin_z;
}

/************************************************************************/
/*                      DGNInverseTransformPoint()                      */
/************************************************************************/

void DGNInverseTransformPoint(DGNInfo *psDGN, DGNPoint *psPoint)

{
    psPoint->x = (psPoint->x + psDGN->origin_x) / psDGN->scale;
    psPoint->y = (psPoint->y + psDGN->origin_y) / psDGN->scale;
    psPoint->z = (psPoint->z + psDGN->origin_z) / psDGN->scale;

    psPoint->x = std::max(-2147483647.0, std::min(2147483647.0, psPoint->x));
    psPoint->y = std::max(-2147483647.0, std::min(2147483647.0, psPoint->y));
    psPoint->z = std::max(-2147483647.0, std::min(2147483647.0, psPoint->z));
}

/************************************************************************/
/*                   DGNInverseTransformPointToInt()                    */
/************************************************************************/

void DGNInverseTransformPointToInt(DGNInfo *psDGN, DGNPoint *psPoint,
                                   unsigned char *pabyTarget)

{
    double adfCT[3] = {(psPoint->x + psDGN->origin_x) / psDGN->scale,
                       (psPoint->y + psDGN->origin_y) / psDGN->scale,
                       (psPoint->z + psDGN->origin_z) / psDGN->scale};

    const int nIter = std::min(3, psDGN->dimension);
    for (int i = 0; i < nIter; i++)
    {
        GInt32 nCTI = static_cast<GInt32>(
            std::max(-2147483647.0, std::min(2147483647.0, adfCT[i])));
        unsigned char abyCTI[4];
        memcpy(abyCTI, &nCTI, sizeof(GInt32));

#ifdef WORDS_BIGENDIAN
        pabyTarget[i * 4 + 0] = abyCTI[1];
        pabyTarget[i * 4 + 1] = abyCTI[0];
        pabyTarget[i * 4 + 2] = abyCTI[3];
        pabyTarget[i * 4 + 3] = abyCTI[2];
#else
        pabyTarget[i * 4 + 3] = abyCTI[1];
        pabyTarget[i * 4 + 2] = abyCTI[0];
        pabyTarget[i * 4 + 1] = abyCTI[3];
        pabyTarget[i * 4 + 0] = abyCTI[2];
#endif
    }
}

/************************************************************************/
/*                             DGNLoadTCB()                             */
/************************************************************************/

/**
 * Load TCB if not already loaded.
 *
 * This function will load the TCB element if it is not already loaded.
 * It is used primarily to ensure the TCB is loaded before doing any operations
 * that require TCB values (like creating new elements).
 *
 * @return FALSE on failure or TRUE on success.
 */

int DGNLoadTCB(DGNHandle hDGN)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    if (psDGN->got_tcb)
        return TRUE;

    while (!psDGN->got_tcb)
    {
        DGNElemCore *psElem = DGNReadElement(hDGN);
        if (psElem == nullptr)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "DGNLoadTCB() - unable to find TCB in file.");
            return FALSE;
        }
        DGNFreeElement(hDGN, psElem);
    }

    return TRUE;
}

/************************************************************************/
/*                         DGNGetElementIndex()                         */
/************************************************************************/

/**
 * Fetch element index.
 *
 * This function will return an array with brief information about every
 * element in a DGN file.  It requires one pass through the entire file to
 * generate (this is not repeated on subsequent calls).
 *
 * The returned array of DGNElementInfo structures contain the level, type,
 * stype, and other flags for each element in the file.  This can facilitate
 * application level code representing the number of elements of various types
 * efficiently.
 *
 * Note that while building the index requires one pass through the whole file,
 * it does not generally request much processing for each element.
 *
 * @param hDGN the file to get an index for.
 * @param pnElementCount the integer to put the total element count into.
 *
 * @return a pointer to an internal array of DGNElementInfo structures (there
 * will be *pnElementCount entries in the array), or NULL on failure.  The
 * returned array should not be modified or freed, and will last only as long
 * as the DGN file remains open.
 */

const DGNElementInfo *DGNGetElementIndex(DGNHandle hDGN, int *pnElementCount)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    DGNBuildIndex(psDGN);

    if (pnElementCount != nullptr)
        *pnElementCount = psDGN->element_count;

    return psDGN->element_index;
}

/************************************************************************/
/*                           DGNGetExtents()                            */
/************************************************************************/

/**
 * Fetch overall file extents.
 *
 * The extents are collected for each element while building an index, so
 * if an index has not already been built, it will be built when
 * DGNGetExtents() is called.
 *
 * The Z min/max values are generally meaningless (0 and 0xffffffff in uor
 * space).
 *
 * @param hDGN the file to get extents for.
 * @param padfExtents pointer to an array of six doubles into which are loaded
 * the values xmin, ymin, zmin, xmax, ymax, and zmax.
 *
 * @return TRUE on success or FALSE on failure.
 */

int DGNGetExtents(DGNHandle hDGN, double *padfExtents)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    DGNBuildIndex(psDGN);

    if (!psDGN->got_bounds)
        return FALSE;

    DGNPoint sMin = {psDGN->min_x - 2147483648.0, psDGN->min_y - 2147483648.0,
                     psDGN->min_z - 2147483648.0};

    DGNTransformPoint(psDGN, &sMin);

    padfExtents[0] = sMin.x;
    padfExtents[1] = sMin.y;
    padfExtents[2] = sMin.z;

    DGNPoint sMax = {psDGN->max_x - 2147483648.0, psDGN->max_y - 2147483648.0,
                     psDGN->max_z - 2147483648.0};

    DGNTransformPoint(psDGN, &sMax);

    padfExtents[3] = sMax.x;
    padfExtents[4] = sMax.y;
    padfExtents[5] = sMax.z;

    return TRUE;
}

/************************************************************************/
/*                           DGNBuildIndex()                            */
/************************************************************************/

void DGNBuildIndex(DGNInfo *psDGN)

{
    if (psDGN->index_built)
        return;

    int nType = 0;
    int nLevel = 0;
    GUInt32 anRegion[6] = {};

    psDGN->index_built = true;

    DGNRewind(psDGN);

    int nMaxElements = 0;

    vsi_l_offset nLastOffset = VSIFTellL(psDGN->fp);
    while (DGNLoadRawElement(psDGN, &nType, &nLevel))
    {
        if (psDGN->element_count == nMaxElements)
        {
            nMaxElements = (int)(nMaxElements * 1.5) + 500;

            psDGN->element_index = (DGNElementInfo *)CPLRealloc(
                psDGN->element_index, nMaxElements * sizeof(DGNElementInfo));
        }

        DGNElementInfo *psEI = psDGN->element_index + psDGN->element_count;
        psEI->level = (unsigned char)nLevel;
        psEI->type = (unsigned char)nType;
        psEI->flags = 0;
        psEI->offset = nLastOffset;

        if (psDGN->abyElem[0] & 0x80)
            psEI->flags |= DGNEIF_COMPLEX;

        if (psDGN->abyElem[1] & 0x80)
            psEI->flags |= DGNEIF_DELETED;

        if (nType == DGNT_LINE || nType == DGNT_LINE_STRING ||
            nType == DGNT_SHAPE || nType == DGNT_CURVE ||
            nType == DGNT_BSPLINE_POLE)
            psEI->stype = DGNST_MULTIPOINT;

        else if (nType == DGNT_GROUP_DATA && nLevel == DGN_GDL_COLOR_TABLE)
        {
            DGNElemCore *psCT = DGNParseColorTable(psDGN);
            DGNFreeElement((DGNHandle)psDGN, psCT);
            psEI->stype = DGNST_COLORTABLE;
        }
        else if (nType == DGNT_ELLIPSE || nType == DGNT_ARC)
            psEI->stype = DGNST_ARC;

        else if (nType == DGNT_COMPLEX_SHAPE_HEADER ||
                 nType == DGNT_COMPLEX_CHAIN_HEADER ||
                 nType == DGNT_3DSURFACE_HEADER || nType == DGNT_3DSOLID_HEADER)
            psEI->stype = DGNST_COMPLEX_HEADER;

        else if (nType == DGNT_TEXT)
            psEI->stype = DGNST_TEXT;

        else if (nType == DGNT_TAG_VALUE)
            psEI->stype = DGNST_TAG_VALUE;

        else if (nType == DGNT_APPLICATION_ELEM)
        {
            if (nLevel == 24)
                psEI->stype = DGNST_TAG_SET;
            else
                psEI->stype = DGNST_CORE;
        }
        else if (nType == DGNT_TCB)
        {
            DGNElemCore *psTCB = DGNParseTCB(psDGN);
            DGNFreeElement((DGNHandle)psDGN, psTCB);
            psEI->stype = DGNST_TCB;
        }
        else if (nType == DGNT_CONE)
            psEI->stype = DGNST_CONE;
        else
            psEI->stype = DGNST_CORE;

        if (!(psEI->flags & DGNEIF_DELETED) &&
            !(psEI->flags & DGNEIF_COMPLEX) &&
            DGNGetRawExtents(psDGN, nType, nullptr, anRegion + 0, anRegion + 1,
                             anRegion + 2, anRegion + 3, anRegion + 4,
                             anRegion + 5))
        {
#ifdef notdef
            printf("panRegion[%d]=%.1f,%.1f,%.1f,%.1f,%.1f,%.1f\n", /*ok*/
                   psDGN->element_count, anRegion[0] - 2147483648.0,
                   anRegion[1] - 2147483648.0, anRegion[2] - 2147483648.0,
                   anRegion[3] - 2147483648.0, anRegion[4] - 2147483648.0,
                   anRegion[5] - 2147483648.0);
#endif
            if (psDGN->got_bounds)
            {
                psDGN->min_x = std::min(psDGN->min_x, anRegion[0]);
                psDGN->min_y = std::min(psDGN->min_y, anRegion[1]);
                psDGN->min_z = std::min(psDGN->min_z, anRegion[2]);
                psDGN->max_x = std::max(psDGN->max_x, anRegion[3]);
                psDGN->max_y = std::max(psDGN->max_y, anRegion[4]);
                psDGN->max_z = std::max(psDGN->max_z, anRegion[5]);
            }
            else
            {
                psDGN->min_x = anRegion[0];
                psDGN->min_y = anRegion[1];
                psDGN->min_z = anRegion[2];
                psDGN->max_x = anRegion[3];
                psDGN->max_y = anRegion[4];
                psDGN->max_z = anRegion[5];
                psDGN->got_bounds = true;
            }
        }

        psDGN->element_count++;

        nLastOffset = VSIFTellL(psDGN->fp);
    }

    DGNRewind(psDGN);

    psDGN->max_element_count = nMaxElements;
}
