/******************************************************************************
 *
 * Project:  Microstation DGN Access Library
 * Purpose:  DGN Access functions related to writing DGN elements.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "dgnlibp.h"

#include <cmath>

#include <algorithm>

CPL_CVSID("$Id: dgnwrite.cpp 9c23a3df3e508de5558886ed45f1bb270f3bfaf8 2019-08-15 15:46:57 +0200 Even Rouault $")

static void DGNPointToInt( DGNInfo *psDGN, DGNPoint *psPoint,
                           unsigned char *pabyTarget );

/************************************************************************/
/*                          DGNResizeElement()                          */
/************************************************************************/

/**
 * Resize an existing element.
 *
 * If the new size is the same as the old nothing happens.
 *
 * Otherwise, the old element in the file is marked as deleted, and the
 * DGNElemCore.offset and element_id are set to -1 indicating that the
 * element should be written to the end of file when next written by
 * DGNWriteElement().  The internal raw data buffer is updated to the new
 * size.
 *
 * Only elements with "raw_data" loaded may be moved.
 *
 * In normal use the DGNResizeElement() call would be called on a previously
 * loaded element, and afterwards the raw_data would be updated before calling
 * DGNWriteElement().  If DGNWriteElement() isn't called after
 * DGNResizeElement() then the element will be lost having been marked as
 * deleted in its old position but never written at the new location.
 *
 * @param hDGN the DGN file on which the element lives.
 * @param psElement the element to alter.
 * @param nNewSize the desired new size of the element in bytes.  Must be
 * a multiple of 2.
 *
 * @return TRUE on success, or FALSE on error.
 */

int DGNResizeElement( DGNHandle hDGN, DGNElemCore *psElement, int nNewSize )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;

/* -------------------------------------------------------------------- */
/*      Check various conditions.                                       */
/* -------------------------------------------------------------------- */
    if( psElement->raw_bytes == 0
        || psElement->raw_bytes != psElement->size )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Raw bytes not loaded, or not matching element size." );
        return FALSE;
    }

    if( nNewSize % 2 == 1 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "DGNResizeElement(%d): "
                  "can't change to odd (not divisible by two) size.",
                  nNewSize );
        return FALSE;
    }

    if( nNewSize == psElement->raw_bytes )
        return TRUE;

/* -------------------------------------------------------------------- */
/*      Mark the existing element as deleted if the element has to      */
/*      move to the end of the file.                                    */
/* -------------------------------------------------------------------- */

    if( psElement->offset != -1 )
    {
        vsi_l_offset nOldFLoc = VSIFTellL( psDGN->fp );
        unsigned char abyLeader[2];

        if( VSIFSeekL( psDGN->fp, psElement->offset, SEEK_SET ) != 0
            || VSIFReadL( abyLeader, sizeof(abyLeader), 1, psDGN->fp ) != 1 )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Failed seek or read when trying to mark existing\n"
                      "element as deleted in DGNResizeElement()\n" );
            return FALSE;
        }

        abyLeader[1] |= 0x80;

        if( VSIFSeekL( psDGN->fp, psElement->offset, SEEK_SET ) != 0
            || VSIFWriteL( abyLeader, sizeof(abyLeader), 1, psDGN->fp ) != 1
            || VSIFSeekL( psDGN->fp, nOldFLoc, SEEK_SET ) != 0 )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Failed seek or write when trying to mark existing\n"
                      "element as deleted in DGNResizeElement()\n" );
            return FALSE;
        }

        if( psElement->element_id != -1 && psDGN->index_built )
            psDGN->element_index[psElement->element_id].flags
                |= DGNEIF_DELETED;
    }

    psElement->offset = -1; /* move to end of file. */
    psElement->element_id = -1;

/* -------------------------------------------------------------------- */
/*      Set the new size information, and realloc the raw data buffer.  */
/* -------------------------------------------------------------------- */
    psElement->size = nNewSize;
    psElement->raw_data = (unsigned char *)
        CPLRealloc( psElement->raw_data, nNewSize );
    psElement->raw_bytes = nNewSize;

/* -------------------------------------------------------------------- */
/*      Update the size information within the raw buffer.              */
/* -------------------------------------------------------------------- */
    const int nWords = (nNewSize / 2) - 2;

    psElement->raw_data[2] = (unsigned char) (nWords % 256);
    psElement->raw_data[3] = (unsigned char) (nWords / 256);

    return TRUE;
}

/************************************************************************/
/*                          DGNWriteElement()                           */
/************************************************************************/

/**
 * Write element to file.
 *
 * Only elements with "raw_data" loaded may be written.  This should
 * include elements created with the various DGNCreate*() functions, and
 * those read from the file with the DGNO_CAPTURE_RAW_DATA flag turned on
 * with DGNSetOptions().
 *
 * The passed element is written to the indicated file.  If the
 * DGNElemCore.offset field is -1 then the element is written at the end of
 * the file (and offset/element are reset properly) otherwise the element
 * is written back to the location indicated by DGNElemCore.offset.
 *
 * If the element is added at the end of the file, and if an element index
 * has already been built, it will be updated to reference the new element.
 *
 * This function takes care of ensuring that the end-of-file marker is
 * maintained after the last element.
 *
 * @param hDGN the file to write the element to.
 * @param psElement the element to write.
 *
 * @return TRUE on success or FALSE in case of failure.
 */

int DGNWriteElement( DGNHandle hDGN, DGNElemCore *psElement )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;

/* ==================================================================== */
/*      If this element hasn't been positioned yet, place it at the     */
/*      end of the file.                                                */
/* ==================================================================== */
    if( psElement->offset == -1 )
    {
        // We must have an index, in order to properly assign the
        // element id of the newly written element.  Ensure it is built.
        if( !psDGN->index_built )
            DGNBuildIndex( psDGN );

        // Read the current "last" element.
        if( !DGNGotoElement( hDGN, psDGN->element_count-1 ) )
            return FALSE;

        int nJunk = 0;
        if( !DGNLoadRawElement( psDGN, &nJunk, &nJunk ) )
            return FALSE;

        // Establish the position of the new element.
        psElement->offset = static_cast<int>(VSIFTellL( psDGN->fp ));
        psElement->element_id = psDGN->element_count;

        // Grow element buffer if needed.
        if( psDGN->element_count == psDGN->max_element_count )
        {
            psDGN->max_element_count += 500;

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

        // Set up the element info
        DGNElementInfo *psInfo = psDGN->element_index + psDGN->element_count;
        psInfo->level = (unsigned char) psElement->level;
        psInfo->type = (unsigned char) psElement->type;
        psInfo->stype = (unsigned char) psElement->stype;
        psInfo->offset = psElement->offset;
        if( psElement->complex )
            psInfo->flags = DGNEIF_COMPLEX;
        else
            psInfo->flags = 0;

        psDGN->element_count++;
    }

/* -------------------------------------------------------------------- */
/*      Write out the element.                                          */
/* -------------------------------------------------------------------- */
    if( VSIFSeekL( psDGN->fp, psElement->offset, SEEK_SET ) != 0
        || VSIFWriteL( psElement->raw_data, psElement->raw_bytes,
                      1, psDGN->fp) != 1 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Error seeking or writing new element of %d bytes at %d.",
                  psElement->offset,
                  psElement->raw_bytes );
        return FALSE;
    }

    psDGN->next_element_id = psElement->element_id + 1;

/* -------------------------------------------------------------------- */
/*      Write out the end of file 0xffff marker (if we were             */
/*      extending the file), but push the file pointer back before      */
/*      this EOF when done.                                             */
/* -------------------------------------------------------------------- */
    if( psDGN->next_element_id == psDGN->element_count )
    {
        const unsigned char abyEOF[2] = { 0xff, 0xff };

        VSIFWriteL( abyEOF, 2, 1, psDGN->fp );
        VSIFSeekL( psDGN->fp, VSIFTellL(psDGN->fp)-2, SEEK_SET );
    }

    return TRUE;
}

/************************************************************************/
/*                             DGNCreate()                              */
/************************************************************************/

/**
 * Create new DGN file.
 *
 * This function will create a new DGN file based on the provided seed
 * file, and return a handle on which elements may be read and written.
 *
 * The following creation flags may be passed:
 * <ul>
 * <li> DGNCF_USE_SEED_UNITS: The master and subunit resolutions and names
 * from the seed file will be used in the new file.  The nMasterUnitPerSubUnit,
 * nUORPerSubUnit, pszMasterUnits, and pszSubUnits arguments will be ignored.
 * <li> DGNCF_USE_SEED_ORIGIN: The origin from the seed file will be used
 * and the X, Y and Z origin passed into the call will be ignored.
 * <li> DGNCF_COPY_SEED_FILE_COLOR_TABLE: Should the first color table occurring
 * in the seed file also be copied?
 * <li> DGNCF_COPY_WHOLE_SEED_FILE: By default only the first three elements
 * (TCB, Digitizer Setup and Level Symbology) are copied from the seed file.
 * If this flag is provided the entire seed file is copied verbatim (with the
 * TCB origin and units possibly updated).
 * </ul>
 *
 * @param pszNewFilename the filename to create.  If it already exists
 * it will be overwritten.
 * @param pszSeedFile the seed file to copy header from.
 * @param nCreationFlags An ORing of DGNCF_* flags that are to take effect.
 * @param dfOriginX the X origin for the file.
 * @param dfOriginY the Y origin for the file.
 * @param dfOriginZ the Z origin for the file.
 * @param nSubUnitsPerMasterUnit the number of subunits in one master unit.
 * @param nUORPerSubUnit the number of UOR (units of resolution) per subunit.
 * @param pszMasterUnits the name of the master units (2 characters).
 * @param pszSubUnits the name of the subunits (2 characters).
 */

DGNHandle
      DGNCreate( const char *pszNewFilename, const char *pszSeedFile,
                 int nCreationFlags,
                 double dfOriginX, double dfOriginY, double dfOriginZ,
                 int nSubUnitsPerMasterUnit, int nUORPerSubUnit,
                 const char *pszMasterUnits, const char *pszSubUnits )

{
/* -------------------------------------------------------------------- */
/*      Open output file.                                               */
/* -------------------------------------------------------------------- */
    VSILFILE *fpNew = VSIFOpenL( pszNewFilename, "wb" );
    if( fpNew == nullptr )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Failed to open output file: %s", pszNewFilename );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Open seed file, and read TCB element.                           */
/* -------------------------------------------------------------------- */
    DGNInfo *psSeed = (DGNInfo *) DGNOpen( pszSeedFile, FALSE );
    if( psSeed == nullptr )
    {
        VSIFCloseL( fpNew );
        return nullptr;
    }

    DGNSetOptions( psSeed, DGNO_CAPTURE_RAW_DATA );

    DGNElemCore *psSrcTCB = DGNReadElement( psSeed );

    CPLAssert( psSrcTCB->raw_bytes >= 1536 );

/* -------------------------------------------------------------------- */
/*      Modify TCB appropriately for the output file.                   */
/* -------------------------------------------------------------------- */
    GByte *pabyRawTCB = static_cast<GByte *>(CPLMalloc(psSrcTCB->raw_bytes));

    memcpy( pabyRawTCB, psSrcTCB->raw_data, psSrcTCB->raw_bytes );

    if( !(nCreationFlags & DGNCF_USE_SEED_UNITS) )
    {
        memcpy( pabyRawTCB+1120, pszMasterUnits, 2 );
        memcpy( pabyRawTCB+1122, pszSubUnits, 2 );

        DGN_WRITE_INT32( nUORPerSubUnit, pabyRawTCB+1116 );
        DGN_WRITE_INT32( nSubUnitsPerMasterUnit,pabyRawTCB+1112);
    }
    else
    {
        nUORPerSubUnit = DGN_INT32( pabyRawTCB+1116 );
        nSubUnitsPerMasterUnit = DGN_INT32( pabyRawTCB+1112 );
    }

    if( !(nCreationFlags & DGNCF_USE_SEED_ORIGIN) )
    {
        dfOriginX *= (nUORPerSubUnit * nSubUnitsPerMasterUnit);
        dfOriginY *= (nUORPerSubUnit * nSubUnitsPerMasterUnit);
        dfOriginZ *= (nUORPerSubUnit * nSubUnitsPerMasterUnit);

        memcpy( pabyRawTCB+1240, &dfOriginX, 8 );
        memcpy( pabyRawTCB+1248, &dfOriginY, 8 );
        memcpy( pabyRawTCB+1256, &dfOriginZ, 8 );

        IEEE2DGNDouble( pabyRawTCB+1240 );
        IEEE2DGNDouble( pabyRawTCB+1248 );
        IEEE2DGNDouble( pabyRawTCB+1256 );
    }

/* -------------------------------------------------------------------- */
/*      Write TCB and EOF to new file.                                  */
/* -------------------------------------------------------------------- */
    VSIFWriteL( pabyRawTCB, psSrcTCB->raw_bytes, 1, fpNew );
    CPLFree( pabyRawTCB );

    unsigned char abyEOF[2] = { 0xff,  0xff };

    VSIFWriteL( abyEOF, 2, 1, fpNew );

    DGNFreeElement( psSeed, psSrcTCB );

/* -------------------------------------------------------------------- */
/*      Close and re-open using DGN API.                                */
/* -------------------------------------------------------------------- */
    VSIFCloseL( fpNew );

    DGNInfo *psDGN = (DGNInfo *) DGNOpen( pszNewFilename, TRUE );

/* -------------------------------------------------------------------- */
/*      Now copy over elements according to options in effect.          */
/* -------------------------------------------------------------------- */
    DGNElemCore *psSrcElement = nullptr;
    DGNElemCore *psDstElement = nullptr;

    while( (psSrcElement = DGNReadElement( psSeed )) != nullptr )
    {
        if( (nCreationFlags & DGNCF_COPY_WHOLE_SEED_FILE)
            || (psSrcElement->stype == DGNST_COLORTABLE
                && nCreationFlags & DGNCF_COPY_SEED_FILE_COLOR_TABLE)
            || psSrcElement->element_id <= 2 )
        {
            psDstElement = DGNCloneElement( psSeed, psDGN, psSrcElement );
            DGNWriteElement( psDGN, psDstElement );
            DGNFreeElement( psDGN, psDstElement );
        }

        DGNFreeElement( psSeed, psSrcElement );
    }

    DGNClose( psSeed );

    return psDGN;
}

/************************************************************************/
/*                          DGNCloneElement()                           */
/************************************************************************/

/**
 * Clone a retargeted element.
 *
 * Creates a copy of an element in a suitable form to write to a
 * different file than that it was read from.
 *
 * NOTE: At this time the clone operation will fail if the source
 * and destination file have a different origin or master/sub units.
 *
 * @param hDGNSrc the source file (from which psSrcElement was read).
 * @param hDGNDst the destination file (to which the returned element may be
 * written).
 * @param psSrcElement the element to be cloned (from hDGNSrc).
 *
 * @return NULL on failure, or an appropriately modified copy of
 * the source element suitable to write to hDGNDst.
 */

DGNElemCore *DGNCloneElement( CPL_UNUSED DGNHandle hDGNSrc,
                              DGNHandle hDGNDst,
                              DGNElemCore *psSrcElement )

{
    DGNElemCore *psClone = nullptr;

    DGNLoadTCB( hDGNDst );

/* -------------------------------------------------------------------- */
/*      Per structure specific copying.  The core is fixed up later.    */
/* -------------------------------------------------------------------- */
    if( psSrcElement->stype == DGNST_CORE )
    {
        psClone = static_cast<DGNElemCore *>(CPLMalloc(sizeof(DGNElemCore)));
        memcpy( psClone, psSrcElement, sizeof(DGNElemCore) );
    }
    else if( psSrcElement->stype == DGNST_MULTIPOINT )
    {
        DGNElemMultiPoint *psSrcMP = (DGNElemMultiPoint *) psSrcElement;

        const size_t nSize = sizeof(DGNElemMultiPoint)
            + sizeof(DGNPoint) * (psSrcMP->num_vertices-1);

        DGNElemMultiPoint *psMP =
            static_cast<DGNElemMultiPoint *>(CPLMalloc( nSize ));
        memcpy( psMP, psSrcElement, nSize );

        psClone = (DGNElemCore *) psMP;
    }
    else if( psSrcElement->stype == DGNST_ARC )
    {
        DGNElemArc *psArc =
            static_cast<DGNElemArc *>(CPLMalloc(sizeof(DGNElemArc)));
        memcpy( psArc, psSrcElement, sizeof(DGNElemArc) );

        psClone = (DGNElemCore *) psArc;
    }
    else if( psSrcElement->stype == DGNST_TEXT )
    {
        DGNElemText *psSrcText = (DGNElemText *) psSrcElement;
        const size_t nSize = sizeof(DGNElemText) + strlen(psSrcText->string);

        DGNElemText *psText =
          static_cast<DGNElemText *>(CPLMalloc( nSize ));
        memcpy( psText, psSrcElement, nSize );

        psClone = (DGNElemCore *) psText;
    }
    else if( psSrcElement->stype == DGNST_TEXT_NODE )
    {
        DGNElemTextNode *psNode = static_cast<DGNElemTextNode *>(
            CPLMalloc(sizeof(DGNElemTextNode)));
        memcpy( psNode, psSrcElement, sizeof(DGNElemTextNode) );

        psClone = (DGNElemCore *) psNode;
    }
    else if( psSrcElement->stype == DGNST_COMPLEX_HEADER )
    {
        DGNElemComplexHeader *psCH = static_cast<DGNElemComplexHeader *>(
            CPLMalloc(sizeof(DGNElemComplexHeader)));
        memcpy( psCH, psSrcElement, sizeof(DGNElemComplexHeader) );

        psClone = (DGNElemCore *) psCH;
    }
    else if( psSrcElement->stype == DGNST_COLORTABLE )
    {
        DGNElemColorTable *psCT = static_cast<DGNElemColorTable *>(
            CPLMalloc(sizeof(DGNElemColorTable)));
        memcpy( psCT, psSrcElement, sizeof(DGNElemColorTable) );

        psClone = (DGNElemCore *) psCT;
    }
    else if( psSrcElement->stype == DGNST_TCB )
    {
        DGNElemTCB *psTCB =
            static_cast<DGNElemTCB *>(CPLMalloc(sizeof(DGNElemTCB)));
        memcpy( psTCB, psSrcElement, sizeof(DGNElemTCB) );

        psClone = (DGNElemCore *) psTCB;
    }
    else if( psSrcElement->stype == DGNST_CELL_HEADER )
    {
        DGNElemCellHeader *psCH = static_cast<DGNElemCellHeader *>(
            CPLMalloc(sizeof(DGNElemCellHeader)));
        memcpy( psCH, psSrcElement, sizeof(DGNElemCellHeader) );

        psClone = (DGNElemCore *) psCH;
    }
    else if( psSrcElement->stype == DGNST_CELL_LIBRARY )
    {
        DGNElemCellLibrary *psCL = static_cast<DGNElemCellLibrary *>(
            CPLMalloc(sizeof(DGNElemCellLibrary)));
        memcpy( psCL, psSrcElement, sizeof(DGNElemCellLibrary) );

        psClone = (DGNElemCore *) psCL;
    }
    else if( psSrcElement->stype == DGNST_TAG_VALUE )
    {
        DGNElemTagValue *psTV = static_cast<DGNElemTagValue *>(
            CPLMalloc(sizeof(DGNElemTagValue)));
        memcpy( psTV, psSrcElement, sizeof(DGNElemTagValue) );

        if( psTV->tagType == 1 )
            psTV->tagValue.string = CPLStrdup( psTV->tagValue.string );

        psClone = (DGNElemCore *) psTV;
    }
    else if( psSrcElement->stype == DGNST_TAG_SET )
    {
        DGNElemTagSet *psTS = static_cast<DGNElemTagSet *>(
            CPLMalloc(sizeof(DGNElemTagSet)));
        memcpy( psTS, psSrcElement, sizeof(DGNElemTagSet) );

        psTS->tagSetName = CPLStrdup( psTS->tagSetName );

        DGNTagDef *pasTagList = static_cast<DGNTagDef *>(
            CPLMalloc( sizeof(DGNTagDef) * psTS->tagCount ));
        memcpy( pasTagList, psTS->tagList,
                sizeof(DGNTagDef) * psTS->tagCount );

        for( int iTag = 0; iTag < psTS->tagCount; iTag++ )
        {
            pasTagList[iTag].name = CPLStrdup( pasTagList[iTag].name );
            pasTagList[iTag].prompt = CPLStrdup( pasTagList[iTag].prompt );
            if( pasTagList[iTag].type == 1 )
                pasTagList[iTag].defaultValue.string =
                    CPLStrdup( pasTagList[iTag].defaultValue.string);
        }

        psTS->tagList = pasTagList;
        psClone = (DGNElemCore *) psTS;
    }
    else if( psSrcElement->stype == DGNST_CONE )
    {
        DGNElemCone *psCone = static_cast<DGNElemCone *>(
            CPLMalloc(sizeof(DGNElemCone)));
        memcpy( psCone, psSrcElement, sizeof(DGNElemCone) );

        psClone = (DGNElemCore *) psCone;
    }
    else if( psSrcElement->stype == DGNST_BSPLINE_SURFACE_HEADER )
    {
        DGNElemBSplineSurfaceHeader *psSurface =
            static_cast<DGNElemBSplineSurfaceHeader *>(
                CPLMalloc(sizeof(DGNElemBSplineSurfaceHeader)));
        memcpy( psSurface, psSrcElement, sizeof(DGNElemBSplineSurfaceHeader) );

        psClone = (DGNElemCore *) psSurface;
    }
    else if( psSrcElement->stype == DGNST_BSPLINE_CURVE_HEADER )
    {
        DGNElemBSplineCurveHeader *psCurve =
            static_cast<DGNElemBSplineCurveHeader *>(
                CPLMalloc(sizeof(DGNElemBSplineCurveHeader)));
        memcpy( psCurve, psSrcElement, sizeof(DGNElemBSplineCurveHeader) );

        psClone = (DGNElemCore *) psCurve;
    }
    else if( psSrcElement->stype == DGNST_BSPLINE_SURFACE_BOUNDARY )
    {
        DGNElemBSplineSurfaceBoundary *psSrcBSB =
            (DGNElemBSplineSurfaceBoundary *) psSrcElement;

        const size_t nSize = sizeof(DGNElemBSplineSurfaceBoundary)
            + sizeof(DGNPoint) * (psSrcBSB->numverts-1);

        DGNElemBSplineSurfaceBoundary *psBSB =
            static_cast<DGNElemBSplineSurfaceBoundary *>(
                CPLMalloc( nSize ));
        memcpy( psBSB, psSrcElement, nSize );

        psClone = (DGNElemCore *) psBSB;
    }
    else if( psSrcElement->stype == DGNST_KNOT_WEIGHT )
    {
        // FIXME: Is it OK to assume that the # of elements corresponds
        // directly to the element size? kintel 20051218.
        const int numelems =
            (psSrcElement->size - 36 - psSrcElement->attr_bytes)/4;

        /* DGNElemKnotWeight *psSrcArray = (DGNElemKnotWeight *) psSrcElement; */

        const size_t nSize =
            sizeof(DGNElemKnotWeight) + sizeof(long) * (numelems-1);

        DGNElemKnotWeight *psArray = static_cast<DGNElemKnotWeight *>(
            CPLMalloc( nSize ));
        memcpy( psArray, psSrcElement, nSize );

        psClone = (DGNElemCore *) psArray;
    }
    else if( psSrcElement->stype == DGNST_SHARED_CELL_DEFN )
    {
        DGNElemSharedCellDefn *psCH = static_cast<DGNElemSharedCellDefn *>(
            CPLMalloc(sizeof(DGNElemSharedCellDefn)));
        memcpy( psCH, psSrcElement, sizeof(DGNElemSharedCellDefn) );

        psClone = (DGNElemCore *) psCH;
    }
    else
    {
        CPLAssert( false );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Copy core raw data, and attributes.                             */
/* -------------------------------------------------------------------- */
    if( psClone->raw_bytes != 0 )
    {
        psClone->raw_data = static_cast<unsigned char *>(
            CPLMalloc(psClone->raw_bytes));
        memcpy( psClone->raw_data, psSrcElement->raw_data,
                psClone->raw_bytes );
    }

    if( psClone->attr_bytes != 0 )
    {
        psClone->attr_data = static_cast<unsigned char *>(
            CPLMalloc(psClone->attr_bytes));
        memcpy( psClone->attr_data, psSrcElement->attr_data,
                psClone->attr_bytes );
    }

/* -------------------------------------------------------------------- */
/*      Clear location and id information.                              */
/* -------------------------------------------------------------------- */
    psClone->offset = -1;
    psClone->element_id = -1;

    return psClone;
}

/************************************************************************/
/*                         DGNUpdateElemCore()                          */
/************************************************************************/

/**
 * Change element core values.
 *
 * The indicated values in the element are updated in the structure, as well
 * as in the raw data.  The updated element is not written to disk.  That
 * must be done with DGNWriteElement().   The element must have raw_data
 * loaded.
 *
 * @param hDGN the file on which the element belongs.
 * @param psElement the element to modify.
 * @param nLevel the new level value.
 * @param nGraphicGroup the new graphic group value.
 * @param nColor the new color index.
 * @param nWeight the new element weight.
 * @param nStyle the new style value for the element.
 *
 * @return Returns TRUE on success or FALSE on failure.
 */

int DGNUpdateElemCore( DGNHandle hDGN, DGNElemCore *psElement,
                       int nLevel, int nGraphicGroup, int nColor,
                       int nWeight, int nStyle )

{
    psElement->level = nLevel;
    psElement->graphic_group = nGraphicGroup;
    psElement->color = nColor;
    psElement->weight = nWeight;
    psElement->style = nStyle;

    return DGNUpdateElemCoreExtended( hDGN, psElement );
}

/************************************************************************/
/*                     DGNUpdateElemCoreExtended()                      */
/************************************************************************/

/**
 * Update internal raw data representation.
 *
 * The raw_data representation of the passed element is updated to reflect
 * the various core fields.  The DGNElemCore level, type, complex, deleted,
 * graphic_group, properties, color, weight and style values are all
 * applied to the raw_data representation.  Spatial bounds, element type
 * specific information and attributes are not updated in the raw data.
 *
 * @param hDGN the file to which the element belongs.
 * @param psElement the element to be updated.
 *
 * @return TRUE on success, or FALSE on failure.
 */

int DGNUpdateElemCoreExtended( CPL_UNUSED DGNHandle hDGN,
                               DGNElemCore *psElement )
{
    GByte *rd = psElement->raw_data;
    const int nWords = (psElement->raw_bytes / 2) - 2;

    if( psElement->raw_data == nullptr
        || psElement->raw_bytes < 36 )
    {
        CPLAssert( false );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Setup first four bytes.                                         */
/* -------------------------------------------------------------------- */
    rd[0] = (GByte) psElement->level;
    if( psElement->complex )
        rd[0] |= 0x80;

    rd[1] = (GByte) psElement->type;
    if( psElement->deleted )
        rd[1] |= 0x80;

    rd[2] = (GByte) (nWords % 256);
    rd[3] = (GByte) (nWords / 256);

/* -------------------------------------------------------------------- */
/*      If the attribute offset hasn't been set, set it now under       */
/*      the assumption it should point to the end of the element.       */
/* -------------------------------------------------------------------- */
    if( psElement->raw_data[30] == 0 && psElement->raw_data[31] == 0 )
    {
        const int nAttIndex = (psElement->raw_bytes - 32) / 2;

        psElement->raw_data[30] = (GByte) (nAttIndex % 256);
        psElement->raw_data[31] = (GByte) (nAttIndex / 256);
    }
/* -------------------------------------------------------------------- */
/*      Handle the graphic properties.                                  */
/* -------------------------------------------------------------------- */
    if( psElement->raw_bytes > 36 && DGNElemTypeHasDispHdr( psElement->type ) )
    {
        rd[28] = (GByte) (psElement->graphic_group % 256);
        rd[29] = (GByte) (psElement->graphic_group / 256);
        rd[32] = (GByte) (psElement->properties % 256);
        rd[33] = (GByte) (psElement->properties / 256);
        rd[34] = (GByte) (psElement->style | (psElement->weight << 3));
        rd[35] = (GByte) psElement->color;
    }

    return TRUE;
}

/************************************************************************/
/*                         DGNInitializeElemCore()                      */
/************************************************************************/

static void DGNInitializeElemCore( CPL_UNUSED DGNHandle hDGN,
                                   DGNElemCore *psElement )
{
    memset( psElement, 0, sizeof(DGNElemCore) );

    psElement->offset = -1;
    psElement->element_id = -1;
}

/************************************************************************/
/*                           DGNWriteBounds()                           */
/*                                                                      */
/*      Write bounds to element raw data.                               */
/************************************************************************/

static void DGNWriteBounds( DGNInfo *psInfo, DGNElemCore *psElement,
                            DGNPoint *psMin, DGNPoint *psMax )

{
    CPLAssert( psElement->raw_bytes >= 28 );

    DGNInverseTransformPointToInt( psInfo, psMin, psElement->raw_data + 4 );
    DGNInverseTransformPointToInt( psInfo, psMax, psElement->raw_data + 16 );

    /* convert from twos complement to "binary offset" format. */

    psElement->raw_data[5] ^= 0x80;
    psElement->raw_data[9] ^= 0x80;
    psElement->raw_data[13] ^= 0x80;
    psElement->raw_data[17] ^= 0x80;
    psElement->raw_data[21] ^= 0x80;
    psElement->raw_data[25] ^= 0x80;
}

/************************************************************************/
/*                      DGNCreateMultiPointElem()                       */
/************************************************************************/

/**
 * Create new multi-point element.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * NOTE: There are restrictions on the nPointCount for some elements. For
 * instance, DGNT_LINE can only have 2 points. Maximum element size
 * precludes very large numbers of points.
 *
 * @param hDGN the file on which the element will eventually be written.
 * @param nType the type of the element to be created.  It must be one of
 * DGNT_LINE, DGNT_LINE_STRING, DGNT_SHAPE, DGNT_CURVE or DGNT_BSPLINE_POLE.
 * @param nPointCount the number of points in the pasVertices list.
 * @param pasVertices the list of points to be written.
 *
 * @return the new element (a DGNElemMultiPoint structure) or NULL on failure.
 */

DGNElemCore *DGNCreateMultiPointElem( DGNHandle hDGN, int nType,
                                      int nPointCount, DGNPoint *pasVertices )

{
    DGNInfo *psDGN = (DGNInfo *) hDGN;

    CPLAssert( nType == DGNT_LINE
               || nType == DGNT_LINE_STRING
               || nType == DGNT_SHAPE
               || nType == DGNT_CURVE
               || nType == DGNT_BSPLINE_POLE );

    DGNLoadTCB( hDGN );

/* -------------------------------------------------------------------- */
/*      Is this too many vertices to write to a single element?         */
/* -------------------------------------------------------------------- */
    if( nPointCount > 101 )
    {
        CPLError( CE_Failure, CPLE_ElementTooBig,
                  "Attempt to create %s element with %d points failed.\n"
                  "Element would be too large.",
                  DGNTypeToName( nType ), nPointCount );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Allocate element.                                               */
/* -------------------------------------------------------------------- */
    DGNElemMultiPoint *psMP = static_cast<DGNElemMultiPoint *>(
        CPLCalloc( sizeof(DGNElemMultiPoint)
                   + sizeof(DGNPoint) * (nPointCount-1), 1 ));
    DGNElemCore *psCore = &(psMP->core);

    DGNInitializeElemCore( hDGN, psCore );
    psCore->stype = DGNST_MULTIPOINT;
    psCore->type = nType;

/* -------------------------------------------------------------------- */
/*      Set multipoint specific information in the structure.           */
/* -------------------------------------------------------------------- */
    psMP->num_vertices = nPointCount;
    // coverity[overrun-buffer-arg]
    memcpy( psMP->vertices + 0, pasVertices, sizeof(DGNPoint) * nPointCount );

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the multipoint section.                      */
/* -------------------------------------------------------------------- */
    if( nType == DGNT_LINE )
    {
        CPLAssert( nPointCount == 2 );

        psCore->raw_bytes = 36 + psDGN->dimension* 4 * nPointCount;

        psCore->raw_data = static_cast<unsigned char *>(
            CPLCalloc(psCore->raw_bytes, 1));

        DGNInverseTransformPointToInt( psDGN, pasVertices + 0,
                                       psCore->raw_data + 36 );
        DGNInverseTransformPointToInt( psDGN, pasVertices + 1,
                                       psCore->raw_data + 36
                                       + psDGN->dimension * 4 );
    }
    else
    {
        CPLAssert( nPointCount >= 2 );

        psCore->raw_bytes = 38 + psDGN->dimension * 4 * nPointCount;
        psCore->raw_data = static_cast<unsigned char *>(
            CPLCalloc(psCore->raw_bytes, 1));

        psCore->raw_data[36] = (unsigned char) (nPointCount % 256);
        psCore->raw_data[37] = (unsigned char) (nPointCount/256);

        for( int i = 0; i < nPointCount; i++ )
            DGNInverseTransformPointToInt( psDGN, pasVertices + i,
                                           psCore->raw_data + 38
                                           + psDGN->dimension * i * 4 );
    }

/* -------------------------------------------------------------------- */
/*      Set the core raw data, including the bounds.                    */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psCore );

    DGNPoint sMin = pasVertices[0];
    DGNPoint sMax = pasVertices[0];
    for( int i = 1; i < nPointCount; i++ )
    {
        sMin.x = std::min(pasVertices[i].x, sMin.x);
        sMin.y = std::min(pasVertices[i].y, sMin.y);
        sMin.z = std::min(pasVertices[i].z, sMin.z);
        sMax.x = std::max(pasVertices[i].x, sMax.x);
        sMax.y = std::max(pasVertices[i].y, sMax.y);
        sMax.z = std::max(pasVertices[i].z, sMax.z);
    }

    DGNWriteBounds( psDGN, psCore, &sMin, &sMax );

    return (DGNElemCore*) psMP;
}

/************************************************************************/
/*                         DGNCreateArcElem2D()                         */
/************************************************************************/

DGNElemCore *
DGNCreateArcElem2D( DGNHandle hDGN, int nType,
                    double dfOriginX, double dfOriginY,
                    double dfPrimaryAxis, double dfSecondaryAxis,
                    double dfRotation,
                    double dfStartAngle, double dfSweepAngle )

{
    return DGNCreateArcElem( hDGN, nType, dfOriginX, dfOriginY, 0.0,
                             dfPrimaryAxis, dfSecondaryAxis,
                             dfStartAngle, dfSweepAngle,
                             dfRotation, nullptr );
}

/************************************************************************/
/*                          DGNCreateArcElem()                          */
/************************************************************************/

/**
 * Create Arc or Ellipse element.
 *
 * Create a new 2D or 3D arc or ellipse element.  The start angle, and sweep
 * angle are ignored for DGNT_ELLIPSE but used for DGNT_ARC.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * @param hDGN the DGN file on which the element will eventually be written.
 * @param nType either DGNT_ELLIPSE or DGNT_ARC to select element type.
 * @param dfOriginX the origin (center of rotation) of the arc (X).
 * @param dfOriginY the origin (center of rotation) of the arc (Y).
 * @param dfOriginZ the origin (center of rotation) of the arc (Y).
 * @param dfPrimaryAxis the length of the primary axis.
 * @param dfSecondaryAxis the length of the secondary axis.
 * @param dfStartAngle start angle, degrees counterclockwise of primary axis.
 * @param dfSweepAngle sweep angle, degrees
 * @param dfRotation Counterclockwise rotation in degrees.
 * @param panQuaternion 3D orientation quaternion (NULL to use rotation).
 *
 * @return the new element (DGNElemArc) or NULL on failure.
 */

DGNElemCore *
DGNCreateArcElem( DGNHandle hDGN, int nType,
                  double dfOriginX, double dfOriginY, double dfOriginZ,
                  double dfPrimaryAxis, double dfSecondaryAxis,
                  double dfStartAngle, double dfSweepAngle,
                  double dfRotation, int *panQuaternion )

{
    CPLAssert( nType == DGNT_ARC || nType == DGNT_ELLIPSE );

    DGNInfo *psDGN = (DGNInfo *) hDGN;
    DGNLoadTCB( hDGN );

/* -------------------------------------------------------------------- */
/*      Allocate element.                                               */
/* -------------------------------------------------------------------- */
    DGNElemArc *psArc = static_cast<DGNElemArc *>(
        CPLCalloc(sizeof(DGNElemArc), 1));
    DGNElemCore *psCore = &(psArc->core);

    DGNInitializeElemCore( hDGN, psCore );
    psCore->stype = DGNST_ARC;
    psCore->type = nType;

/* -------------------------------------------------------------------- */
/*      Set arc specific information in the structure.                  */
/* -------------------------------------------------------------------- */
    DGNPoint sOrigin = { dfOriginX, dfOriginY, dfOriginZ };

    psArc->origin = sOrigin;
    psArc->primary_axis = dfPrimaryAxis;
    psArc->secondary_axis = dfSecondaryAxis;
    memset( psArc->quat, 0, sizeof(int) * 4 );
    psArc->startang = dfStartAngle;
    psArc->sweepang = dfSweepAngle;

    psArc->rotation = dfRotation;
    if( panQuaternion == nullptr )
    {
        DGNRotationToQuaternion( dfRotation, psArc->quat );
    }
    else
    {
        memcpy( psArc->quat, panQuaternion, sizeof(int)*4 );
    }

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the arc section.                             */
/* -------------------------------------------------------------------- */
    GInt32 nAngle = 0;

    if( nType == DGNT_ARC )
    {
        double dfScaledAxis;

        if( psDGN->dimension == 3 )
            psCore->raw_bytes = 100;
        else
            psCore->raw_bytes = 80;
        psCore->raw_data = static_cast<unsigned char *>(
            CPLCalloc(psCore->raw_bytes, 1));

        /* start angle */
        nAngle = (int) (dfStartAngle * 360000.0);
        DGN_WRITE_INT32( nAngle, psCore->raw_data + 36 );

        /* sweep angle */
        if( dfSweepAngle < 0.0 )
        {
            nAngle = static_cast<int>(std::abs(dfSweepAngle) * 360000.0);
            nAngle |= 0x80000000;
        }
        else if( dfSweepAngle > 364.9999 )
        {
            nAngle = 0;
        }
        else
        {
            nAngle = (int) (dfSweepAngle * 360000.0);
        }
        DGN_WRITE_INT32( nAngle, psCore->raw_data + 40 );

        /* axes */
        dfScaledAxis = dfPrimaryAxis / psDGN->scale;
        memcpy( psCore->raw_data + 44, &dfScaledAxis, 8 );
        IEEE2DGNDouble( psCore->raw_data + 44 );

        dfScaledAxis = dfSecondaryAxis / psDGN->scale;
        memcpy( psCore->raw_data + 52, &dfScaledAxis, 8 );
        IEEE2DGNDouble( psCore->raw_data + 52 );

        if( psDGN->dimension == 3 )
        {
            /* quaternion */
            DGN_WRITE_INT32( psArc->quat[0], psCore->raw_data + 60 );
            DGN_WRITE_INT32( psArc->quat[1], psCore->raw_data + 64 );
            DGN_WRITE_INT32( psArc->quat[2], psCore->raw_data + 68 );
            DGN_WRITE_INT32( psArc->quat[3], psCore->raw_data + 72 );

            /* origin */
            DGNInverseTransformPoint( psDGN, &sOrigin );
            memcpy( psCore->raw_data + 76, &(sOrigin.x), 8 );
            memcpy( psCore->raw_data + 84, &(sOrigin.y), 8 );
            memcpy( psCore->raw_data + 92, &(sOrigin.z), 8 );
            IEEE2DGNDouble( psCore->raw_data + 76 );
            IEEE2DGNDouble( psCore->raw_data + 84 );
            IEEE2DGNDouble( psCore->raw_data + 92 );
        }
        else
        {
            /* rotation */
            nAngle = (int) (dfRotation * 360000.0);
            DGN_WRITE_INT32( nAngle, psCore->raw_data + 60 );

            /* origin */
            DGNInverseTransformPoint( psDGN, &sOrigin );
            memcpy( psCore->raw_data + 64, &(sOrigin.x), 8 );
            memcpy( psCore->raw_data + 72, &(sOrigin.y), 8 );
            IEEE2DGNDouble( psCore->raw_data + 64 );
            IEEE2DGNDouble( psCore->raw_data + 72 );
        }
    }

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the ellipse section.                         */
/* -------------------------------------------------------------------- */
    else
    {
        double dfScaledAxis;

        if( psDGN->dimension == 3 )
            psCore->raw_bytes = 92;
        else
            psCore->raw_bytes = 72;
        psCore->raw_data = (unsigned char*) CPLCalloc(psCore->raw_bytes,1);

        /* axes */
        dfScaledAxis = dfPrimaryAxis / psDGN->scale;
        memcpy( psCore->raw_data + 36, &dfScaledAxis, 8 );
        IEEE2DGNDouble( psCore->raw_data + 36 );

        dfScaledAxis = dfSecondaryAxis / psDGN->scale;
        memcpy( psCore->raw_data + 44, &dfScaledAxis, 8 );
        IEEE2DGNDouble( psCore->raw_data + 44 );

        if( psDGN->dimension == 3 )
        {
            /* quaternion */
            DGN_WRITE_INT32( psArc->quat[0], psCore->raw_data + 52 );
            DGN_WRITE_INT32( psArc->quat[1], psCore->raw_data + 56 );
            DGN_WRITE_INT32( psArc->quat[2], psCore->raw_data + 60 );
            DGN_WRITE_INT32( psArc->quat[3], psCore->raw_data + 64 );

            /* origin */
            DGNInverseTransformPoint( psDGN, &sOrigin );
            memcpy( psCore->raw_data + 68, &(sOrigin.x), 8 );
            memcpy( psCore->raw_data + 76, &(sOrigin.y), 8 );
            memcpy( psCore->raw_data + 84, &(sOrigin.z), 8 );
            IEEE2DGNDouble( psCore->raw_data + 68 );
            IEEE2DGNDouble( psCore->raw_data + 76 );
            IEEE2DGNDouble( psCore->raw_data + 84 );
        }
        else
        {
            /* rotation */
            nAngle = (int) (dfRotation * 360000.0);
            DGN_WRITE_INT32( nAngle, psCore->raw_data + 52 );

            /* origin */
            DGNInverseTransformPoint( psDGN, &sOrigin );
            memcpy( psCore->raw_data + 56, &(sOrigin.x), 8 );
            memcpy( psCore->raw_data + 64, &(sOrigin.y), 8 );
            IEEE2DGNDouble( psCore->raw_data + 56 );
            IEEE2DGNDouble( psCore->raw_data + 64 );
        }

        psArc->startang = 0.0;
        psArc->sweepang = 360.0;
    }

/* -------------------------------------------------------------------- */
/*      Set the core raw data, including the bounds.                    */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psCore );

    DGNPoint sMin = {
        dfOriginX - std::max(dfPrimaryAxis, dfSecondaryAxis),
        dfOriginY - std::max(dfPrimaryAxis, dfSecondaryAxis),
        dfOriginZ - std::max(dfPrimaryAxis, dfSecondaryAxis)
    };
    DGNPoint sMax = {
        dfOriginX + std::max(dfPrimaryAxis, dfSecondaryAxis),
        dfOriginY + std::max(dfPrimaryAxis, dfSecondaryAxis),
        dfOriginZ + std::max(dfPrimaryAxis, dfSecondaryAxis)
    };

    DGNWriteBounds( psDGN, psCore, &sMin, &sMax );

    return (DGNElemCore*) psArc;
}

/************************************************************************/
/*                          DGNCreateConeElem()                         */
/************************************************************************/

/**
 * Create Cone element.
 *
 * Create a new 3D cone element.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * @param hDGN the DGN file on which the element will eventually be written.
 * @param dfCenter_1X the center of the first bounding circle (X).
 * @param dfCenter_1Y the center of the first bounding circle (Y).
 * @param dfCenter_1Z the center of the first bounding circle (Z).
 * @param dfRadius_1 the radius of the first bounding circle.
 * @param dfCenter_2X the center of the second bounding circle (X).
 * @param dfCenter_2Y the center of the second bounding circle (Y).
 * @param dfCenter_2Z the center of the second bounding circle (Z).
 * @param dfRadius_2 the radius of the second bounding circle.
 * @param panQuaternion 3D orientation quaternion (NULL for default orientation - circles parallel to the X-Y plane).
 *
 * @return the new element (DGNElemCone) or NULL on failure.
 */

DGNElemCore *
DGNCreateConeElem( DGNHandle hDGN,
                   double dfCenter_1X, double dfCenter_1Y,
                   double dfCenter_1Z, double dfRadius_1,
                   double dfCenter_2X, double dfCenter_2Y,
                   double dfCenter_2Z, double dfRadius_2,
                   int *panQuaternion )
{
    DGNInfo *psDGN = (DGNInfo *) hDGN;

    DGNLoadTCB( hDGN );

/* -------------------------------------------------------------------- */
/*      Allocate element.                                               */
/* -------------------------------------------------------------------- */
    DGNElemCone *psCone = (DGNElemCone *) CPLCalloc( sizeof(DGNElemCone), 1 );
    DGNElemCore *psCore = &(psCone->core);

    DGNInitializeElemCore( hDGN, psCore );
    psCore->stype = DGNST_CONE;
    psCore->type = DGNT_CONE;

/* -------------------------------------------------------------------- */
/*      Set cone specific information in the structure.                 */
/* -------------------------------------------------------------------- */
    DGNPoint sCenter_1 = { dfCenter_1X, dfCenter_1Y, dfCenter_1Z };
    DGNPoint sCenter_2 = { dfCenter_2X, dfCenter_2Y, dfCenter_2Z };
    psCone->center_1 = sCenter_1;
    psCone->center_2 = sCenter_2;
    psCone->radius_1 = dfRadius_1;
    psCone->radius_2 = dfRadius_2;

    memset( psCone->quat, 0, sizeof(int) * 4 );
    if( panQuaternion != nullptr )
    {
        memcpy( psCone->quat, panQuaternion, sizeof(int)*4 );
    }
    else
    {
      psCone->quat[0] = 1 << 31;
      psCone->quat[1] = 0;
      psCone->quat[2] = 0;
      psCone->quat[3] = 0;
    }

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the cone.                                    */
/* -------------------------------------------------------------------- */
    psCore->raw_bytes = 118;
    psCore->raw_data = (unsigned char*) CPLCalloc(psCore->raw_bytes,1);

    /* unknown data */
    psCore->raw_data[36] = 0;
    psCore->raw_data[37] = 0;

    /* quaternion */
    DGN_WRITE_INT32( psCone->quat[0], psCore->raw_data + 38 );
    DGN_WRITE_INT32( psCone->quat[1], psCore->raw_data + 42 );
    DGN_WRITE_INT32( psCone->quat[2], psCore->raw_data + 46 );
    DGN_WRITE_INT32( psCone->quat[3], psCore->raw_data + 50 );

    /* center_1 */
    DGNInverseTransformPoint( psDGN, &sCenter_1 );
    memcpy( psCore->raw_data + 54, &sCenter_1.x, 8 );
    memcpy( psCore->raw_data + 62, &sCenter_1.y, 8 );
    memcpy( psCore->raw_data + 70, &sCenter_1.z, 8 );
    IEEE2DGNDouble( psCore->raw_data + 54 );
    IEEE2DGNDouble( psCore->raw_data + 62 );
    IEEE2DGNDouble( psCore->raw_data + 70 );

    /* radius_1 */
    double dfScaledRadius = psCone->radius_1 / psDGN->scale;
    memcpy( psCore->raw_data + 78, &dfScaledRadius, 8 );
    IEEE2DGNDouble( psCore->raw_data + 78 );

    /* center_2 */
    DGNInverseTransformPoint( psDGN, &sCenter_2 );
    memcpy( psCore->raw_data + 86, &sCenter_2.x, 8 );
    memcpy( psCore->raw_data + 94, &sCenter_2.y, 8 );
    memcpy( psCore->raw_data + 102, &sCenter_2.z, 8 );
    IEEE2DGNDouble( psCore->raw_data + 86 );
    IEEE2DGNDouble( psCore->raw_data + 94 );
    IEEE2DGNDouble( psCore->raw_data + 102 );

    /* radius_2 */
    dfScaledRadius = psCone->radius_2 / psDGN->scale;
    memcpy( psCore->raw_data + 110, &dfScaledRadius, 8 );
    IEEE2DGNDouble( psCore->raw_data + 110 );

/* -------------------------------------------------------------------- */
/*      Set the core raw data, including the bounds.                    */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psCore );

    //FIXME: Calculate bounds. Do we need to take the quaternion into account?
    // kintel 20030819

    // Old implementation attempt:
    // What if center_1.z > center_2.z ?
//     double largestRadius =
//       psCone->radius_1>psCone->radius_2?psCone->radius_1:psCone->radius_2;
//     sMin.x = psCone->center_1.x-largestRadius;
//     sMin.y = psCone->center_1.y-largestRadius;
//     sMin.z = psCone->center_1.z;
//     sMax.x = psCone->center_2.x+largestRadius;
//     sMax.y = psCone->center_2.y+largestRadius;
//     sMax.z = psCone->center_2.z;

    DGNPoint sMin = { 0.0, 0.0, 0.0 };
    DGNPoint sMax = { 0.0, 0.0, 0.0 };
    DGNWriteBounds( psDGN, psCore, &sMin, &sMax );

    return (DGNElemCore*) psCone;
}

/************************************************************************/
/*                         DGNCreateTextElem()                          */
/************************************************************************/

/**
 * Create text element.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * @param hDGN the file on which the element will eventually be written.
 * @param pszText the string of text.
 * @param nFontId microstation font id for the text.  1 may be used as default.
 * @param nJustification text justification.  One of DGNJ_LEFT_TOP,
 * DGNJ_LEFT_CENTER, DGNJ_LEFT_BOTTOM, DGNJ_CENTER_TOP, DGNJ_CENTER_CENTER,
 * DGNJ_CENTER_BOTTOM, DGNJ_RIGHT_TOP, DGNJ_RIGHT_CENTER, DGNJ_RIGHT_BOTTOM.
 * @param dfLengthMult character width in master units.
 * @param dfHeightMult character height in master units.
 * @param dfRotation Counterclockwise text rotation in degrees.
 * @param panQuaternion 3D orientation quaternion (NULL to use rotation).
 * @param dfOriginX Text origin (X).
 * @param dfOriginY Text origin (Y).
 * @param dfOriginZ Text origin (Z).
 *
 * @return the new element (DGNElemText) or NULL on failure.
 */

DGNElemCore *
DGNCreateTextElem( DGNHandle hDGN, const char *pszText,
                   int nFontId, int nJustification,
                   double dfLengthMult, double dfHeightMult,
                   double dfRotation, int *panQuaternion,
                   double dfOriginX, double dfOriginY, double dfOriginZ )

{
    DGNInfo *psDGN = (DGNInfo *) hDGN;

    DGNLoadTCB( hDGN );

/* -------------------------------------------------------------------- */
/*      Allocate element.                                               */
/* -------------------------------------------------------------------- */
    DGNElemText *psText = (DGNElemText *)
        CPLCalloc( sizeof(DGNElemText)+strlen(pszText), 1 );
    DGNElemCore *psCore = &(psText->core);

    DGNInitializeElemCore( hDGN, psCore );
    psCore->stype = DGNST_TEXT;
    psCore->type = DGNT_TEXT;

/* -------------------------------------------------------------------- */
/*      Set arc specific information in the structure.                  */
/* -------------------------------------------------------------------- */
    psText->font_id = nFontId;
    psText->justification = nJustification;
    psText->length_mult = dfLengthMult;
    psText->height_mult = dfHeightMult;
    psText->rotation = dfRotation;
    psText->origin.x = dfOriginX;
    psText->origin.y = dfOriginY;
    psText->origin.z = dfOriginZ;
    strcpy( psText->string, pszText );

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the text specific portion.                   */
/* -------------------------------------------------------------------- */
    if( psDGN->dimension == 2 )
        psCore->raw_bytes = 60 + static_cast<int>(strlen(pszText));
    else
        psCore->raw_bytes = 76 + static_cast<int>(strlen(pszText));

    psCore->raw_bytes += (psCore->raw_bytes % 2);
    psCore->raw_data = (unsigned char*) CPLCalloc(psCore->raw_bytes,1);

    psCore->raw_data[36] = (unsigned char) nFontId;
    psCore->raw_data[37] = (unsigned char) nJustification;

    GInt32 nIntValue =
        static_cast<int>(dfLengthMult * 1000.0 / (psDGN->scale * 6.0) + 0.5);
    DGN_WRITE_INT32( nIntValue, psCore->raw_data + 38 );

    nIntValue = (int) (dfHeightMult * 1000.0 / (psDGN->scale * 6.0) + 0.5);
    DGN_WRITE_INT32( nIntValue, psCore->raw_data + 42 );

    GInt32 nBase = 0;

    if( psDGN->dimension == 2 )
    {
        nIntValue = (int) (dfRotation * 360000.0);
        DGN_WRITE_INT32( nIntValue, psCore->raw_data + 46 );

        DGNInverseTransformPointToInt( psDGN, &(psText->origin),
                                       psCore->raw_data + 50 );

        nBase = 58;
    }
    else
    {
        int anQuaternion[4];

        if( panQuaternion == nullptr )
            DGNRotationToQuaternion( dfRotation, anQuaternion );
        else
            memcpy( anQuaternion, panQuaternion, sizeof(int) * 4 );

        DGN_WRITE_INT32( anQuaternion[0], psCore->raw_data + 46 );
        DGN_WRITE_INT32( anQuaternion[1], psCore->raw_data + 50 );
        DGN_WRITE_INT32( anQuaternion[2], psCore->raw_data + 54 );
        DGN_WRITE_INT32( anQuaternion[3], psCore->raw_data + 58 );

        DGNInverseTransformPointToInt( psDGN, &(psText->origin),
                                       psCore->raw_data + 62 );
        nBase = 74;
    }

    psCore->raw_data[nBase] = (unsigned char) strlen(pszText);
    psCore->raw_data[nBase+1] = 0; /* edflds? */
    memcpy( psCore->raw_data + nBase+2, pszText, strlen(pszText) );

/* -------------------------------------------------------------------- */
/*      Set the core raw data, including the bounds.                    */
/*                                                                      */
/*      Code contributed by Mart Kelder.                                */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psCore );

    //calculate bounds if rotation is 0
    DGNPoint sMin = { dfOriginX, dfOriginY, 0.0 };
    DGNPoint sMax = {
        dfOriginX + dfLengthMult * strlen(pszText),
        dfOriginY + dfHeightMult,
        0.0
    };

    //calculate rotated bounding box coordinates
    const double length = sMax.x-sMin.x;
    const double height = sMax.y-sMin.y;
    const double diagonal=sqrt(length*length+height*height);
    const DGNPoint sLowLeft = { sMin.x, sMin.y, 0.0 };
    const DGNPoint sLowRight = {
       sMin.x+cos(psText->rotation*M_PI/180.0)*length,
       sMin.y+sin(psText->rotation*M_PI/180.0)*length,
       0.0
    };
    const DGNPoint sUpRight = {
        sMin.x+cos((psText->rotation*M_PI/180.0)+atan(height/length))*diagonal,
        sMin.y+sin((psText->rotation*M_PI/180.0)+atan(height/length))*diagonal,
        0.0
    };
    const DGNPoint sUpLeft = {
        sMin.x+cos((psText->rotation+90.0)*M_PI/180.0)*height,
        sMin.y+sin((psText->rotation+90.0)*M_PI/180.0)*height,
        0.0
    };

    //calculate new values for bounding box
    sMin.x = std::min(sLowLeft.x,
                      std::min(sLowRight.x, std::min(sUpLeft.x, sUpRight.x)));
    sMin.y = std::min(sLowLeft.y,
                      std::min(sLowRight.y, std::min(sUpLeft.y, sUpRight.y)));
    sMax.x = std::max(sLowLeft.x,
                      std::max(sLowRight.x, std::max(sUpLeft.x, sUpRight.x)));
    sMax.y = std::max(sLowLeft.y,
                      std::max(sLowRight.y, std::max(sUpLeft.y, sUpRight.y)));
    sMin.x = dfOriginX - dfLengthMult * strlen(pszText);
    sMin.y = dfOriginY - dfHeightMult;
    sMin.z = 0.0;
    sMax.x = dfOriginX + dfLengthMult * strlen(pszText);
    sMax.y = dfOriginY + dfHeightMult;
    sMax.z = 0.0;

    DGNWriteBounds( psDGN, psCore, &sMin, &sMax );

    return (DGNElemCore*) psText;
}

/************************************************************************/
/*                      DGNCreateColorTableElem()                       */
/************************************************************************/

/**
 * Create color table element.
 *
 * Creates a color table element with the indicated color table.
 *
 * Note that color table elements are actually of type DGNT_GROUP_DATA(5)
 * and always on level 1.  Do not alter the level with DGNUpdateElemCore()
 * or the element will essentially be corrupt.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * @param hDGN the file to which the element will eventually be written.
 * @param nScreenFlag the screen to which the color table applies
 * (0 = left, 1 = right).
 * @param abyColorInfo array of 256 color entries. The first is
 * the background color.
 *
 * @return the new element (DGNElemColorTable) or NULL on failure.
 */

DGNElemCore *
DGNCreateColorTableElem( DGNHandle hDGN, int nScreenFlag,
                         GByte abyColorInfo[256][3] )

{
/* -------------------------------------------------------------------- */
/*      Allocate element.                                               */
/* -------------------------------------------------------------------- */
    DGNElemColorTable *psCT = (DGNElemColorTable *)
        CPLCalloc( sizeof(DGNElemColorTable), 1 );
    DGNElemCore *psCore = &(psCT->core);

    DGNInitializeElemCore( hDGN, psCore );
    psCore->stype = DGNST_COLORTABLE;
    psCore->type = DGNT_GROUP_DATA;
    psCore->level = DGN_GDL_COLOR_TABLE;

/* -------------------------------------------------------------------- */
/*      Set colortable specific information in the structure.           */
/* -------------------------------------------------------------------- */
    psCT->screen_flag = nScreenFlag;
    memcpy( psCT->color_info, abyColorInfo, 768 );

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the color table specific portion.            */
/* -------------------------------------------------------------------- */
    psCore->raw_bytes = 41+(256-1)*3;
    psCore->raw_data = (unsigned char*) CPLCalloc(psCore->raw_bytes,1);

    psCore->raw_data[36] = (unsigned char) (nScreenFlag % 256);
    psCore->raw_data[37] = (unsigned char) (nScreenFlag / 256);

    memcpy( psCore->raw_data + 38, abyColorInfo[255], 3 );
    memcpy( psCore->raw_data + 41, abyColorInfo, (256-1)*3 );

/* -------------------------------------------------------------------- */
/*      Set the core raw data.                                          */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psCore );

    return (DGNElemCore*) psCT;
}

/************************************************************************/
/*                     DGNCreateComplexHeaderElem()                     */
/************************************************************************/

/**
 * Create complex chain/shape header.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * The nTotLength is the sum of the size of all elements in the complex
 * group plus 5.  The DGNCreateComplexHeaderFromGroup() can be used to build
 * a complex element from the members more conveniently.
 *
 * @param hDGN the file on which the element will be written.
 * @param nType DGNT_COMPLEX_CHAIN_HEADER or DGNT_COMPLEX_SHAPE_HEADER.
 * depending on whether the list is open or closed (last point equal to last)
 * or if the object represents a surface or a solid.
 * @param nTotLength the value of the totlength field in the element.
 * @param nNumElems the number of elements in the complex group not including
 * the header element.
 *
 * @return the new element (DGNElemComplexHeader) or NULL on failure.
 */
DGNElemCore *
DGNCreateComplexHeaderElem( DGNHandle hDGN, int nType,
                            int nTotLength, int nNumElems )
{
    unsigned char abyRawZeroLinkage[8] = {0,0,0,0,0,0,0,0};

    CPLAssert( nType == DGNT_COMPLEX_CHAIN_HEADER
               || nType == DGNT_COMPLEX_SHAPE_HEADER );

    DGNLoadTCB( hDGN );

/* -------------------------------------------------------------------- */
/*      Allocate element.                                               */
/* -------------------------------------------------------------------- */
    DGNElemComplexHeader *psCH = (DGNElemComplexHeader *)
        CPLCalloc( sizeof(DGNElemComplexHeader), 1 );
    DGNElemCore *psCore = &(psCH->core);

    DGNInitializeElemCore( hDGN, psCore );
    psCore->complex = TRUE;
    psCore->stype = DGNST_COMPLEX_HEADER;
    psCore->type = nType;

/* -------------------------------------------------------------------- */
/*      Set complex header specific information in the structure.       */
/* -------------------------------------------------------------------- */
    psCH->totlength = nTotLength - 4;
    psCH->numelems = nNumElems;
    psCH->surftype = 0;
    psCH->boundelms = 0;

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the complex specific portion.                */
/* -------------------------------------------------------------------- */
    psCore->raw_bytes = 40;
    psCore->raw_data = (unsigned char*) CPLCalloc(psCore->raw_bytes,1);

    psCore->raw_data[36] = (unsigned char) ((nTotLength-4) % 256);
    psCore->raw_data[37] = (unsigned char) ((nTotLength-4) / 256);
    psCore->raw_data[38] = (unsigned char) (nNumElems % 256);
    psCore->raw_data[39] = (unsigned char) (nNumElems / 256);

/* -------------------------------------------------------------------- */
/*      Set the core raw data.                                          */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psCore );

/* -------------------------------------------------------------------- */
/*      Elements have to be at least 48 bytes long, so we have to       */
/*      add a dummy bit of attribute data to fill out the length.       */
/* -------------------------------------------------------------------- */
    DGNAddRawAttrLink( hDGN, psCore, 8, abyRawZeroLinkage );

    return (DGNElemCore*) psCH;
}

/************************************************************************/
/*                  DGNCreateComplexHeaderFromGroup()                   */
/************************************************************************/

/**
 * Create complex chain/shape header.
 *
 * This function is similar to DGNCreateComplexHeaderElem(), but it takes
 * care of computing the total size of the set of elements being written,
 * and collecting the bounding extents.  It also takes care of some other
 * convenience issues, like marking all the member elements as complex, and
 * setting the level based on the level of the member elements.
 *
 * @param hDGN the file on which the element will be written.
 * @param nType DGNT_COMPLEX_CHAIN_HEADER or DGNT_COMPLEX_SHAPE_HEADER.
 * depending on whether the list is open or closed (last point equal to last)
 * or if the object represents a surface or a solid.
 * @param nNumElems the number of elements in the complex group not including
 * the header element.
 * @param papsElems array of pointers to nNumElems elements in the complex
 * group.  Some updates may be made to these elements.
 *
 * @return the new element (DGNElemComplexHeader) or NULL on failure.
 */

DGNElemCore *
DGNCreateComplexHeaderFromGroup( DGNHandle hDGN, int nType,
                                 int nNumElems, DGNElemCore **papsElems )

{
    DGNLoadTCB( hDGN );

    if( nNumElems < 1 || papsElems == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Need at least one element to form a complex group." );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Collect the total size, and bounds.                             */
/* -------------------------------------------------------------------- */
    int nTotalLength = 5;
    const int nLevel = papsElems[0]->level;
    DGNPoint sMin = { 0.0, 0.0, 0.0 };
    DGNPoint sMax = { 0.0, 0.0, 0.0 };

    for( int i = 0; i < nNumElems; i++ )
    {
        nTotalLength += papsElems[i]->raw_bytes / 2;

        papsElems[i]->complex = TRUE;
        papsElems[i]->raw_data[0] |= 0x80;

        if( papsElems[i]->level != nLevel )
        {
            CPLError( CE_Warning, CPLE_AppDefined,
                      "Not all level values matching in a complex set group!");
        }

        DGNPoint sThisMin = { 0.0, 0.0, 0.0 };
        DGNPoint sThisMax = { 0.0, 0.0, 0.0 };

        DGNGetElementExtents( hDGN, papsElems[i], &sThisMin, &sThisMax );
        if( i == 0 )
        {
            sMin = sThisMin;
            sMax = sThisMax;
        }
        else
        {
            sMin.x = std::min(sMin.x, sThisMin.x);
            sMin.y = std::min(sMin.y, sThisMin.y);
            sMin.z = std::min(sMin.z, sThisMin.z);
            sMax.x = std::max(sMax.x, sThisMax.x);
            sMax.y = std::max(sMax.y, sThisMax.y);
            sMax.z = std::max(sMax.z, sThisMax.z);
        }
    }

/* -------------------------------------------------------------------- */
/*      Create the corresponding complex header.                        */
/* -------------------------------------------------------------------- */
    DGNElemCore *psCH =
        DGNCreateComplexHeaderElem( hDGN, nType, nTotalLength, nNumElems );
    DGNUpdateElemCore( hDGN, psCH, papsElems[0]->level, psCH->graphic_group,
                       psCH->color, psCH->weight, psCH->style );

    DGNWriteBounds( (DGNInfo *) hDGN, psCH, &sMin, &sMax );

    return psCH;
}

/************************************************************************/
/*                     DGNCreateSolidHeaderElem()                       */
/************************************************************************/

/**
 * Create 3D solid/surface.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * The nTotLength is the sum of the size of all elements in the solid
 * group plus 6.  The DGNCreateSolidHeaderFromGroup() can be used to build
 * a solid element from the members more conveniently.
 *
 * @param hDGN the file on which the element will be written.
 * @param nType DGNT_3DSURFACE_HEADER or DGNT_3DSOLID_HEADER.
 * @param nSurfType the surface/solid type, one of DGNSUT_* or DGNSOT_*.
 * @param nBoundElems the number of elements in each boundary.
 * @param nTotLength the value of the totlength field in the element.
 * @param nNumElems the number of elements in the solid not including
 * the header element.
 *
 * @return the new element (DGNElemComplexHeader) or NULL on failure.
 */
DGNElemCore *
DGNCreateSolidHeaderElem( DGNHandle hDGN, int nType, int nSurfType,
                          int nBoundElems, int nTotLength, int nNumElems )
{
    CPLAssert( nType == DGNT_3DSURFACE_HEADER
               || nType == DGNT_3DSOLID_HEADER );

    DGNLoadTCB( hDGN );

/* -------------------------------------------------------------------- */
/*      Allocate element.                                               */
/* -------------------------------------------------------------------- */
    DGNElemComplexHeader *psCH = (DGNElemComplexHeader *)
        CPLCalloc( sizeof(DGNElemComplexHeader), 1 );
    DGNElemCore *psCore = &(psCH->core);

    DGNInitializeElemCore( hDGN, psCore );
    psCore->complex = TRUE;
    psCore->stype = DGNST_COMPLEX_HEADER;
    psCore->type = nType;

/* -------------------------------------------------------------------- */
/*      Set solid header specific information in the structure.         */
/* -------------------------------------------------------------------- */
    psCH->totlength = nTotLength - 4;
    psCH->numelems = nNumElems;
    psCH->surftype = nSurfType;
    psCH->boundelms = nBoundElems;

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the solid specific portion.                  */
/* -------------------------------------------------------------------- */
    psCore->raw_bytes = 42;

    psCore->raw_data = (unsigned char*) CPLCalloc(psCore->raw_bytes,1);

    psCore->raw_data[36] = (unsigned char) ((nTotLength-4) % 256);
    psCore->raw_data[37] = (unsigned char) ((nTotLength-4) / 256);
    psCore->raw_data[38] = (unsigned char) (nNumElems % 256);
    psCore->raw_data[39] = (unsigned char) (nNumElems / 256);
    psCore->raw_data[40] = (unsigned char) psCH->surftype;
    psCore->raw_data[41] = (unsigned char) psCH->boundelms - 1;

/* -------------------------------------------------------------------- */
/*      Set the core raw data.                                          */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psCore );

/* -------------------------------------------------------------------- */
/*      Elements have to be at least 48 bytes long, so we have to       */
/*      add a dummy bit of attribute data to fill out the length.       */
/* -------------------------------------------------------------------- */
    unsigned char abyRawZeroLinkage[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    DGNAddRawAttrLink( hDGN, psCore, 8, abyRawZeroLinkage );

    return (DGNElemCore*) psCH;
}

/************************************************************************/
/*                  DGNCreateSolidHeaderFromGroup()                     */
/************************************************************************/

/**
 * Create 3D solid/surface header.
 *
 * This function is similar to DGNCreateSolidHeaderElem(), but it takes
 * care of computing the total size of the set of elements being written,
 * and collecting the bounding extents.  It also takes care of some other
 * convenience issues, like marking all the member elements as complex, and
 * setting the level based on the level of the member elements.
 *
 * @param hDGN the file on which the element will be written.
 * @param nType DGNT_3DSURFACE_HEADER or DGNT_3DSOLID_HEADER.
 * @param nSurfType the surface/solid type, one of DGNSUT_* or DGNSOT_*.
 * @param nBoundElems the number of boundary elements.
 * @param nNumElems the number of elements in the solid not including
 * the header element.
 * @param papsElems array of pointers to nNumElems elements in the solid.
 * Some updates may be made to these elements.
 *
 * @return the new element (DGNElemComplexHeader) or NULL on failure.
 */

DGNElemCore *
DGNCreateSolidHeaderFromGroup( DGNHandle hDGN, int nType, int nSurfType,
                               int nBoundElems, int nNumElems,
                               DGNElemCore **papsElems )

{
    DGNLoadTCB( hDGN );

    if( nNumElems < 1 || papsElems == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Need at least one element to form a solid." );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Collect the total size, and bounds.                             */
/* -------------------------------------------------------------------- */
    const int nLevel = papsElems[0]->level;
    int nTotalLength = 6;
    DGNPoint sMin = { 0.0, 0.0, 0.0 };
    DGNPoint sMax = { 0.0, 0.0, 0.0 };

    for( int i = 0; i < nNumElems; i++ )
    {
        nTotalLength += papsElems[i]->raw_bytes / 2;

        papsElems[i]->complex = TRUE;
        papsElems[i]->raw_data[0] |= 0x80;

        if( papsElems[i]->level != nLevel )
        {
            CPLError( CE_Warning, CPLE_AppDefined,
                      "Not all level values matching in a complex set group!");
        }

        DGNPoint sThisMin = { 0.0, 0.0, 0.0 };
        DGNPoint sThisMax = { 0.0, 0.0, 0.0 };
        DGNGetElementExtents( hDGN, papsElems[i], &sThisMin, &sThisMax );
        if( i == 0 )
        {
            sMin = sThisMin;
            sMax = sThisMax;
        }
        else
        {
            sMin.x = std::min(sMin.x,sThisMin.x);
            sMin.y = std::min(sMin.y,sThisMin.y);
            sMin.z = std::min(sMin.z,sThisMin.z);
            sMax.x = std::max(sMax.x,sThisMax.x);
            sMax.y = std::max(sMax.y,sThisMax.y);
            sMax.z = std::max(sMax.z,sThisMax.z);
        }
    }

/* -------------------------------------------------------------------- */
/*      Create the corresponding solid header.                          */
/* -------------------------------------------------------------------- */
    DGNElemCore *psCH =
        DGNCreateSolidHeaderElem( hDGN, nType, nSurfType, nBoundElems,
                                  nTotalLength, nNumElems );
    DGNUpdateElemCore( hDGN, psCH, papsElems[0]->level, psCH->graphic_group,
                       psCH->color, psCH->weight, psCH->style );

    DGNWriteBounds( (DGNInfo *) hDGN, psCH, &sMin, &sMax );

    return psCH;
}

/************************************************************************/
/*                      DGNCreateCellHeaderElem()                       */
/************************************************************************/

DGNElemCore CPL_DLL  *
DGNCreateCellHeaderElem( DGNHandle hDGN, int nTotLength, const char *pszName,
                         short nClass, short *panLevels,
                         DGNPoint *psRangeLow, DGNPoint *psRangeHigh,
                         DGNPoint *psOrigin, double dfXScale, double dfYScale,
                         double dfRotation )

/**
 * Create cell header.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * Generally speaking the function DGNCreateCellHeaderFromGroup() should
 * be used instead of this function.
 *
 * @param hDGN the file handle on which the element is to be written.
 * @param nTotLength total length of cell in words not including the 38 bytes
 * of the cell header that occur before the totlength indicator.
 * @param nClass the class value for the cell.
 * @param panLevels an array of shorts holding the bit mask of levels in
 * effect for this cell.  This array should contain 4 shorts (64 bits).
 * @param psRangeLow the cell diagonal origin in original cell file
 * coordinates.
 * @param psRangeHigh the cell diagonal top left corner in original cell file
 * coordinates.
 * @param psOrigin the origin of the cell in output file coordinates.
 * @param dfXScale the amount of scaling applied in the X dimension in
 * mapping from cell file coordinates to output file coordinates.
 * @param dfYScale the amount of scaling applied in the Y dimension in
 * mapping from cell file coordinates to output file coordinates.
 * @param dfRotation the amount of rotation (degrees counterclockwise) in
 * mapping from cell coordinates to output file coordinates.
 *
 * @return the new element (DGNElemCellHeader) or NULL on failure.
 */

{
    DGNInfo *psInfo = (DGNInfo *) hDGN;

    DGNLoadTCB( hDGN );

/* -------------------------------------------------------------------- */
/*      Allocate element.                                               */
/* -------------------------------------------------------------------- */
    DGNElemCellHeader *psCH = (DGNElemCellHeader *)
        CPLCalloc( sizeof(DGNElemCellHeader), 1 );
    DGNElemCore *psCore = &(psCH->core);

    DGNInitializeElemCore( hDGN, psCore );
    psCore->stype = DGNST_CELL_HEADER;
    psCore->type = DGNT_CELL_HEADER;

/* -------------------------------------------------------------------- */
/*      Set complex header specific information in the structure.       */
/* -------------------------------------------------------------------- */
    psCH->totlength = nTotLength;

/* -------------------------------------------------------------------- */
/*      Setup Raw data for the cell header specific portion.            */
/* -------------------------------------------------------------------- */
    if( psInfo->dimension == 2 )
        psCore->raw_bytes = 92;
    else
        psCore->raw_bytes = 124;
    psCore->raw_data = (unsigned char*) CPLCalloc(psCore->raw_bytes,1);

    psCore->raw_data[36] = (unsigned char) (nTotLength % 256);
    psCore->raw_data[37] = (unsigned char) (nTotLength / 256);

    DGNAsciiToRad50( pszName, (unsigned short *) (psCore->raw_data + 38) );
    if( strlen(pszName) > 3 )
        DGNAsciiToRad50( pszName+3, (unsigned short *) (psCore->raw_data+40) );

    psCore->raw_data[42] = (unsigned char) (nClass % 256);
    psCore->raw_data[43] = (unsigned char) (nClass / 256);

    memcpy( psCore->raw_data + 44, panLevels, 8 );

    if( psInfo->dimension == 2 )
    {
        DGNPointToInt( psInfo, psRangeLow, psCore->raw_data + 52 );
        DGNPointToInt( psInfo, psRangeHigh, psCore->raw_data+ 60 );

        DGNInverseTransformPointToInt( psInfo, psOrigin,
                                       psCore->raw_data + 84 );
    }
    else
    {
        DGNPointToInt( psInfo, psRangeLow, psCore->raw_data + 52 );
        DGNPointToInt( psInfo, psRangeHigh, psCore->raw_data+ 64 );

        DGNInverseTransformPointToInt( psInfo, psOrigin,
                                       psCore->raw_data + 112 );
    }

/* -------------------------------------------------------------------- */
/*      Produce a transformation matrix that approximates the           */
/*      requested scaling and rotation.                                 */
/* -------------------------------------------------------------------- */
    if( psInfo->dimension == 2 )
    {
        long anTrans[4];
        double cos_a = cos(-dfRotation * M_PI / 180.0);
        double sin_a = sin(-dfRotation * M_PI / 180.0);

        anTrans[0] = (long) (cos_a * dfXScale * 214748);
        anTrans[1] = (long) (sin_a * dfYScale * 214748);
        anTrans[2] = (long)(-sin_a * dfXScale * 214748);
        anTrans[3] = (long) (cos_a * dfYScale * 214748);

        DGN_WRITE_INT32( anTrans[0], psCore->raw_data + 68 );
        DGN_WRITE_INT32( anTrans[1], psCore->raw_data + 72 );
        DGN_WRITE_INT32( anTrans[2], psCore->raw_data + 76 );
        DGN_WRITE_INT32( anTrans[3], psCore->raw_data + 80 );
    }
    else
    {
        long anTrans[9];

        // NOTE: This is still just rotation in the plane
        double cos_a = cos(-dfRotation * M_PI / 180.0);
        double sin_a = sin(-dfRotation * M_PI / 180.0);
        double dfZScale = 1.0; // Should we get this from somewhere?

        anTrans[0] = (long) ( cos_a * dfXScale * 214748);
        anTrans[1] = (long) ( sin_a * dfYScale * 214748);
        anTrans[2] = (long) ( sin_a * dfZScale * 214748);

        anTrans[3] = (long) (-sin_a * dfXScale * 214748);
        anTrans[4] = (long) ( cos_a * dfYScale * 214748);
        anTrans[5] = (long) ( sin_a * dfZScale * 214748);

        anTrans[6] = (long) (-sin_a * dfXScale * 214748);
        anTrans[7] = (long) (-sin_a * dfYScale * 214748);
        anTrans[8] = (long) ( cos_a * dfZScale * 214748);

        DGN_WRITE_INT32( anTrans[0], psCore->raw_data + 76 );
        DGN_WRITE_INT32( anTrans[1], psCore->raw_data + 80 );
        DGN_WRITE_INT32( anTrans[2], psCore->raw_data + 84 );
        DGN_WRITE_INT32( anTrans[3], psCore->raw_data + 88 );
        DGN_WRITE_INT32( anTrans[4], psCore->raw_data + 92 );
        DGN_WRITE_INT32( anTrans[5], psCore->raw_data + 96 );
        DGN_WRITE_INT32( anTrans[6], psCore->raw_data + 100 );
        DGN_WRITE_INT32( anTrans[7], psCore->raw_data + 104 );
        DGN_WRITE_INT32( anTrans[8], psCore->raw_data + 108 );
    }

/* -------------------------------------------------------------------- */
/*      Set the core raw data.                                          */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psCore );

    return (DGNElemCore*) psCH;
}

/************************************************************************/
/*                           DGNPointToInt()                            */
/*                                                                      */
/*      Convert a point directly to integer coordinates and write to    */
/*      the indicate memory location.  Intended to be used for the      */
/*      range section of the CELL HEADER.                               */
/************************************************************************/

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

{
    double adfCT[3] = {
        psPoint->x,
        psPoint->y,
        psPoint->z
    };

    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 *pabyCTI = (unsigned char *) &nCTI;

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

/************************************************************************/
/*                    DGNCreateCellHeaderFromGroup()                    */
/************************************************************************/

/**
 * Create cell header from a group of elements.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * This function will compute the total length, bounding box, and diagonal
 * range values from the set of provided elements.  Note that the proper
 * diagonal range values will only be written if 1.0 is used for the x and y
 * scale values, and 0.0 for the rotation.  Use of other values will result
 * in incorrect scaling handles being presented to the user in Microstation
 * when they select the element.
 *
 * @param hDGN the file handle on which the element is to be written.
 * @param nClass the class value for the cell.
 * @param panLevels an array of shorts holding the bit mask of levels in
 * effect for this cell.  This array should contain 4 shorts (64 bits).
 * This array would normally be passed in as NULL, and the function will
 * build a mask from the passed list of elements.
 * @param psOrigin the origin of the cell in output file coordinates.
 * @param dfXScale the amount of scaling applied in the X dimension in
 * mapping from cell file coordinates to output file coordinates.
 * @param dfYScale the amount of scaling applied in the Y dimension in
 * mapping from cell file coordinates to output file coordinates.
 * @param dfRotation the amount of rotation (degrees counterclockwise) in
 * mapping from cell coordinates to output file coordinates.
 *
 * @return the new element (DGNElemCellHeader) or NULL on failure.
 */

DGNElemCore *
DGNCreateCellHeaderFromGroup( DGNHandle hDGN, const char *pszName,
                              short nClass, short *panLevels,
                              int nNumElems, DGNElemCore **papsElems,
                              DGNPoint *psOrigin,
                              double dfXScale, double dfYScale,
                              double dfRotation )

{
    DGNInfo *psInfo = (DGNInfo *) hDGN;

    DGNLoadTCB( hDGN );

    if( nNumElems < 1 || papsElems == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Need at least one element to form a cell." );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Collect the total size, and bounds.                             */
/* -------------------------------------------------------------------- */
    int nTotalLength = psInfo->dimension == 2 ? 27 : 43;
    // nLevel = papsElems[0]->level;x
    DGNPoint sMin = { 0.0, 0.0, 0.0 };
    DGNPoint sMax = { 0.0, 0.0, 0.0 };
    unsigned char abyLevelsOccurring[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

    for( int i = 0; i < nNumElems; i++ )
    {
        nTotalLength += papsElems[i]->raw_bytes / 2;

        /* mark as complex */
        papsElems[i]->complex = TRUE;
        papsElems[i]->raw_data[0] |= 0x80;

        /* establish level */
        int nLevel = papsElems[i]->level;
        nLevel = std::max(1,std::min(nLevel,64));
        abyLevelsOccurring[(nLevel-1) >> 3] |= (0x1 << ((nLevel-1)&0x7));

        DGNPoint sThisMin = { 0.0, 0.0, 0.0 };
        DGNPoint sThisMax = { 0.0, 0.0, 0.0 };
        DGNGetElementExtents( hDGN, papsElems[i], &sThisMin, &sThisMax );
        if( i == 0 )
        {
            sMin = sThisMin;
            sMax = sThisMax;
        }
        else
        {
            sMin.x = std::min(sMin.x,sThisMin.x);
            sMin.y = std::min(sMin.y,sThisMin.y);
            sMin.z = std::min(sMin.z,sThisMin.z);
            sMax.x = std::max(sMax.x,sThisMax.x);
            sMax.y = std::max(sMax.y,sThisMax.y);
            sMax.z = std::max(sMax.z,sThisMax.z);
        }
    }

/* -------------------------------------------------------------------- */
/*      It seems that the range needs to be adjusted according to       */
/*      the rotation and scaling.                                       */
/*                                                                      */
/*      NOTE: Omitting code ... this is already done in                 */
/*      DGNInverseTransformPoint() called from DGNWriteBounds().        */
/* -------------------------------------------------------------------- */
#ifdef notdef
    sMin.x -= psOrigin->x;
    sMin.y -= psOrigin->y;
    sMin.z -= psOrigin->z;
    sMax.x -= psOrigin->x;
    sMax.y -= psOrigin->y;
    sMax.z -= psOrigin->z;

    sMin.x /= ((DGNInfo *) hDGN)->scale;
    sMin.y /= ((DGNInfo *) hDGN)->scale;
    sMin.z /= ((DGNInfo *) hDGN)->scale;
    sMax.x /= ((DGNInfo *) hDGN)->scale;
    sMax.y /= ((DGNInfo *) hDGN)->scale;
    sMax.z /= ((DGNInfo *) hDGN)->scale;
#endif

/* -------------------------------------------------------------------- */
/*      Create the corresponding cell header.                           */
/* -------------------------------------------------------------------- */
    if( panLevels == nullptr )
        panLevels = (short *) abyLevelsOccurring + 0;

    DGNElemCore *psCH =
        DGNCreateCellHeaderElem( hDGN, nTotalLength, pszName,
                                 nClass, panLevels,
                                 &sMin, &sMax, psOrigin,
                                 dfXScale, dfYScale, dfRotation );
    DGNWriteBounds( (DGNInfo *) hDGN, psCH, &sMin, &sMax );

    return psCH;
}

/************************************************************************/
/*                            DGNAddMSLink()                            */
/************************************************************************/

/**
 * Add a database link to element.
 *
 * The target element must already have raw_data loaded, and it will be
 * resized (see DGNResizeElement()) as needed for the new attribute data.
 * Note that the element is not written to disk immediate.  Use
 * DGNWriteElement() for that.
 *
 * @param hDGN the file to which the element corresponds.
 * @param psElement the element being updated.
 * @param nLinkageType link type (DGNLT_*).  Usually one of DGNLT_DMRS,
 * DGNLT_INFORMIX, DGNLT_ODBC, DGNLT_ORACLE, DGNLT_RIS, DGNLT_SYBASE,
 * or DGNLT_XBASE.
 * @param nEntityNum indicator of the table referenced on target database.
 * @param nMSLink indicator of the record referenced on target table.
 *
 * @return -1 on failure, or the link index.
 */

int DGNAddMSLink( DGNHandle hDGN, DGNElemCore *psElement,
                  int nLinkageType, int nEntityNum, int nMSLink )

{
    unsigned char abyLinkage[32] = {};
    int nLinkageSize = 0;

    if( nLinkageType == DGNLT_DMRS )
    {
        nLinkageSize = 8;
        abyLinkage[0] = 0x00;
        abyLinkage[1] = 0x00;
        abyLinkage[2] = (GByte) (nEntityNum % 256);
        abyLinkage[3] = (GByte) (nEntityNum / 256);
        abyLinkage[4] = (GByte) (nMSLink % 256);
        abyLinkage[5] = (GByte) ((nMSLink / 256) % 256);
        abyLinkage[6] = (GByte) (nMSLink / 65536);
        abyLinkage[7] = 0x01;
    }
    else
    {
        nLinkageSize = 16;
        abyLinkage[0] = 0x07;
        abyLinkage[1] = 0x10;
        abyLinkage[2] = (GByte) (nLinkageType % 256);
        abyLinkage[3] = (GByte) (nLinkageType / 256);
        abyLinkage[4] = (GByte) (0x81);
        abyLinkage[5] = (GByte) (0x0F);
        abyLinkage[6] = (GByte) (nEntityNum % 256);
        abyLinkage[7] = (GByte) (nEntityNum / 256);
        abyLinkage[8] = (GByte) (nMSLink % 256);
        abyLinkage[9] = (GByte) ((nMSLink / 256) % 256);
        abyLinkage[10] = (GByte) ((nMSLink / 65536) % 256);
        abyLinkage[11] = (GByte) (nMSLink / 16777216);
        abyLinkage[12] = 0x00;
        abyLinkage[13] = 0x00;
        abyLinkage[14] = 0x00;
        abyLinkage[15] = 0x00;
    }

    return DGNAddRawAttrLink( hDGN, psElement, nLinkageSize, abyLinkage );
}

/************************************************************************/
/*                         DGNAddRawAttrLink()                          */
/************************************************************************/

/**
 * Add a raw attribute linkage to element.
 *
 * Given a raw data buffer, append it to this element as an attribute linkage
 * without trying to interpret the linkage data.
 *
 * The target element must already have raw_data loaded, and it will be
 * resized (see DGNResizeElement()) as needed for the new attribute data.
 * Note that the element is not written to disk immediate.  Use
 * DGNWriteElement() for that.
 *
 * This function will take care of updating the "totlength" field of
 * complex chain or shape headers to account for the extra attribute space
 * consumed in the header element.
 *
 * @param hDGN the file to which the element corresponds.
 * @param psElement the element being updated.
 * @param nLinkSize the size of the linkage in bytes.
 * @param pabyRawLinkData the raw linkage data (nLinkSize bytes worth).
 *
 * @return -1 on failure, or the link index.
 */

int DGNAddRawAttrLink( DGNHandle hDGN, DGNElemCore *psElement,
                       int nLinkSize, unsigned char *pabyRawLinkData )

{
    if( nLinkSize % 2 == 1 )
        nLinkSize++;

    if( psElement->size + nLinkSize > 768 )
    {
        CPLError( CE_Failure, CPLE_ElementTooBig,
                  "Attempt to add %d byte linkage to element exceeds maximum"
                  " element size.",
                  nLinkSize );
        return -1;
    }

/* -------------------------------------------------------------------- */
/*      Ensure the attribute linkage bit is set.                        */
/* -------------------------------------------------------------------- */
    psElement->properties |= DGNPF_ATTRIBUTES;

/* -------------------------------------------------------------------- */
/*      Append the attribute linkage to the linkage area.               */
/* -------------------------------------------------------------------- */
    psElement->attr_bytes += nLinkSize;
    psElement->attr_data = (unsigned char *)
        CPLRealloc( psElement->attr_data, psElement->attr_bytes );

    memcpy( psElement->attr_data + (psElement->attr_bytes-nLinkSize),
            pabyRawLinkData, nLinkSize );

/* -------------------------------------------------------------------- */
/*      Grow the raw data, if we have rawdata.                          */
/* -------------------------------------------------------------------- */
    psElement->raw_bytes += nLinkSize;
    psElement->raw_data = (unsigned char *)
        CPLRealloc( psElement->raw_data, psElement->raw_bytes );

    memcpy( psElement->raw_data + (psElement->raw_bytes-nLinkSize),
            pabyRawLinkData, nLinkSize );

/* -------------------------------------------------------------------- */
/*      If the element is a shape or chain complex header, then we      */
/*      need to increase the total complex group size appropriately.    */
/* -------------------------------------------------------------------- */
    if( psElement->stype == DGNST_COMPLEX_HEADER ||
        psElement->stype == DGNST_TEXT_NODE )  // compatible structures
    {
        DGNElemComplexHeader *psCT = (DGNElemComplexHeader *) psElement;

        psCT->totlength += (nLinkSize / 2);

        psElement->raw_data[36] = (unsigned char) (psCT->totlength % 256);
        psElement->raw_data[37] = (unsigned char) (psCT->totlength / 256);
    }

/* -------------------------------------------------------------------- */
/*      Ensure everything is updated properly, including element        */
/*      length and properties.                                          */
/* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended( hDGN, psElement );

/* -------------------------------------------------------------------- */
/*      Figure out what the linkage index is.                           */
/* -------------------------------------------------------------------- */
    int iLinkage = 0;  // Used after for.
    for( ; ; iLinkage++ )
    {
        if( DGNGetLinkage( hDGN, psElement, iLinkage, nullptr, nullptr, nullptr, nullptr )
            == nullptr )
            break;
    }

    return iLinkage - 1;
}

/************************************************************************/
/*                        DGNAddShapeFileInfo()                         */
/************************************************************************/

/**
 * Add a shape fill attribute linkage.
 *
 * The target element must already have raw_data loaded, and it will be
 * resized (see DGNResizeElement()) as needed for the new attribute data.
 * Note that the element is not written to disk immediate.  Use
 * DGNWriteElement() for that.
 *
 * @param hDGN the file to which the element corresponds.
 * @param psElement the element being updated.
 * @param nColor fill color (color index from palette).
 *
 * @return -1 on failure, or the link index.
 */

int DGNAddShapeFillInfo( DGNHandle hDGN, DGNElemCore *psElement,
                          int nColor )

{
    unsigned char abyFillInfo[16] =
    { 0x07, 0x10, 0x41, 0x00, 0x02, 0x08, 0x01, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

    abyFillInfo[8] = (unsigned char) nColor;

    // coverity[overrun-buffer-arg]
    return DGNAddRawAttrLink( hDGN, psElement, 16, abyFillInfo );
}
